Microsoft 365 was down for roughly 25 hours this week — and the outage didn't start with a server dying. It started with a health check doing exactly what it was designed to do.

The timeline, in short: the outage began on November 12 at 11:17 ET, ran about 25–26 hours, and was largely recovered by 13:00 ET on November 13. As of November 14, Microsoft still lists it as an open degradation. Downdetector recorded 130,000+ Outlook reports at the peak, across 70+ countries. Email delivery was delayed by up to 48 hours.

This post is about the failure mode behind those numbers — a cascade where the safety mechanism itself became the amplifier — and about the part nobody budgeted for: when Outlook went down, the ChatGPT and Claude email and calendar connectors went down with it, because they share the same failure domain.

The timeline

  • Nov 12, 11:17 ET — Outage begins. Incident IDs EX1550543 / MO1550821.
  • Nov 12–13 — Exchange Online, Outlook, Teams, SharePoint, Copilot, Intune, Planner, To Do, Defender XDR and Purview are all affected. Downdetector's Outlook reports peak above 130,000.
  • Nov 13, 13:00 ET — Broad recovery after roughly 25–26 hours.
  • Nov 14 — Still listed as an open degradation. Some mail is still catching up, with delivery delays stretching to 48 hours.

No company names were attached to the impact reports in the source material, and this post doesn't add any. The numbers above are the public ones.

The cascade: when the safety net pulls the floor out

Microsoft's preliminary account is short, and worth reading literally:

  1. An earlier network capacity upgrade introduced a defect.
  2. A recent network change made that defect surface.
  3. Health checks started failing on affected servers.
  4. Failing servers were removed from service — exactly as designed.
  5. The remaining servers took the load, and the secure-connection core process overloaded.

Step 4 is the one that matters. A health check is a binary decision: healthy or not, in or out. When the thing that makes servers look unhealthy is shared — a bad network change, not a bad server — the health checks fire everywhere at once. Each removal concentrates more load on the survivors, which makes their health checks more likely to fail, which removes more servers.

That is the cascade: the protection mechanism has no concept of "too many servers failing at once must mean the check is wrong." It just keeps doing its job, one server at a time, until there is nothing left to protect.

Two things Microsoft has ruled out, on the record: this was not a cyberattack, and it had nothing to do with AI.

The part nobody budgeted for: your AI connectors were in the blast radius

Here is the detail that turns an email outage into an agent outage. The ChatGPT and Claude email and calendar connectors stopped working during the same window — not because the models were down, but because the connectors live in the same failure domain as the mailbox they read from.

If your agent's morning routine is "read my email, check my calendar, draft the replies," then an Outlook outage is not a comms problem. It is a tooling outage. The agent loses its eyes and hands at the same time, and nothing in the agent's own error handling can tell the difference between "no new mail" and "the mailbox is unreachable."

The fix is not "add a retry." The fix is to treat the connector as a dependency with its own health signal, and to degrade in a way a human can see:

# Circuit breaker for a mailbox connector: fail loud, fail once import time class ConnectorBreaker: def __init__(self, failures_before_open=3, cooldown_seconds=300): self.failures = 0 self.open_until = 0.0 self.failures_before_open = failures_before_open self.cooldown_seconds = cooldown_seconds def call(self, fn, *args, **kwargs): if time.time() < self.open_until: # Don't hammer a sick service — and don't pretend it's fine raise RuntimeError("mail connector is in cooldown; showing stale data") try: result = fn(*args, **kwargs) except Exception: self.failures += 1 if self.failures >= self.failures_before_open: self.open_until = time.time() + self.cooldown_seconds self.failures = 0 raise self.failures = 0 return result 

Three failures, then stop calling for five minutes and say so out loud. The worst outcome in this outage was not the downtime — it was agents silently acting on an empty inbox as if nothing had happened.

Health checks need tiers, not just thresholds

The deeper lesson is about the health check itself. A single binary check cannot distinguish "this server is sick" from "the network is sick." Tiered checks can:

# Health-check tiers: a node-level failure and a fleet-level failure # must trigger different responses health_checks: - name: node_process scope: single_server on_fail: remove_server_from_rotation - name: fleet_connectivity scope: fleet_wide on_fail: freeze_removals_and_page_oncall # If >20% of servers fail checks within 5 minutes,  # the check is suspect — stop removing servers, alert a human.  fleet_failure_threshold: 0.20 window_seconds: 300 

The rule is simple: if a fifth of the fleet fails at once, the problem is almost certainly not the fleet. Freeze removals, page a human, and let the servers that are still up keep serving while someone looks at the shared cause.

Degradation checklist

[ ] Map every AI connector to the service it reads from — the mailbox IS the dependency [ ] Give connectors their own circuit breaker; never let an agent act on an empty inbox silently [ ] Show staleness in the UI: "last successful sync" beats a silent empty state [ ] Tier health checks: node-level failures remove servers, fleet-level failures freeze removals [ ] Pre-write the comms: who tells users, and what the fallback workflow is, before the outage [ ] After recovery, audit what the agent did during the gap — silent actions need a second look 

Honest limitations

First, the root cause above is Microsoft's preliminary wording — a defect from a capacity upgrade, surfaced by a later network change, amplified by health-check removals. It is not an independent diagnosis, and the details may change as the investigation continues.

Second, the code samples are generic patterns written for this post, not Microsoft's code and not a reproduction of the failure. They illustrate the design principle; adapt them to your own stack.

Third, the outage was still listed as an open degradation at the time of writing, with mail delays up to 48 hours. If you are reading this later, check Microsoft's service health page for the final status.

The takeaway

The outage lasted a day. The lesson lasts longer: health checks are safety equipment, and safety equipment needs its own failure mode. A check that can remove every server, one by one, without ever asking whether the check itself is wrong, is not a safety net — it is a countdown.

And for anyone building agents on top of email and calendars: your connector is only as reliable as the mailbox behind it. Plan the degradation before the outage, not during it.

Has an outage ever taken out your agent's tools while the model itself was fine? What did your fallback look like — or did you find out the hard way that there wasn't one?