Envello
Engineering

Idempotency keys: why your email-sending code needs them

Envello Team·2026-07-17·6 min read

A network timeout on a payment API is annoying. A network timeout on an email-sending API, followed by an automatic retry, can mean a customer gets the same password-reset or order-confirmation email twice, or worse, gets charged twice if your retry logic re-triggers a downstream action. Idempotency keys are the standard fix, and they matter more for email sending than most APIs you'll integrate with.

How it actually works

Pass an idempotencyKey alongside your send request. If a request with that same key and the same request body comes in again, whether because your code retried after a timeout, a queue redelivered a job, or a proxy duplicated the request, Envello replays the original response instead of sending a second email. Your code gets the result it expected without a duplicate send actually happening.

Where to generate the key

The key should be generated once, at the point where the "this email needs to go out" decision is made, not regenerated on every retry attempt. A natural choice is something tied to the business event itself: order-4821-confirmation, password-reset-user-9213-1721234567. If you generate a fresh random key on every retry attempt, you've defeated the purpose, the whole point is that retries reuse the same key so they collapse into one actual send.

How long the deduplication window lasts

Idempotency keys are deduplicated within a bounded window, not stored forever, typically 24 hours is enough to cover any realistic retry scenario (a timed-out request, a redelivered queue job) without requiring indefinite storage of every key ever used. A genuinely new send for the same business event outside that window (a real second password reset request the next day) needs its own new key anyway, since it's a legitimately different send.

Idempotency at the queue level, not just the API call

If your application uses a job queue to trigger sends (common for anything not sent synchronously inside a web request), the idempotency key should be generated when the job is enqueued, not when the worker picks it up. A job that's redelivered by the queue after a worker crash mid-processing should reuse the same key on its second attempt, otherwise the queue's own at-least-once delivery guarantee, which exists precisely to survive worker crashes, becomes a duplicate-send risk instead of a reliability feature.

Why this matters more for transactional email specifically

Most API integrations fail safely on a duplicate call, calling a read endpoint twice is harmless. Sending an email twice is directly visible to the end user in a way a duplicated internal API call usually isn't. A customer who gets two "your password was reset" emails within a second of each other notices, and it looks like a bug even when the underlying send logic is otherwise correct. Idempotency keys close that specific, user-visible gap for close to zero implementation cost.

Envello

EU-hosted transactional email, done right by default.