---
title: "Idempotency Keys"
slug: idempotency-keys
description: "Use idempotency keys to ensure that emails are sent only once."
created_at: "2025-05-01"
updated_at: "2025-05-01"
image: https://cdn.resend.com/posts/idempotency-keys.jpg
humans: ["alexandre-cisneiros", "joao-melo"]
---

When sending emails at scale, it's important to ensure your emails are sent only once. Emails may accidentally be sent multiple times for a variety of reasons:
- **Retry logic** that attempts to send an email due to a server error or timeout
- When **different services might trigger** the same email
- **Multiple form submissions** on a website
- When handling **network failures**

Duplicate emails can be frustrating for your users, cost you money, and potentially have other side effects, like when email sending triggers other state changes in your application.

Today, we're excited to announce that the **Resend Email API now supports idempotency keys**.

## What are idempotency keys?

An idempotency key is a unique identifier for a specific email request. It ensures that the same **email request is processed only once**, even if the request is sent multiple times.

```bash
# First request sent
curl -X POST 'http://localhost:3000/emails' \
  -H 'Authorization: re_xxxxxxxxx' \
  -H 'Content-Type: application/json' \
  -H 'Idempotency-Key: foo-bar' ...

# Second request sent with the same idempotency key and parameters
curl -X POST 'http://localhost:3000/emails' \
  -H 'Authorization: re_xxxxxxxxx' \
  -H 'Content-Type: application/json' \
  -H 'Idempotency-Key: foo-bar' ...

# Both return the same email id
{"id":"49a3999c-0ce1-4ea6-ab68-afcd6dc2e794"}
```

## How does it work?

Idempotency keys can be **up to 256 characters** and should be unique per API request.

We **recommend using a UUID** or other string that uniquely identifies that specific email.

<Callout type="insight">
  If you have multiple events related to a single entity in your system, you can format your idempotency keys to take advantage of that entity's ID. One idea is to format idempotency keys like `<event-type>/<entity-id>`, for example `welcome-user/123456789`. The specific format you use is up to you.
</Callout>

We keep idempotency keys in our system for **24 hours**. This should give you an ample window to retry any failed processes on your end without having to keep track of the sent status.

## How to use idempotency keys?

As with all new features, we're supporting idempotency keys in all of our SDKs.

<CodeTabs>

```nodejs
await resend.emails.send(
  {
    from: 'Acme <onboarding@resend.dev>',
    to: ['delivered@resend.dev'],
    subject: 'hello world',
    html: '<p>it works!</p>',
  },
  {
    idempotencyKey: 'welcome-user/123456789',
  },
);
```

```php
$resend = Resend::client('re_xxxxxxxxx');

$resend->emails->send([
  'from' => 'Acme <onboarding@resend.dev>',
  'to' => ['delivered@resend.dev'],
  'subject' => 'hello world',
  'html' => '<p>it works!</p>',
], [
  'idempotency_key' => 'welcome-user/123456789',
]);
```

```python
params: resend.Emails.SendParams = {
  "from": "Acme <onboarding@resend.dev>",
  "to": ["delivered@resend.dev"],
  "subject": "hello world",
  "html": "<p>it works!</p>"
}

options: resend.Emails.SendOptions = {
  "idempotency_key": "welcome-user/123456789",
}

resend.Emails.send(params, options)
```

```ruby
params = {
  "from": "Acme <onboarding@resend.dev>",
  "to": ["delivered@resend.dev"],
  "subject": "hello world",
  "html": "<p>it works!</p>"
}
Resend::Emails.send(
  params,
  options: { idempotency_key: "welcome-user/123456789" }
)
```

```go
ctx := context.TODO()
params := &resend.SendEmailRequest{
  From:    "onboarding@resend.dev",
  To:      []string{"delivered@resend.dev"},
  Subject: "hello world",
  Text:    "it works!",
}
options := &resend.SendEmailOptions{
  IdempotencyKey: "welcome-user/123456789",
}
_, err := client.Emails.SendWithOptions(ctx, params, options)
if err != nil {
  panic(err)
}
```

```rust
use resend_rs::types::CreateEmailBaseOptions;
use resend_rs::{Resend, Result};

#[tokio::main]
async fn main() -> Result<()> {
  let resend = Resend::new("re_xxxxxxxxx");

  let from = "Acme <onboarding@resend.dev>";
  let to = ["delivered@resend.dev"];
  let subject = "Hello World";

  let email = CreateEmailBaseOptions::new(from, to, subject)
    .with_html("<p>it works!</p>")
    .with_idempotency_key("welcome-user/123456789");

  let _email = resend.emails.send(email).await?;

  Ok(())
}
```

```java
CreateEmailOptions params = CreateEmailOptions.builder()
  .from("Acme <onboarding@resend.dev>")
  .to("delivered@resend.dev")
  .subject("hello world")
  .html("<p>it works!</p>")
  .build();

RequestOptions options = RequestOptions.builder()
  .setIdempotencyKey("welcome-user/123456789").build();

CreateEmailResponse data = resend.emails().send(params, options);
```

```dotnet
using Resend;

IResend resend = ResendClient.Create( "re_xxxxxxxxx" );

var key = IdempotencyKey.New<int>( "welcome-user", 123456789 );
var resp = await resend.EmailSendAsync(key, new EmailMessage()
{
    From = "Acme <onboarding@resend.dev>",
    To = "delivered@resend.dev",
    Subject = "hello world",
    HtmlBody = "<p>it works!</p>",
} );
Console.WriteLine( "Email Id={0}", resp.Content );
```

```curl
curl -X POST 'https://api.resend.com/emails' \
     -H 'Authorization: Bearer re_xxxxxxxxx' \
     -H 'Content-Type: application/json' \
     -H 'Idempotency-Key: welcome-user/123456789' \
     -d $'{
  "from": "Acme <onboarding@resend.dev>",
  "to": ["delivered@resend.dev"],
  "subject": "hello world",
  "html": "<p>it works!</p>"
}'
```

</CodeTabs>


## Possible responses

After checking if an email with the same idempotency key has already been sent, Resend returns one of the following responses:

- **Successful responses** will return the email id of the sent email.
- **Error responses** will return one of the following errors:
  - `400`: `invalid_idempotency_key` - the idempotency key has to be between 1-256 characters. You can retry with a valid key or without supplying an idempotency key.
  - `409`: `invalid_idempotent_request` - this idempotency key has already been used on a request that had a different payload. Retrying this request is useless without changing the idempotency key or payload.
  - `409`: `concurrent_idempotent_requests` - another request with the same idempotency key is currently in progress. As it isn't finished yet, Resend can't return its original response, but it is safe to retry this request later if needed.

## Future improvements

Adding idempotency keys not only helps you avoid sending duplicate emails, but also enables us to add additional features like automatic retries and more. For more details, see our [idempotency documentation](/docs/dashboard/emails/idempotency-keys).

We are excited to see how idempotency keys empower your sending.