Symptom
A message-sending API returned 400 Bad Request. Suspecting the payload, escaping, length and special characters were all adjusted in turn. None of it mattered.
Cause
The reason was in the response body.
{"description": "Bad Request: PEER_FLOOD"}
It was a per-sender rate limit for too many messages in a short window — nothing to do with content. The code caught HTTPError, took the status code, and discarded the body.
★★ The exception object carries the response. One e.read() reveals it. Take only the status code and the reason disappears entirely.
Diagnosis — same input, different sender
The identical body succeeded from a different sender. That is the evidence for "not a content problem."
★ Re-running with exactly one variable changed separates a content problem from a state problem.
Response — three stages
★ Channels that had been split by purpose became availability redundancy here. Splitting creates alternate paths as a side effect — with one sender there is no stage 3.
★★ Aside — the test consumed the production quota
The limit was hit by testing. Repeatedly clearing the idempotency file (the "already handled" list) and re-running meant sending the same notifications over and over.
★★ When testing something that consumes an external quota, "clear the processed list and run it again" multiplies your outbound volume. That is a path where testing breaks production. Keep a separate dummy recipient that costs no quota.