Idempotency
The problem
Networks are unreliable. Your client can lose its connection after the API stored your email but before the response arrived — and your natural reaction is to retry. Without a guard, that retry creates a duplicate send (and double quota usage). Delivery is at-least-once, so your sending code must be safe to run twice.
How it works
Add an idempotency_key field to the request body. The key is unique per team. The first request with a given key stores its row and returns 201 Created; any later request with the same key returns the original stored row unchanged — same id, original field values, no merge — with 200 OK instead.
First send (201 Created):
{
"id": "01a0a11c-188e-7c9e-9b69-753fcb4c8d0d",
"team_id": "00000000-0000-0000-0000-000000000001",
"domain_id": "01a0a118-2c7b-7da6-aa31-ceb20daf80c6",
"template_id": null,
"from_address": "Notideus <noreply@demo.dev>",
"to_emails": ["jane@example.com"],
"subject": "Welcome to Notideus",
"html": "<p>Hi Jane,</p><p>Your account is ready.</p>",
"size_kb": 0.04,
"tags": ["welcome"],
"status": "queued",
"idempotency_key": "docs-quickstart-1",
"created_at": "2026-09-14T18:09:28.206638Z",
"requeued_at": null,
"ses_message_id": null,
"broadcast_id": null,
"contact_id": null,
"unsubscribe_token": null
}
Replay of the same idempotency_key (200 OK):
{
"id": "01a0a11c-188e-7c9e-9b69-753fcb4c8d0d",
"team_id": "00000000-0000-0000-0000-000000000001",
"domain_id": "01a0a118-2c7b-7da6-aa31-ceb20daf80c6",
"template_id": null,
"from_address": "Notideus <noreply@demo.dev>",
"to_emails": ["jane@example.com"],
"subject": "Welcome to Notideus",
"html": "<p>Hi Jane,</p><p>Your account is ready.</p>",
"size_kb": 0.04,
"tags": ["welcome"],
"status": "queued",
"idempotency_key": "docs-quickstart-1",
"created_at": "2026-09-14T18:09:28.206638Z",
"requeued_at": null,
"ses_message_id": null,
"broadcast_id": null,
"contact_id": null,
"unsubscribe_token": null
}
Decode:
- Same
id— the replay echoes the stored row; it is not a new email, and the second request body is not merged into the first. - Same original values —
status,created_at, addresses: the first stored row wins, byte for byte.
With the SDK
Pass idempotency_key exactly as above — key choice and replay semantics
are unchanged. The SDK adds one layer on top: when a send carries a key, it
automatically retries network failures, 5xx and 429 (honoring
Retry-After), because the key makes the retry safe. Keyless sends are
never auto-retried.
A replay resolves normally with the original stored row — the SDK does not
distinguish 200 from 201, and email.id is stable across attempts.
In PHP the behavior is identical: keyed sends are automatically retried on
network failures, 5xx and 429 (honoring Retry-After), keyless sends are
never auto-retried, and a replay resolves normally with $email['id'] stable
across attempts.
Scope
POST /v1/emails— one key per email.POST /v1/emails/batch— each item carries its own key; replays are resolved per item.POST /v1/whatsapp/messages— keyed per key and recipient: a logical message to three phones replays per phone, never for just some of them.
Key choice
order-12345-welcome, invoice-6789-receipt — never a random value per attempt. The common pattern: generate one UUID per logical operation, store it in your database next to the order or user, and reuse it on every retry of that operation.