How do I use idempotency keys with Resend to prevent duplicate sends on retries?
Communications APIs (CPaaS)

How do I use idempotency keys with Resend to prevent duplicate sends on retries?

9 min read

Idempotency keys are one of the simplest and most reliable ways to prevent duplicate emails or messages when using Resend—especially in retry scenarios where network errors, timeouts, or transient failures can cause your code to re-send a request. By attaching a stable, unique idempotency key to each logical “send,” Resend will treat repeated requests with the same key as the same operation and avoid sending duplicates.

This guide explains how to use idempotency keys with Resend to prevent duplicate sends on retries, how they work under the hood, and how to design keys that align with your application’s needs.


What is an idempotency key in Resend?

An idempotency key is a unique identifier you generate on the client (or server) side and send along with your email or message request. Resend stores and reuses the result for that key, so:

  • The first request with a new idempotency key is processed normally.
  • Subsequent requests with the same idempotency key return the same response as the first one, without sending the email again.

In other words, even if your code retries the request multiple times (because it isn’t sure if the previous attempt succeeded), Resend will send the email once and keep the result tied to that key.


Why idempotency keys matter for retries and duplicate sends

In real-world systems, retries are common:

  • Your server times out waiting for Resend’s response.
  • A network glitch interrupts the request.
  • A queue or job worker automatically retries failed jobs.

Without idempotency keys, every retry is treated as a new send, and you can end up with:

  • Duplicate emails to the same recipient.
  • Confusing user experiences (e.g., multiple confirmation or receipt emails).
  • Increased sending costs and potential deliverability issues.

By using idempotency keys with Resend, you ensure that retries are safe, avoiding duplicate sends even when your infrastructure is unreliable.


How idempotency works with Resend under the hood

While implementation details can evolve, the general behavior is:

  1. You send a request to Resend with an Idempotency-Key header (or equivalent configuration in the SDK).
  2. Resend checks if that key has been seen before:
    • If not, it processes the request, sends the email, and stores the result associated with the key.
    • If yes, it does not send the email again; it simply returns the stored result.
  3. The response you get for the same idempotency key will be identical (or functionally equivalent) across retries.

Important implications:

  • Same key = same operation: If you reuse the key, you’re saying, “This is the same send as before.”
  • New key = new send: A different key means Resend will treat it as a separate email.

Where to use idempotency keys with Resend

You should consider idempotency keys whenever the same logical action might be retried or triggered multiple times:

  • Transactional emails:
    • Order confirmations
    • Password reset emails
    • Account verification / signup confirmations
    • Subscription confirmations or invoices
  • Webhook or event-based sends:
    • Job/process failures that get retried
    • Queue workers (e.g., background jobs) that may run the same task twice
    • Webhooks from payment processors or other systems that are delivered multiple times

In all these cases, you can generate a key based on a unique application-level identifier (e.g., order_id, user_id + event_type, or payment_id) and use that as the Idempotency-Key.


Basic pattern: one idempotency key per logical send

The core pattern is:

  1. Identify a logical send in your application (for example, “welcome email for user 123”).
  2. Generate or derive a unique, stable idempotency key for that send.
  3. Use that same key every time you attempt to send that email.

If your app retries because of a timeout, or your job worker runs the same job twice, those attempts will all share the same key.

Example key designs

Depending on your use case, keys might look like:

  • "user-<USER_ID>-welcome-email"
  • "order-<ORDER_ID>-confirmation-email"
  • "invoice-<INVOICE_ID>-receipt"
  • "password-reset-<USER_ID>-<RESET_REQUEST_ID>"

As long as the key uniquely represents “this email for this purpose,” Resend can guarantee idempotent behavior for that action.


Using idempotency keys with Resend in practice

Below are conceptual examples showing how you’d add idempotency keys in common integration patterns. Adjust syntax to match the official Resend SDK for your language/framework.

Example: Node.js / TypeScript with Resend email

Assume you have an order confirmation email that might be retried:

import { Resend } from 'resend';

const resend = new Resend(process.env.RESEND_API_KEY);

async function sendOrderConfirmation(orderId: string, to: string) {
  // Derive a stable idempotency key for this order confirmation
  const idempotencyKey = `order-${orderId}-confirmation-email`;

  try {
    const response = await resend.emails.send({
      from: 'Sales <sales@yourdomain.com>',
      to,
      subject: `Your order ${orderId} confirmation`,
      html: `<p>Thank you for your order ${orderId}.</p>`,
      // Many SDKs expose a place to pass custom headers or idempotency
      headers: {
        'Idempotency-Key': idempotencyKey,
      },
    });

    return response;
  } catch (error) {
    // Your retry logic can safely retry based on error type
    throw error;
  }
}

In your retry logic (e.g., a job queue), always call sendOrderConfirmation with the same orderId, and you’ll automatically reuse the same idempotency key.

Note: Depending on Resend’s SDK updates, idempotency may be available as a top-level option (e.g., idempotencyKey) instead of a raw header. Always check the latest SDK docs for the preferred field.


Using idempotency keys with HTTP requests directly

If you’re calling Resend over HTTP without an SDK, you can usually pass an Idempotency-Key HTTP header yourself:

curl -X POST https://api.resend.com/emails \
  -H "Authorization: Bearer $RESEND_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: order-12345-confirmation-email" \
  -d '{
    "from": "Sales <sales@yourdomain.com>",
    "to": "customer@example.com",
    "subject": "Your order 12345 confirmation",
    "html": "<p>Thank you for your order 12345.</p>"
  }'

If this exact curl command runs multiple times (e.g., from a job retry), Resend will only send the email once, and return the same response for that Idempotency-Key.


Handling retries with idempotency in your application

Idempotency keys handle the API side of deduplication. You still need to design your app’s retry behavior:

  1. Detect transient errors
    Only retry when it makes sense—e.g., network errors, timeouts, or 5xx responses.

  2. Always reuse the same idempotency key for the same logical send
    If your job worker or retry system regenerates the key on each attempt, you lose idempotency and risk duplicates.

  3. Store the key alongside your domain object
    When sending a message related to an entity (order, invoice, user), store the idempotency key in your database so it can be reused later if needed.

  4. Design idempotent job payloads
    Queue jobs should include enough information to reconstruct the idempotency key. For example:

    • Job payload: { "orderId": "12345", "type": "sendOrderConfirmation" }
    • In the worker: const key = "order-" + payload.orderId + "-confirmation-email";

Choosing a good idempotency key strategy

The right key design for preventing duplicate sends with Resend depends on your use case.

1. One-time transactional emails

For emails that should be sent exactly once per entity (e.g., order confirmation):

  • Use a key directly tied to that entity:
    • order-<ORDER_ID>-confirmation-email
  • This ensures:
    • Multiple attempts for the same order generate one email.
    • Sending a different email for a different order uses a different key.

2. Emails that can be sent multiple times intentionally

Some emails can be legitimately sent multiple times, like password resets:

  • Use a unique identifier per reset request:
    • Generate a reset_token or reset_id each time the user initiates a reset.
    • Use password-reset-<USER_ID>-<RESET_ID> as the idempotency key.
  • Retries for the same reset request share the same key (no duplicates).
  • New reset requests get new keys and new emails.

3. Digest or batch emails

If you send daily/weekly digests:

  • Base keys on the time period and recipient:
    • daily-digest-<USER_ID>-2024-04-12
    • weekly-digest-<USER_ID>-2024-W15

This ensures that retries for the same digest don’t duplicate, while future digests still go out.


Common mistakes to avoid when using idempotency keys with Resend

To effectively prevent duplicate sends on retries, avoid these pitfalls:

  1. Generating a random key on each attempt

    • Wrong: idempotencyKey = crypto.randomUUID() inside retry logic.
    • This makes Resend treat each retry as a new send.
    • Instead: Base the key on a stable business identifier (order, invoice, event).
  2. Using the same key for different logical emails

    • If you reuse a key for different messages, Resend will consider them the same and might not send the new one.
    • Ensure keys reflect both the subject/entity and the specific email type.
  3. Not logging or storing keys

    • Without logging, debugging issues becomes harder.
    • Save idempotency keys in logs or related database records so you can correlate sends with Resend’s responses.
  4. Mixing idempotency keys across environments

    • Use environment prefixes to avoid collisions between staging and production:
      • prod-order-123-confirmation-email
      • staging-order-123-confirmation-email

Observing idempotent behavior with Resend

To confirm idempotency is working as expected:

  1. Send a request with a known Idempotency-Key.
  2. Repeat the same request with the identical key:
    • The first time: email is sent; Resend returns a success response.
    • The second time: email should not send again, but the response matches the first one.
  3. Check the recipient inbox and Resend dashboard/logs:
    • You should see only a single email for that idempotency key.
    • The dashboard (if provided) may show metadata about the key or deduplicated requests.

Integrating idempotency keys with broader reliability patterns

Using idempotency keys with Resend is part of a larger reliability strategy:

  • Retry policies:
    Configure exponential backoff and max retries. Idempotency ensures retries don’t cause duplicates.

  • Job queues / background workers:
    Each job should:

    • Know its idempotency key.
    • Use that key consistently across attempts.
    • Be safe to rerun without side effects beyond the email send (e.g., don’t double-bill or double-update statuses).
  • Application-level state tracking:
    In some cases, you may also want to track if an email was sent in your own database:

    • A flag like confirmation_email_sent_at or welcome_email_sent on your entity.
    • Combine this with idempotency keys for defense in depth.

Summary: how to use idempotency keys with Resend to prevent duplicate sends on retries

To prevent duplicate sends on retries with Resend:

  1. Generate a stable, unique idempotency key for each logical email send (e.g., per order, per reset request, per digest period).
  2. Include the key with every Resend request associated with that email, usually via an Idempotency-Key header or SDK option.
  3. Reuse the same key across retries, so Resend can recognize the operation and avoid sending duplicates.
  4. Align keys with your business logic, making sure they represent the specific message type and resource.
  5. Log and store idempotency keys to debug issues and track send behavior.

By following these practices, you can confidently implement retries in your application while ensuring Resend sends each email only once, even if your code attempts to send it multiple times.