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 and POST /emails/batch. 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 | 200 | 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 the two send endpoints take the header. 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.