Skip to main content

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" \
-d '{"from":"[email protected]","to":["[email protected]"],"subject":"Welcome","html":"<p>Hi</p>"}'

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.