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

We recently enabled idempotency keys for the `/emails` endpoint to help you avoid sending duplicate emails.

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.

## Idempotency for batch emails

While useful for single transactional emails, idempotency keys are also useful for sending emails in bulk.

Our bulk endpoint enables up to 100 emails to be sent in a single API call. Today, we're excited to announce that the `/emails/batch` endpoint now supports idempotency keys.

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

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

# Both return the same email ids
{
  data: [
    {"id":"49a3999c-0ce1-4ea6-ab68-afcd6dc2e794"},
    {"id":"c11a2b12-c014-41df-9386-2b9b8240cbd5"}
  ]
}
```

## How does it work?

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

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.

<Callout type="insight">
  If you have multiple events related to an entity in your system, you can format your idempotency keys to take advantage of that entity's ID (i.e., `<event-type>/<entity-id>`). For batch sends, choose a key that represents the whole batch, like a team, workspace, or project (i.e., `team-quota/123456789`).
</Callout>


## How to use idempotency keys for batch sends

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

<CodeTabs codeHeight={450}>

```nodejs
import { Resend } from 'resend';

const resend = new Resend('re_xxxxxxxxx');

await resend.batch.send(
  [
    {
      from: 'Acme <onboarding@resend.dev>',
      to: ['foo@gmail.com'],
      subject: 'hello world',
      html: '<h1>it works!</h1>',
    },
    {
      from: 'Acme <onboarding@resend.dev>',
      to: ['bar@outlook.com'],
      subject: 'world hello',
      html: '<p>it works!</p>',
    },
  ],
  {
    idempotencyKey: 'team-quota/123456789',
  },
);
```

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

$resend->batch->send(
  [
    [
      'from' => 'Acme <onboarding@resend.dev>',
      'to' => ['foo@gmail.com'],
      'subject' => 'hello world',
      'html' => '<h1>it works!</h1>',
    ],
    [
      'from' => 'Acme <onboarding@resend.dev>',
      'to' => ['bar@outlook.com'],
      'subject' => 'world hello',
      'html' => '<p>it works!</p>',
    ]
  ],
  [
    'idempotency_key' => 'team-quota/123456789',
  ]
);
```

```python
import resend
from typing import List

resend.api_key = "re_xxxxxxxxx"

params: List[resend.Emails.SendParams] = [
  {
    "from": "Acme <onboarding@resend.dev>",
    "to": ["foo@gmail.com"],
    "subject": "hello world",
    "html": "<h1>it works!</h1>",
  },
  {
    "from": "Acme <onboarding@resend.dev>",
    "to": ["bar@outlook.com"],
    "subject": "world hello",
    "html": "<p>it works!</p>",
  }
]

options: resend.Batch.SendOptions = {
  "idempotency_key": "team-quota/123456789",
}

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

```ruby
require "resend"

Resend.api_key = 're_xxxxxxxxx'

params = [
  {
    "from": "Acme <onboarding@resend.dev>",
    "to": ["foo@gmail.com"],
    "subject": "hello world",
    "html": "<h1>it works!</h1>",
  },
  {
    "from": "Acme <onboarding@resend.dev>",
    "to": ["bar@outlook.com"],
    "subject": "world hello",
    "html": "<p>it works!</p>",
  }
]

Resend::Batch.send(
  params,
  options: { idempotency_key: "team-quota/123456789" }
)
```

```go
package examples

import (
	"fmt"
	"os"

	"github.com/resend/resend-go/v2"
)

func main() {

  ctx := context.TODO()

  client := resend.NewClient("re_xxxxxxxxx")

  var batchEmails = []*resend.SendEmailRequest{
    {
      From:    "Acme <onboarding@resend.dev>",
      To:      []string{"foo@gmail.com"},
      Subject: "hello world",
      Html:    "<h1>it works!</h1>",
    },
    {
      From:    "Acme <onboarding@resend.dev>",
      To:      []string{"bar@outlook.com"},
      Subject: "world hello",
      Html:    "<p>it works!</p>",
    },
  }

  options := &resend.BatchSendEmailOptions{
    IdempotencyKey: "team-quota/123456789",
  }

  sent, err := client.Batch.SendWithOptions(ctx, batchEmails, options)

  if err != nil {
    panic(err)
  }
  fmt.Println(sent.Data)
}
```

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

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

  let emails = vec![
    CreateEmailBaseOptions::new(
      "Acme <onboarding@resend.dev>",
      vec!["foo@gmail.com"],
      "hello world",
    )
    .with_html("<h1>it works!</h1>"),
    CreateEmailBaseOptions::new(
      "Acme <onboarding@resend.dev>",
      vec!["bar@outlook.com"],
      "world hello",
    )
    .with_html("<p>it works!</p>"),
  ];

  let _emails = resend.batch.send_with_idempotency_key(emails, "team-quota/123456789").await?;

  Ok(())
}
```

```java
import com.resend.*;

public class Main {
    public static void main(String[] args) {
        Resend resend = new Resend("re_xxxxxxxxx");

        CreateEmailOptions firstEmail = CreateEmailOptions.builder()
            .from("Acme <onboarding@resend.dev>")
            .to("foo@gmail.com")
            .subject("hello world")
            .html("<h1>it works!</h1>")
            .build();

        CreateEmailOptions secondEmail = CreateEmailOptions.builder()
            .from("Acme <onboarding@resend.dev>")
            .to("bar@outlook.com")
            .subject("world hello")
            .html("<p>it works!</p>")
            .build();

        CreateBatchEmailsResponse data = resend.batch().send(
            Arrays.asList(firstEmail, secondEmail),
            Map.of("idempotency_key", "team-quota/123456789")
        );
    }
}
```

```dotnet
using Resend;

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

var key = IdempotencyKey.New<int>( "team-quota", 123456789 );

var mail1 = new EmailMessage()
{
    From = "Acme <onboarding@resend.dev>",
    To = "foo@gmail.com",
    Subject = "hello world",
    HtmlBody = "<p>it works!</p>",
};

var mail2 = new EmailMessage()
{
    From = "Acme <onboarding@resend.dev>",
    To = "bar@outlook.com",
    Subject = "hello world",
    HtmlBody = "<p>it works!</p>",
};

var resp = await resend.EmailBatchAsync(key, [ mail1, mail2 ] );
Console.WriteLine( "Nr Emails={0}", resp.Content.Count );
```

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

</CodeTabs>

## Future improvements

We look forward to adding additional features like automatic retries and more. For more details, see our [idempotency documentation](/docs/dashboard/emails/idempotency-keys).