Networks fail. A request can time out after the email was queued, and retrying without protection would send it twice. Idempotency keys solve this.

Use a stable idempotency key

Every send needs an Idempotency-Key header. Derive it from the event that triggers the email, and store it with that event:

EmailGood keyAvoid
Welcomewelcome-user_8f3k2A random UUID per attempt
Receiptreceipt-order_1042A timestamp
Password resetreset-request_77ab1The recipient's address alone

Retrying with the same key and request returns the original response — messageId, delivery IDs, and all — without sending again. A different request with a reused key is rejected with 400.

Decide what to retry

ResponseRetry?Why
Network error or timeoutYesThe email may or may not have been queued; the key makes the retry safe.
429 Too Many RequestsYes, after Retry-AfterUnless the body has a code for a plan limit — then retrying won't help.
500, 502, 503, 504YesTemporary server-side failures.
400, 401, 403, 404, 409NoFix the request, key, or permissions first.

Back off exponentially

Wait longer between attempts, add random jitter so many clients don't retry in lockstep, and cap the number of attempts.

TypeScript
async function sendWithRetry(send: () => Promise<Response>, attempts = 4) {
  for (let attempt = 0; ; attempt++) {
    const response = await send().catch(() => undefined);
    const retryable = !response || response.status === 429 || response.status >= 500;

    if (!retryable || attempt === attempts - 1) {
      return response;
    }

    const retryAfter = Number(response?.headers.get('Retry-After'));
    const backoff = 250 * 2 ** attempt + Math.random() * 250;
    await new Promise((resolve) => setTimeout(resolve, retryAfter ? retryAfter * 1000 : backoff));
  }
}

The Node.js SDK does this for you, reusing the same key on every attempt.

Send from a background job

For emails triggered by user actions, enqueue a job instead of sending inside the web request. The job stores the idempotency key, retries with backoff, and survives deploys and restarts — and your users never wait on email.

Nexiom Connect
Open dashboard