Mailbase
FeaturesPricingDocsBlogComparisonsChangelog
Sign inStart free
Home/Blog/Email Idempotency Keys: Prevent Duplicate Sends
Email infrastructureUpdated July 15, 20269 min read

Email Idempotency Keys: Prevent Duplicate Sends

A practical framework for using email idempotency keys to make retries safe, prevent duplicate sends, and keep email operations observable.

By Mailbase Team · Target keyword: email idempotency keys
Glowing network cables converging into a switch — Email Idempotency Keys: Prevent Duplicate Sends
Photo from Unsplash
On this page
OverviewWhy duplicate emails happen in healthy systemsDesign the key around the logical email, not the attemptStore a send state machine, not just a sent flagMake retries, webhooks, and replays idempotent togetherImplementation checklist for idempotent email sendsWhere Mailbase fits in idempotent email operationsCommon MistakesSources & Further ReadingRelated guidesFAQRelated reading

Overview

Email idempotency keys are stable identifiers that tell your sending system, 'this logical email has already been requested.' When a worker crashes, a webhook arrives twice, or an API client retries after a timeout, the key lets the system return the original result instead of sending the same password reset, receipt, onboarding nudge, or campaign message again.

The safest model is simple: every logical send gets one deterministic key, every send attempt is stored, and retries reuse the key until the message reaches a terminal state. That turns duplicate prevention from a hopeful code comment into a database-backed operating rule.

Why duplicate emails happen in healthy systems

Glowing server rack and cabling — Why duplicate emails happen in healthy systems
Photo from Unsplash

Duplicate emails are usually not caused by careless developers clicking 'send' twice. They happen because good production systems retry work: HTTP clients retry after timeouts, queues redeliver jobs after worker crashes, providers retry webhooks, and operators replay failed batches after an incident. Without a stable idempotency boundary, every retry can look like a brand-new send request.

Email makes this worse than many product events because the side effect is external. You can delete a duplicate database row; you cannot unsend a second invoice email or a repeated security alert. Idempotency gives the system a memory of intent so retry logic can be aggressive without becoming recipient-hostile.

  • Timeouts can hide a successful provider request from your app
  • Queue workers can crash after sending but before marking the job complete
  • Webhook replays can trigger downstream automation more than once
  • Manual incident replays are safer when the dedupe rule is already enforced

Design the key around the logical email, not the attempt

Screen full of analytics charts and metrics — Design the key around the logical email, not the attempt
Photo from Unsplash

An idempotency key should describe the email the recipient is supposed to receive, not the worker attempt that happens to process it. If a password reset request creates one email, the key might combine `password_reset`, the user ID, and the reset token ID. If a receipt is tied to an order, use the order ID and receipt type. If a campaign targets a contact, combine campaign ID, recipient ID, and variant ID.

Do not use random UUIDs generated inside the worker as your only key. A random value per attempt prevents deduplication because every retry looks unique. Random IDs are useful for provider message IDs and attempt rows; the idempotency key should remain stable across retries of the same logical send.

  • The same logical email should always produce the same key
  • Different variants or materially different versions should get distinct keys
  • Include recipient identity when a campaign sends the same message to many people
  • Keep keys opaque enough that logs do not leak personal data
MailstreamGood key shapeAvoid
Password resetpassword_reset:user_id:reset_token_idA new UUID per worker retry
Receiptreceipt:order_id:receipt_versionCustomer email plus timestamp only
Trial onboardingonboarding:user_id:step_idA daily cron timestamp with no user state
Campaigncampaign:campaign_id:recipient_id:variant_idOnly the campaign ID
Webhook-triggered follow-upfollowup:source_event_id:rule_idThe webhook delivery attempt ID

Store a send state machine, not just a sent flag

A single boolean like `sent = true` is too weak for real email operations. Store the key, recipient, template or campaign version, provider, provider message ID, current state, first requested time, last attempt time, attempt count, and terminal result. Then make the idempotency key unique in the database for the scope that matters: usually workspace plus mailstream plus key.

The state machine should let callers distinguish 'already accepted' from 'still processing' and 'safe to retry later.' A request that finds the same key in `accepted` can return the original provider message ID. A request that finds `processing` can return a 202-style response or wait behind a lock. A request that finds a terminal failure can decide whether the same key may be retried or whether a new corrected send is required.

  • Use a unique constraint, not only application-level checks
  • Separate logical send intent from provider attempts
  • Keep provider message IDs connected to your internal send IDs
  • Make duplicate requests observable so clients learn they are retrying too often
  1. Create a send-intent row with a unique idempotency key before calling the provider.
  2. Acquire the row or lock so only one worker performs the external send.
  3. Record every provider attempt with timestamps and responses.
  4. Move the intent to accepted, failed, suppressed, or cancelled with a clear reason.
  5. On duplicate requests, return the existing state instead of creating a new send.

Make retries, webhooks, and replays idempotent together

Sending idempotency is only half the pattern. Delivery, bounce, complaint, and open/click webhooks can also be delivered more than once or arrive out of order. Treat provider event IDs, provider message IDs, and event timestamps as their own dedupe layer so analytics, suppression, and automations do not double-count the same signal.

For batch or campaign replays, require the replay tool to reuse the original idempotency key for each recipient unless the operator explicitly creates a new send version. That small constraint prevents the most painful incident pattern: a team fixes a partial failure by replaying the whole segment and accidentally emails everyone twice.

  • Retry policy and idempotency policy should be designed together
  • Webhook dedupe protects both analytics and compliance actions
  • Operator replay tools must show which recipients are already terminal
  • A new key should mean a genuinely new email, not a hidden retry
WorkflowIdempotency ruleOperational benefit
API retryClient sends the same idempotency key for the same requestTimeouts do not create duplicate messages
Queue retryWorker locks the existing send-intent rowCrashes are safe to recover from
Webhook replayStore provider event ID plus message IDAnalytics and suppression are not double-applied
Campaign replayReuse campaign-recipient-variant keysPartial failures can be retried without resending successes
Template correctionCreate a new versioned key deliberatelyA corrected email is explicit, not accidental

Implementation checklist for idempotent email sends

Start with the mailstreams where duplicates hurt most: password resets, login codes, receipts, invoices, trial lifecycle messages, and high-volume campaigns. Add idempotency at the boundary where your product asks for an email, not deep inside a provider adapter where business context has already been lost.

Then test the ugly cases on purpose. Simulate a provider timeout after acceptance, kill a worker after the provider call but before local completion, replay a webhook twice, and rerun a partially failed campaign segment. If every test returns the same logical send result or cleanly skips already terminal recipients, the system is ready for real incidents.

  • Do not wait for a duplicate-send incident to add the unique constraint
  • Make the key visible in logs and support tooling
  • Version keys when the content or recipient intent truly changes
  • Keep suppression checks inside the same send-intent workflow
  1. Define the logical-send key shape for each transactional and campaign mailstream.
  2. Add a database unique constraint on workspace, mailstream, and idempotency key.
  3. Record send-intent rows before external provider calls.
  4. Store provider attempts separately from the logical send result.
  5. Return existing state for duplicate API calls or queue jobs.
  6. Dedupe webhook events before updating analytics, suppression, or automations.
  7. Build replay tooling that reuses original keys by default.

Where Mailbase fits in idempotent email operations

Mailbase sits above the sending layer, so the practical value is workflow visibility: durable send jobs, scheduled sends, analytics, suppression/compliance controls, reply context, and webhook ingestion all benefit from stable send identity. If a campaign worker retries or a provider event arrives twice, operators need to know whether the recipient actually received one email, two attempts, or no accepted message at all.

For teams using Mailbase with hosted sending or a BYO useSend setup, idempotency still belongs in the operating model. The workflow layer can make send state, retries, replies, and suppression easier to manage, but the team should still design each transactional or campaign trigger so the same logical send has one stable identity.

  • Use durable send jobs and analytics to separate attempts from accepted sends
  • Connect suppression and unsubscribe checks before retrying optional mail
  • Keep reply context tied to the original campaign or transactional trigger
  • Use MCP or internal tooling to inspect send state before replaying a failed batch

Common Mistakes

  • Choosing a tool before deciding who owns deliverability.
  • Treating DNS authentication as a one-time checkbox instead of an operating baseline.
  • Mixing product-critical transactional email with experimental marketing sends, with no clear boundary.
  • Trusting headline metrics (like open rate) that privacy proxies now inflate.

Sources & Further Reading

Official docs for current setup details, pricing, and API behavior — verify specifics there, since they change.

RFC 5321: Simple Mail Transfer Protocol
Amazon SES docs

Related guides

More on email idempotency keys and the surrounding email infrastructure workflow:

Mailbase API docs
API token docs
transactional email for SaaS
email API vs SMTP
SMTP relay for SaaS
AWS SES email stack
reply inbox workflow
Try Mailbase free
Send 200 emails a month on us. Paid plans start at €9 — or bring your own useSend for €5.
See plans

FAQ

What is an email idempotency key?

An email idempotency key is a stable identifier for one logical email send. If the same request is retried, the system uses the key to return the original send state instead of sending another copy.

Should an idempotency key be random?

It can include opaque IDs, but it should not be a new random value for every retry. The key must stay the same for the same logical email, such as a receipt for one order or one campaign variant for one recipient.

Do idempotency keys replace email retry logic?

No. They make retry logic safe. You still need retry policies for temporary failures, but the idempotency key prevents those retries from creating duplicate external sends.

Where should email idempotency be enforced?

Enforce it at the send-intent boundary with a database unique constraint, before the external provider call. Application checks are useful, but the database constraint is what protects you during concurrency, crashes, and replay tools.

Related reading

Transactional email7 min read
Transactional Email for SaaS: The Practical Guide
A practical guide to transactional email for SaaS: what counts as transactional, choosing an API or SMTP provider, templates, deliverability, idempotency, and the operations that keep critical mail flowing.
Deliverability7 min read
Email API vs SMTP: Which Should You Use?
Email API vs SMTP compared for SaaS: how each works, reliability, speed, debugging, features, and portability — plus why many teams use both.
Transactional email7 min read
SMTP Relay for SaaS Apps: What to Use and Why
What an SMTP relay is, when a SaaS app should use one, how it differs from an email API, the providers to consider, and the DNS and production tradeoffs.
Transactional email7 min read
AWS SES Email Stack for SaaS Builders
How to think about Amazon SES as part of a SaaS email stack: what SES is and isn't, the layers you build on top of it, the sandbox/SNS setup, and where it fits versus a full API.
Product6 min read
Reply Inbox Email Workflow in Mailbase
A product guide to the Mailbase reply inbox for teams that want campaign replies, support context, and follow-up work in one place.
Email infrastructure9 min read
Email Webhook Events: A Practical Processing Guide
A practical email webhook events playbook for building idempotent ingestion, clean event models, suppression updates, and reliable analytics.
Mailbase
Product
FeaturesPricinguseSend integrationChangelog
Learn
BlogDocsAPI referenceResources
Compare
ComparisonsAlternatives
Guides
Transactional email servicesSelf-hosted useSend stackSelf-hosted email marketingSPF, DKIM & DMARCEmail deliverability
Legal
TermsPrivacy
© 2026 Mailbase · french-webEmail workflow for builders.