Idempotency
A network timeout tells you nothing about whether the request arrived. Retrying blindly can send the same email twice. An idempotency key makes the retry safe.
Send one on any POST:
Idempotency-Key: order-4821-welcome
If we have already processed that key, we return the original response instead of doing the work again.
What happens on a retry
curl https://api.epostix.com/v1/emails \
-H "Authorization: Bearer $EPOSTIX_API_KEY" \
-H "Idempotency-Key: order-4821-welcome" \
-H "Content-Type: application/json" \
Run that twice and you get the same id both times, one email, and on the second response:
Idempotent-Replayed: true
That header is how you distinguish a replay from a fresh send.
Keys are remembered for 24 hours, scoped to your workspace and to the specific endpoint.
The same key on POST /emails and on POST /suppressions are unrelated.
The three answers you can get
The same response, with Idempotent-Replayed: true. The original request finished. You
are looking at its result.
idempotency_conflict, 409. The key has been used with a
different body. This nearly always means the key is not as unique as you thought. It is a
signal, not an obstacle: something is reusing a key across genuinely different sends.
idempotency_in_progress, 409 with Retry-After: 1.
An identical request is still running. This is what stops two racing retries both sending.
Wait and try again, and you will get the first request's response.
Choosing a key
Derive it from the thing you are reacting to, not from the moment you are reacting.
order-4821-welcome
invoice-99120-reminder-2
password-reset-user-7731-2026-08-19T13:04:11Z
A key built from your own identifiers survives a process restart, a queue redelivery and a retry from a different worker. A random UUID generated at call time does not: the retry generates a new one and sends a second email, which is exactly the failure you were trying to prevent.
Keys may be up to 255 characters.
When errors are and are not remembered
A request that failed with a 5xx is not cached. Retry it with the same key and it runs again, which is what you want when the failure was ours.
A request rejected for being invalid is likewise not stored, so fixing the body and retrying with the same key works.
Where it is worth using
Anywhere a duplicate would be visible to a person: order confirmations, password resets, one-time codes, invoices. Anywhere your sender is a queue consumer, since redelivery is normal rather than exceptional.
Idempotency and duplicate detection solve different problems. Idempotency protects a single logical send from being repeated by retries. Duplicate detection catches a loop hammering one recipient with the same message.