Email API Idempotency Keys: Model the Intended Message

Email API Idempotency Keys: Model the Intended Message

Use email idempotency keys correctly by preserving operation identity, immutable payloads and recovery evidence instead of generating a new key per retry.

SendDart Team

TL;DR

  • Idempotency keys must model the intended business message, not individual HTTP attempts, so correct design preserves one intended message through repeated attempts and complements durable application records.
  • Create and persist a purpose-specific operation identity before sending—use an internal notification ID and durable outbox so retries reconnect to the same intended notification rather than random worker attempts.
  • Treat uncertain outcomes explicitly and verify recovery with tests: store uncertain state and original evidence, and exercise repeated requests, concurrent submissions and worker restarts to validate behavior.

The key represents intent, not an HTTP attempt

An idempotency key identifies one intended operation so repeated submissions can be recognized as the same work under a provider's documented contract. For email, that operation might be an order confirmation, a particular reply or a specific batch. It should not mean “whatever this worker happens to send right now.”

The distinction is simple but important. If a worker generates a new key every time it retries, the provider sees new operations. If the application reuses one key for every message to a recipient, legitimate later messages can collide. Good key design starts with the business event and message purpose.

Idempotency is not a universal exactly-once guarantee. Its scope, retention, supported endpoints and conflict behavior depend on the provider. Your application still needs durable intent, concurrency control and a recovery process for uncertain outcomes.

Choose the right identity boundary

Consider a fictional order with one confirmation and two shipments. The confirmation is one intended message. Each shipment notification is another. Deriving all three keys from only the order ID would conflate distinct operations; deriving them from random worker attempts would fail to connect retries.

Persist an internal notification ID for each purpose and associate the provider operation key with it. The key can be opaque and need not reveal the recipient's email address or order details. Keep the explanatory mapping in your database where access can be controlled.

Create the identity before submitting the request. If it exists only in process memory, a crash may lose the key at the exact moment it becomes necessary for recovery. A durable outbox or notification table provides the place to retain it.

Related reading: Best Email API: Choose With a Production Acceptance Test.

Keep the payload stable during recovery

Reusing an operation identity should mean recovering the same message, not quietly editing it. Save the rendered content or an immutable template version and input snapshot. If a customer address or template changes, decide explicitly whether a new notification is authorized.

SendDart documents a conflict when the same key is reused with a different payload, and a separate conflict for an operation still in flight. Its supported send, batch, reply and forward operations honor the documented idempotency contract; unrelated resource operations do not become idempotent simply because the header is supplied. SendDart SDK documentation.

Do not “fix” a conflict by generating a new key without investigation. That can bypass the very protection intended to prevent duplicate sends. Determine whether the payload changed accidentally, another worker is active, or the application has correctly authorized a distinct operation.

Separate business deduplication from provider deduplication

Provider idempotency protects a supported external operation. Business deduplication prevents your application from creating multiple intended notifications for the same event. You usually need both.

For example, a payment processor may deliver the same event twice. The application should recognize the event and create one receipt notification. The provider key then protects retries of that notification. If the application creates two different notification records with two different keys, provider-level deduplication cannot infer that they represent the same business event.

Use database uniqueness or another concurrency-safe mechanism for the business boundary. A read-then-insert check without a constraint can race when two workers process the event simultaneously. Test the concurrent case rather than only sequential replays.

Treat uncertainty as a first-class state

A network timeout does not establish whether the remote service accepted the operation. Store an uncertain state and retain the original key, payload and any returned identifier. Follow the provider's documented recovery process before deciding to submit new work.

If evidence identifies an original message, retrieve or reconcile it where supported. Do not automatically send through a backup provider under a new key; idempotency generally does not extend across separate services. The backup can deliver a second copy while the first attempt completes.

Resend also documents its own idempotency-key behavior, which should be reviewed independently when integrating that service. Similar feature names do not establish identical scope or lifetime. Resend idempotency documentation.

Handle batches as a separate recovery problem

A batch can contain operations with different outcomes. If an interrupted response includes confirmed sends, reserved or attempted items and a never-attempted tail, preserve those distinctions. Replaying the full list under a new key can resend the completed prefix.

SendDart's recovery documentation describes evidence that must be reconciled before creating a new operation for known-unattempted items. Your worker should store the batch identity and item identities, not merely a single failed flag.

Design the user interface around that evidence. An operator should see why an item is safe to retry or why it remains uncertain. A bulk “send all again” action is dangerous when it hides partial progress.

Test the key lifecycle

Test a repeated request with the same key and same payload, the same key with changed content, concurrent submissions and a worker restart. Verify the application's state as well as the provider adapter's response handling.

Add a test where two different business events go to the same recipient and must both be sent. This catches overly broad key schemes. Add another where the same business event arrives twice with different delivery attempt IDs and must create only one notification.

Document how long your application retains operation evidence and what happens when provider-side recovery guarantees no longer apply. Do not claim that an old key protects an operation indefinitely unless the service explicitly guarantees it.

Correct idempotency design preserves one intended message through repeated attempts. It complements, rather than replaces, durable application records and honest handling of uncertainty.

Related reading: Scheduled Email Delivery: Store the Time, Content and Cancellation State.