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

When you’re calling the Resend API from a backend or job worker that may retry on failures, idempotency keys are the safest way to prevent duplicate emails or messages from being sent. Instead of trying to manually track what has already been sent, you let Resend identify duplicate send attempts based on a unique key you provide with each request.

This guide walks through what idempotency keys are, how they work with Resend, how to generate them correctly, and how to integrate them into your retry and error-handling logic so you can prevent duplicate sends on retries.


What is an idempotency key in Resend?

An idempotency key is a unique string you attach to a send request so that if the same request is retried (for example, due to a network error or 5xx response), Resend can recognize it and avoid sending the same email or message multiple times.

Key behavior in Resend:

  • You generate the idempotency_key and send it with the request.
  • If Resend receives another request with the same idempotency_key and the same payload, it:
    • Does not create a new send.
    • Returns the original response (or an error if the original failed).
  • This means retries can safely be retried without causing duplicate sends.

Why you should use idempotency keys with Resend

Using idempotency keys with Resend to prevent duplicate sends on retries is important whenever:

  • Your HTTP client or job queue automatically retries failed requests.
  • You manually retry operations from logs, dashboards, or scripts.
  • Your network is flaky and you may not be sure if a request actually succeeded.
  • You have payment or transaction flows where sending the same email twice is problematic (e.g., receipts, password resets, one-time codes).

Without idempotency keys, each retry is treated as a brand-new send. With idempotency keys, retries become safe and predictable.


How idempotency keys work on retries

Here’s the lifecycle when using idempotency keys with Resend to prevent duplicate sends on retries:

  1. Initial request

    • Your backend calls POST /emails (or another Resend endpoint) with:
      • The full payload (to, from, subject, etc.)
      • A unique idempotency_key (typically as a header or field, depending on the SDK).
    • Resend processes the request and returns a response with an ID (e.g., email.id).
  2. Retry scenario

    • Your HTTP client doesn’t receive a response (timeout), or you get an error that triggers a retry.
    • Your retry logic runs the same request with the same idempotency_key.
  3. Resend behavior

    • Resend looks up the idempotency_key.
    • If it finds an existing request with that key:
      • It checks that the payload matches (idempotency keys should be tied to a specific request body).
      • If it matches, Resend returns the previously stored response.
      • No new email is sent.
    • If the payload does not match (which you should avoid), Resend will typically reject it as a conflict.

This pattern allows you to make your retry logic “fire and forget” regarding duplicates: you just ensure you reuse the same idempotency_key for the same logical operation.


Where to include idempotency keys in Resend

Depending on the Resend SDK or HTTP usage, idempotency keys are usually passed as:

  • A header (e.g., Idempotency-Key) in raw HTTP calls.
  • An option parameter in Resend client libraries (Node, Python, etc.).

Check the latest Resend documentation for exact syntax, but a common pattern looks like:

POST /emails HTTP/1.1
Host: api.resend.com
Authorization: Bearer YOUR_API_KEY
Content-Type: application/json
Idempotency-Key: password-reset-USER123-20240401T120000Z

{
  "from": "no-reply@example.com",
  "to": "user@example.com",
  "subject": "Reset your password",
  "html": "<p>Click here to reset your password…</p>"
}

If the same request is sent again with the same Idempotency-Key, Resend will prevent duplicate sends on retries.


How to generate good idempotency keys

When using idempotency keys with Resend to prevent duplicate sends on retries, the key design should follow a few principles:

1. Uniqueness per logical operation

An idempotency key should uniquely represent a single “logical send” operation, not just a single HTTP request attempt.

Examples:

  • Password reset: password-reset:userId:timestamp
  • Order confirmation: order-confirmation:orderId
  • Magic link login: magic-link:userId:nonce

2. Deterministic when needed

If you might need to regenerate the key for the same operation (e.g., from a job queue or admin tool), make it deterministic:

  • Use identifiers you already have: user IDs, order IDs, event IDs.
  • Combine them with the event type, not random values.

For instance:

email:order-confirmation:order_12345
email:password-reset:user_9876:request_abc123

This way, any system that knows the order ID or request ID can reconstruct the same idempotency_key.

3. Length and character set

In practice:

  • Keep it reasonably short (under 255 characters).
  • Use safe characters: letters, digits, -, _, :.
  • Avoid sensitive data in plain text (no full emails, no tokens, no PII unless hashed).

4. Optional: UUIDs for one-off events

For operations that truly happen once and don’t need deterministic reconstruction, you can use a UUID:

uuid: 09b7ab94-ea79-4e63-92e2-3a56f1e0c4b9

But remember: to prevent duplicate sends on retries, retried attempts must reuse the same UUID, not generate a new one.


Practical examples with Resend

Below are example patterns for using idempotency keys with Resend to prevent duplicate sends on retries in common flows.

Example 1: Node.js + Resend with automatic retries

import { Resend } from 'resend';
import crypto from 'crypto';

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

async function sendOrderConfirmationEmail(order) {
  const idempotencyKey = `order-confirmation:${order.id}`;

  try {
    const { data, error } = await resend.emails.send(
      {
        from: 'store@example.com',
        to: order.customerEmail,
        subject: `Your order ${order.id} is confirmed`,
        html: `<p>Thanks for your purchase!</p>`,
      },
      {
        // Example: hypothetical options object where Resend SDK
        // accepts headers or idempotency key
        headers: {
          'Idempotency-Key': idempotencyKey,
        },
      }
    );

    if (error) {
      throw error;
    }

    return data;
  } catch (err) {
    // Your retry logic can re-call this function.
    // As long as idempotencyKey stays the same, no duplicate email.
    throw err;
  }
}

Your retry mechanism (e.g., a job queue) can re-run sendOrderConfirmationEmail(order) if it fails; Resend will ensure no duplicate sends on retries.

Example 2: HTTP request with Axios or Fetch

import axios from 'axios';

async function sendPasswordResetEmail(userId, email, resetToken) {
  const idempotencyKey = `password-reset:${userId}:${resetToken}`;

  const payload = {
    from: 'no-reply@example.com',
    to: email,
    subject: 'Reset your password',
    html: `<p>Use this token: ${resetToken}</p>`,
  };

  const response = await axios.post(
    'https://api.resend.com/emails',
    payload,
    {
      headers: {
        Authorization: `Bearer ${process.env.RESEND_API_KEY}`,
        'Content-Type': 'application/json',
        'Idempotency-Key': idempotencyKey,
      },
      // Axios-level retries (via interceptors or plugins) can reuse this call.
    }
  );

  return response.data;
}

If the request times out or gets retried by your HTTP layer, using the same Idempotency-Key means Resend will prevent duplicate sends on retries.


Integrating idempotency keys with your retry strategy

To take full advantage of idempotency keys with Resend to prevent duplicate sends on retries, align them with your retry logic:

1. Store the idempotency key alongside your job or event

If you’re using a queue (e.g., BullMQ, Sidekiq, Celery):

  • Generate the idempotency_key when you enqueue the job.
  • Store it in the job payload.
  • Use the same key in every attempt of that job.

This ensures that every retry of the same job uses the same key.

2. Tie idempotency keys to business events

Rather than generating keys at the HTTP layer, generate them when the business event occurs:

  • On “order paid”: email:order-confirmation:orderId.
  • On “password reset requested”: email:password-reset:requestId.

Any code that sends the associated email uses the same key, so even if multiple components attempt the send, Resend prevents duplicates.

3. Handle partial failures gracefully

Some scenarios:

  • Client timeout, but email was sent
    You might re-send. With idempotency keys, Resend returns the existing send result instead of sending again.

  • First send fails before reaching Resend
    No existing idempotency key will be found, so the next attempt will create the send as normal.

  • First send returns a 4xx validation error
    Resend may remember this error for the idempotency key. Retries with the same invalid payload and key will repeat the error rather than creating a new send. Fix the payload or change the key only if it is a different logical operation.


Common mistakes to avoid

When using idempotency keys with Resend to prevent duplicate sends on retries, watch out for these pitfalls:

1. Generating a new random key for every retry

  • Problem: Each retry appears as a new request; Resend can’t know they’re related.
  • Fix: Generate one key per logical send and reuse it across all retries.

2. Using the same key for different emails

  • Problem: A single idempotency key reused for multiple different payloads can conflict.
  • Fix: Encode business context into the key, so each logical email type and event has its own namespace.

For example, avoid:

idempotency_key = userId

Instead:

idempotency_key = `welcome-email:${userId}`

3. Including sensitive data in clear text

  • Problem: Keys may appear in logs or monitoring tools.
  • Fix: If you must include sensitive identifiers, hash them (e.g., SHA-256) before using them in the idempotency key.

Testing your implementation

Before fully relying on idempotency keys with Resend to prevent duplicate sends on retries, test these scenarios:

  1. Repeated sends with the same key

    • Send the exact same payload with the same Idempotency-Key several times.
    • Confirm only one email is actually delivered (check logs, inbox, or Resend dashboard).
    • Confirm the API returns the same email.id for each call.
  2. Retries after network failures

    • Simulate a network timeout or manually cancel the request after sending.
    • Immediately retry with the same key.
    • Confirm only one email is delivered.
  3. Different payloads with the same key (should fail)

    • Send one payload with an idempotency key.
    • Change the payload (e.g., subject or body) and send again with the same key.
    • Expect an error indicating key conflict or payload mismatch.

Best practices checklist

To effectively use idempotency keys with Resend to prevent duplicate sends on retries:

  • Always generate an idempotency_key per logical email send event.
  • Use a deterministic format such as emailType:entityId[:extraContext].
  • Pass the key to Resend via header or SDK options.
  • Ensure retry logic reuses the same key for the same logical send.
  • Don’t reuse keys across unrelated emails or different payloads.
  • Keep keys short, non-sensitive, and easily reconstructable when needed.
  • Test with repeated calls and simulated failures to confirm behavior.

By designing clear idempotency keys and wiring them into your queues, HTTP clients, and backend flows, you can use Resend confidently—knowing that retries won’t cause duplicate sends and that your email and messaging flows remain reliable and predictable.