429 Too Many Requests
Situations in the vault where SharePoint, SPFx, Microsoft Graph or the Azure services around them answer with HTTP 429. Each one explains why it happens and how to fix it.
- A newsletter looped over /me/sendMail is not bulk e-mail — and ACS Email is no longer the way outgotchas / graphPer-mailbox and tenant limits, bulk mail through Microsoft 365 "isn't a supported use", JSON sendMail can't set List-Unsubscribe, ACS Email retires 2028; keep Graph for personal batches, send newsletters through an ESP…
- A permission check that only asks "who has too much?" passes on an empty ACLgotchas / permissions"no principal outside the allow-list can write" is also satisfied when nobody can write at all; assert the opposite direction with a non-zero writer count, and read the principals strictly so a transient 429 cannot…
- Azure OpenAI 429 on a single request is a TPM ceiling, not a busy service — measure size, not frequencygotchas / azure-functionsOne request whose prompt exceeds the deployment's TPM allowance can never fit the window, so retrying is futile — a sub-second rejection means "over the ceiling", not "busy"; separate the two with a small/large request…
- A throttled permission probe must not downgrade — and cache — the rolegotchas / rest-apiA 429/403 on currentuser/groups resolves to the lowest role; cache it and the user is stuck read-only for the TTL; only persist a confirmed (200) result
- Retry only fast failures — a gateway error after 230 seconds is a timeout, not a hiccupgotchas / azure-functionsThe 502/503/504 a client rightly retries for transient failures also comes back when a request ran out of time (Azure Load Balancer returns 502 after 230 s), and retrying that multiplies the wait (3 × ~230 s) and the…
- Retry-After does not survive CORS — a header you send and the client reads can still arrive as nullgotchas / azure-functionsA cross-origin response exposes only the headers named in Access-Control-Expose-Headers plus a short safelist, so an API that sends Retry-After and a client that reads it still meet at null — both ends look right and a…
- Retrying a throttled call is safe for GET onlygotchas / rest-api429 means "not now", not "nothing happened"; no idempotency key exists, so a retried write duplicates the row (+ RateLimit-* headers are not guaranteed for delegated calls)
- Search REST returns 500 "unknown error" and 429 "low resources" for minutes at a time — retry the read, don't fix the querygotchas / searchSharePoint Search REST can fail for a few minutes with HTTP 500 SearchServiceException (no Retry-After) and HTTP 429 low-resources errors; the same query succeeds seconds later, so retry reads briefly with a fixed…
- /me/sendMail from an account without a mailbox can time out with 504 – and the Graph SDK retries it three more timesgotchas / graphNo MailboxNotEnabled error, just a 504 after ~30 s; the SDK RetryHandler repeats the non-idempotent POST up to 3×, so the button hangs ~2 min and a late success can send duplicates; check assignedPlans for an enabled…
- Unbounded Promise.all fan-out over a customer-sized collection invites HTTP 429gotchas / rest-apiFan-out is safe when YOUR code decides the count, dangerous when the customer's data does; bounded batch and worker-pool patterns