Developer Guides
Idempotency keys, and why your retry sent two emails
Your request to send a message times out. Your client retries, correctly, because a timeout tells you nothing about whether the server acted.
Except it did act. The message was accepted; the response was lost. The retry sends a second one, and a customer gets two receipts.
What an idempotency key does
You generate a unique value per logical operation and send it with the request. The server records it alongside the result. A second request with the same key returns the stored result instead of doing the work again.
The retry becomes safe. It either completes the original operation or returns what the original operation already produced.
Choosing the key
The key must identify the operation, not the request. This is where it goes wrong.
A UUID generated fresh for each retry is useless — every attempt looks new. The key has to be generated once, before the first attempt, and reused for every retry of that same logical send.
Deriving it from what you are doing is usually cleaner than generating one:
order-9f2a1c-receipt
Deterministic, meaningful in a log, and naturally unique per operation. If your code can compute the same key twice for two genuinely different sends, it is the wrong key.
How long keys are kept
Twenty-four hours is typical, and it bounds the problem rather than solving it permanently. Retries happen within seconds or minutes; a day is generous.
Beyond the window the key is forgotten and a request carrying it is treated as new. This matters for scheduled jobs: a nightly task that uses the same key every night will be deduplicated on night one and sent normally on night two, which is almost certainly not what you meant. Include the date.
Batches
For a batch request the key covers the batch, not the messages in it. Retrying a batch returns the original batch result — including the per-message failures it contained.
Which means a partially failed batch cannot be fixed by retrying it. You read the per-message results and resend the failures individually, with their own keys. See partial failures in batch sends.
What is not idempotent
The key makes your request idempotent. It does not make delivery idempotent. If a message was accepted, delivered, and the recipient's server duplicated it, no key on your side prevents that.
It also does not help if you change the content and reuse the key. Most implementations will either return the original result — ignoring your change — or reject the request outright. Neither is what you wanted; use a new key for different content.
The practical rule
Generate the key where the decision to send is made, not where the HTTP call happens. If the key lives in the retry loop, it is new every attempt and does nothing.
DKIM signing, SPF alignment and DMARC are set up the moment you verify a domain.