GuidesIdempotency
Idempotency
A network timeout does not tell you whether the request arrived. An idempotency key makes it safe to find out by asking again.
How to use it
Send an Idempotency-Key header on POST /emails, POST /emails/batch and the two test sends, POST /broadcasts/{id}/test and POST /templates/{id}/test. Choose the value from something that identifies the work, not the attempt — an order ID, a job ID, a UUID you generate once and reuse across retries.
Idempotency-Key: order-1042- 1–256 characters.
- Scoped to your team, so two teams may use the same string without colliding.
- Remembered for 24 hours, counted from the first request.
Generating a fresh key on each retry defeats the mechanism entirely — that is the one mistake worth guarding against. The key must be the same string every time you retry the same send.
What happens on a repeat
| Situation | Status | Answer |
|---|---|---|
| Same key, same payload, first request still running | 409 | concurrent_idempotent_requests |
| Same key, same payload, first request finished | The original status | The original response, with Idempotent-Replayed: true |
| Same key, different payload | 409 | invalid_idempotent_request |
| A key shorter than 1 or longer than 256 characters | 400 | invalid_idempotency_key |
The payload is compared by fingerprint, so a reordered JSON object is still the same payload. A genuinely different body under a key you have already used is refused rather than sent: two different emails under one key is a bug, and we would rather surface it than pick one.
Batches
One Idempotency-Key covers a whole batch, not each message inside it. Retrying the batch replays the whole batch; there is no partial replay.
Everything else
Only those four send endpoints take the header, and a custom event sent to POST /events/send deliberately takes none: two calls are two events. GET and DELETE are naturally idempotent — repeating them changes nothing — and the remaining writes are either keyed by a resource you name or are safe to repeat.