Idempotency
Use an idempotency key when your application may retry a send request. Lettermint returns the first result instead of sending the same email twice.
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:
- Hashes the request body to create a fingerprint of your email content
- Stores the key + hash for 24 hours, scoped to your project
- 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 Conflicterror
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
Header
Code
The header also works when you resend an email.
Key requirements
| Requirement | Value |
|---|---|
| Length | 1–255 characters |
| Format | Any string (UUID v4 recommended) |
| Validity | 24 hours from first use |
| Scope | Per project |
Request scenarios
| Scenario | Result |
|---|---|
| First request with new key | Email sent, response cached |
| Same key + same body (retry) | Cached response returned, no duplicate |
| Same key + different body | 409 Conflict error |
| Same key + request in progress | 409 Conflict error |
| Same key after an error before any email was queued, such as a validation error | Processed as a new request |
Same key after a 5xx error whose outcome is unknown | 409 Conflict until the key expires |
| Key used 24+ hours ago | Treated as new key |
Examples
Send with idempotency key
Response (202 accepted)
Code
Safe retry (same key + same body)
If you retry the exact same request with the same idempotency key and body within 24 hours, you receive the cached 202 Accepted response with the same message_id. No duplicate email is sent.
Code
Conflict (same key + different body)
If you use an existing key with a different request body:
HTTP 409 Conflict
Code
Concurrent requests
If another request with the same key is still being processed:
HTTP 409 Conflict
Code
When you receive a concurrent request error, wait briefly and retry. The original request will complete shortly.
Batch sending
A batch sent to POST /v1/send/batch can fail halfway: emails are accepted in order, so when the third email fails, the first two may already be sent. To retry part of a batch safely, give each email an idempotency_key of its own.
cURL
- An email whose key was accepted in the last 24 hours with the same content returns its original result and is not sent again, in any later batch and at any position.
- Keys are scoped per project, must be unique within a batch, and are 1 to 255 characters.
- When an email fails, the error lists the emails accepted before it in
details.accepted, with theirindexandmessage_id. Fix the failed email and send the batch again with the same keys. - When a key was used before with different content, or its email is still being processed or may already have been sent, the whole batch is rejected with
409 IDEMPOTENCY_CONFLICTand nothing is sent.details.conflictslists those emails. For an email withoutcome_unknown: true, check whether it was sent before you send it again with a new key.
SMTP
The Idempotency-Key header works the same way when sending via SMTP relay.
Error reference
| Error Code | HTTP Status | Message |
|---|---|---|
invalid_idempotency_key | 422 | The idempotency key must be between 1-255 characters. |
invalid_idempotent_request | 409 | This idempotency key has already been used on a request with a different payload. |
concurrent_idempotent_requests | 409 | Another request with the same idempotency key is currently being processed, or an earlier one failed after its email may have been sent. |
IDEMPOTENCY_CONFLICT | 409 | Batch sends only. The key of one or more emails conflicts, see details.conflicts. No emails were sent. |
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 Case | Recommendation |
|---|---|
| Order confirmations, receipts | Always use idempotency |
| Password resets, verification emails | Always use idempotency |
| Webhook-triggered emails | Use if webhook might retry |
| Marketing campaigns (batch) | Give each email an idempotency_key, so you can retry a failed batch |
| Test emails during development | Skip, duplicates are harmless |
Troubleshooting
Why am I getting 'key already used' errors?
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.
What's the difference between 409 and 422?
- 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).
Why do I still get a 409 after the first request failed?
The first request failed after its email may already have been sent, so Lettermint blocks the key for 24 hours to prevent a duplicate. Check whether the email was sent, for example in the dashboard. If it was not, send it again with a new key.
How long do idempotency keys last?
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.
Can I use the same key across different projects?
Yes. Idempotency keys are scoped per project, so order-123 in Project A is independent from order-123 in Project B.