Skip to main content

Overview

Idempotency ensures that retrying a request (due to network failures, timeouts, or application errors) won’t result in duplicate emails. This is critical for transactional emails where sending the same message twice can confuse recipients or cause unintended actions.
Common scenarios where idempotency helps:
  • Network timeout occurs but the email was actually sent
  • Your application crashes after sending but before recording success
  • Load balancer retries a request that succeeded on the first attempt

How it works

When you include an Idempotency-Key header, Lettermint:
  1. Hashes the request body to create a fingerprint of your email content
  2. Stores the key + hash for 24 hours, scoped to your project
  3. On subsequent requests with the same key:
    • If the body hash matches → returns the cached response (no duplicate sent)
    • If the body hash differs → returns a 409 Conflict error
Idempotency keys are scoped per project. The same key can be used across different projects without conflict. Within a single project, each key must be unique per email.

Usage

Idempotency-Key: <your-unique-key>

Key requirements

RequirementValue
Length1–255 characters
FormatAny string (UUID v4 recommended)
Validity24 hours from first use
ScopePer project

Request scenarios

ScenarioResult
First request with new keyEmail sent, response cached
Same key + same body (retry)Cached response returned, no duplicate
Same key + different body409 Conflict error
Same key + request in progress409 Conflict error
Key used 24+ hours agoTreated as new key

Examples

Send with idempotency key

import { Lettermint } from "lettermint";

const lettermint = new Lettermint({
  apiToken: process.env.LETTERMINT_API_TOKEN
});

const response = await lettermint.email
  .from("John Doe <john@yourdomain.com>")
  .to("recipient@example.com")
  .subject("Order Confirmation #12345")
  .html("<p>Your order has been confirmed.</p>")
  .idempotencyKey("order-12345-confirmation")
  .send();

console.log(`Email sent: ${response.message_id}`);

Response (202 Accepted)

{
  "message_id": "f47ac10b-58cc-4372-a567-0e02b2c3d479"
}

Safe retry (same key + same body)

If you retry the exact same request with the same idempotency key and body, you receive a 202 Accepted with the same message_id. No duplicate email is sent.
{
  "message_id": "f47ac10b-58cc-4372-a567-0e02b2c3d479"
}

Conflict (same key + different body)

If you use an existing key with a different request body: HTTP 409 Conflict
{
  "message": "This idempotency key has already been used on a request with a different payload. Retrying this request requires changing the idempotency key or payload."
}

Concurrent requests

If another request with the same key is still being processed: HTTP 409 Conflict
{
  "message": "Another request with the same idempotency key is currently being processed. Please retry this request later."
}
When you receive a concurrent request error, wait briefly and retry. The original request will complete shortly.

SMTP

The Idempotency-Key header works the same way when sending via SMTP relay.
import nodemailer from "nodemailer";

const transporter = nodemailer.createTransport({
  host: "smtp.lettermint.co",
  port: 587,
  secure: false,
  auth: {
    user: "lettermint",
    pass: process.env.LETTERMINT_API_TOKEN,
  },
});

await transporter.sendMail({
  from: "sender@yourdomain.com",
  to: "recipient@example.com",
  subject: "Order Confirmation #12345",
  html: "<p>Your order has been confirmed.</p>",
  headers: {
    "Idempotency-Key": "order-12345-confirmation"
  }
});

Error reference

Error CodeHTTP StatusMessage
invalid_idempotency_key422The idempotency key must be between 1-255 characters.
invalid_idempotent_request409This idempotency key has already been used on a request with a different payload.
concurrent_idempotent_requests409Another request with the same idempotency key is currently being processed.

Best practices

Generate unique keys per email. Use a combination of entity ID + action type (e.g., order-12345-confirmation, user-789-welcome). UUID v4 works well for truly unique operations.
Store keys for retry logic. If your application might retry a request, store the idempotency key so you can use the same key on retry. Generating a new key defeats the purpose.

When to use idempotency

Use CaseRecommendation
Order confirmations, receiptsAlways use idempotency
Password resets, verification emailsAlways use idempotency
Webhook-triggered emailsUse if webhook might retry
Marketing campaigns (batch)Optional, typically single-shot
Test emails during developmentSkip, duplicates are harmless

Troubleshooting

This happens when you reuse an idempotency key with a different email body. Common causes:
  • Using a static key instead of generating per-email keys
  • Changing the email content (subject, body, recipients) between retries
Solution: Generate a unique key for each distinct email. For retries, use the exact same request body.
  • 422 (Unprocessable Entity): Your idempotency key is invalid (empty, too long, or wrong format)
  • 409 (Conflict): The key is valid but conflicts with an existing request (different body or concurrent request)
Solution: For 422, fix the key format. For 409, either wait and retry (concurrent) or use a different key (payload mismatch).
Keys expire 24 hours after first use. After expiration, the same key can be reused as if it were new. This prevents permanent key exhaustion while providing a reasonable retry window.
Yes. Idempotency keys are scoped per project, so order-123 in Project A is independent from order-123 in Project B.