Skip to main content
Before continuing, make sure you’ve completed the SDK setup and requirements.

Overview

Network issues, timeouts, and client crashes can cause API requests to fail without a clear response. When this happens, you may not know if the email was actually sent. Retrying the request risks sending the same email twice. Idempotency keys solve this problem. By including an Idempotency-Key header in your request, you tell the API to process the request only once. If you retry with the same key, the API returns the original response instead of sending the email again.

How it works

  1. You generate a unique key and include it in the Idempotency-Key header. This can be a technical identifier like a UUID, or a business identifier such as an external customer ID or an order reference (e.g., order-12345).
  2. The API processes the request and stores the response associated with that key.
  3. If you send the same request again with the same key, the API returns the stored response without reprocessing.
  4. Keys expire after 24 hours. After that, the same key can be reused.

Send an email with an idempotency key

With the SDK

import { randomUUID } from 'node:crypto';

const idempotencyKey = randomUUID();

const { data, error } = await nuntly.emails.send({
  from: 'ray@info.tomlinson.ai',
  subject: 'Order Confirmation',
  to: 'brian67@gmail.com',
  html: '<h1>Your order has been confirmed</h1>',
}, {
  headers: {
    'Idempotency-Key': idempotencyKey,
  },
});

if (error) {
  return console.error('Error sending email:', error);
}
console.log(`Email sent ${data.id}`);

With the REST API

curl -X POST https://api.nuntly.com/emails \
  -H 'Authorization: Bearer ntly_your_api_key_here' \
  -H 'Content-Type: application/json' \
  -H 'Idempotency-Key: 550e8400-e29b-41d4-a716-446655440000' \
  -d '{
    "from": "ray@info.tomlinson.ai",
    "subject": "Order Confirmation",
    "to": "brian67@gmail.com",
    "html": "<h1>Your order has been confirmed</h1>"
  }'

Bulk emails

For bulk sending, the idempotency key applies to the entire bulk request, not to individual emails within it. If you retry a bulk request with the same key, the full bulk response (including per-email statuses) is returned from cache.
import { randomUUID } from 'node:crypto';

const idempotencyKey = randomUUID();

const { data, error } = await nuntly.emails.bulk.send({
  fallback: {
    from: 'ray@info.tomlinson.ai',
    subject: 'Weekly Newsletter',
  },
  emails: [
    { to: 'carlo43@gmail.com', text: 'Hello Carlo!' },
    { to: 'pink42@yahoo.com', text: 'Hello Pinky!' },
  ],
}, {
  headers: {
    'Idempotency-Key': idempotencyKey,
  },
});

Detecting replayed responses

When the API returns a cached response, it includes the Idempotent-Replayed: true header. The response body and status code are identical to the original response. You can use this header to distinguish a replayed response from a fresh one in your logs or monitoring.

Key format

PropertyValue
Minimum length8 characters
Maximum length255 characters
Recommended formatUUID v4
Case sensitiveYes
You can use any string format you prefer, such as UUIDs, ULIDs, or composite keys like order-12345.

Error handling

The API returns specific error codes for idempotency-related issues.

Invalid key

Returned when the key is empty, too short, or too long.
{
  "error": {
    "status": 400,
    "code": "idempotency_key_invalid",
    "title": "Invalid idempotency key"
  }
}

Key already in progress

Returned when a request with the same key is still being processed. Wait a moment and retry.
{
  "error": {
    "status": 409,
    "code": "idempotency_key_in_progress",
    "title": "Idempotency key is already being processed"
  }
}

Payload mismatch

Returned when you reuse a key with a different request body. Each key is bound to the exact request body that was sent with it. Use a new key for different requests.
{
  "error": {
    "status": 409,
    "code": "idempotency_key_payload_mismatch",
    "title": "Idempotency key reused with different payload"
  }
}

Behavior on errors

How the API handles idempotency depends on the type of error from the original request:
  • Client errors (4xx): The error response is cached. Retrying with the same key returns the same error. You need to fix the issue and use a new key.
  • Server errors (5xx): The error response is not cached. You can safely retry with the same key, and the API will process the request again.
An idempotency key represents a single intent. If a request fails with a 4xx error (such as an unverified domain or an exceeded quota), fix the underlying issue and then use a new key for your next attempt.

Best practices

  • Generate a new unique key for each distinct email you want to send
  • Store the key alongside the operation in your system so you can retry with the same key if needed
  • Use UUIDs or similar random identifiers to avoid accidental collisions
  • The key is optional. If you do not need retry safety, you can omit the header entirely