# 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.

An idempotency key:

```text
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.
