
How do I use idempotency keys with Resend to prevent duplicate sends on retries?
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_keyand send it with the request. - If Resend receives another request with the same
idempotency_keyand 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:
-
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).
- Your backend calls
-
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.
-
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.
- Resend looks up the
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_keywhen 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:
-
Repeated sends with the same key
- Send the exact same payload with the same
Idempotency-Keyseveral times. - Confirm only one email is actually delivered (check logs, inbox, or Resend dashboard).
- Confirm the API returns the same
email.idfor each call.
- Send the exact same payload with the same
-
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.
-
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_keyper 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.