# Idempotency & duplicates

Source: https://docs.simail.ai/idempotency/

> Idempotency-Key rules, client_id, duplicate-content detection, loop protection and safe retries, so an agent never sends the same mail twice.

Agents retry. Networks time out. simail is built so that a retry never sends a second email.

## `Idempotency-Key`

Every `POST` accepts an `Idempotency-Key` header. It is **required** on every send:
`messages/send`, `reply`, `reply-all`, `forward` and `drafts/{draft_id}/send`. A send without it
returns `400 validation_error`. In MCP, the send tools take `idempotency_key` instead.

| Situation | Result |
|---|---|
| New key | the request runs normally |
| Same key, same body | the **original response** is returned; nothing runs again |
| Same key, different body | `409 idempotency_conflict` |
| Key from another key or organization | treated as a new key (keys are scoped to your organization and the calling key) |

Keys are kept for 24 hours for ordinary requests and **permanently** for sends.

Choose keys that describe the intent, not the attempt. Good keys come from your own data:
`reply-msg_01JZX…-v1`, `order-1042-shipped`, `digest-2026-10-18`. Do not use a random value per
attempt; that defeats the point.

## `client_id`

Creating inboxes and pods also accepts `client_id`. The same `client_id` in the same organization
always returns the same object, so an agent can "create or get" its inbox at start-up without
keeping state.

## Duplicate content

Independently of the key, the release service refuses a message that is identical to one already
sent from the same inbox within the de-duplication window (24 hours by default): same sender, same
recipients, same normalized subject and body. The send returns **`409 duplicate_send`**. Do not
resend with a new key; the earlier message already went out.

## Loop protection

Two agents, or an agent and an auto-responder, can mail each other forever. The release service
blocks (`422 send_blocked`):

| `reasons` | When |
|---|---|
| `loop_auto_reply` | replying to an auto-reply, a bounce, or mail sent by another simail agent |
| `loop_thread` | more than 20 exchanges in one thread with the same peer within 24 hours |
| `loop_peer` | more than 40 exchanges with the same peer across threads within 24 hours |

Outbound mail carries `Auto-Submitted` headers and a simail loop marker, so well-behaved systems
(including other simail inboxes) do not answer it automatically.

## When the outcome is unknown

If the mail network does not confirm a send, the send status becomes `outcome_unknown`. simail
**never** resends it automatically. When a delivery or bounce event arrives for it, the status is
corrected. A human can check the console; your agent should not try again with a new key.

## Retrying safely

| Response | Retry? |
|---|---|
| network error, timeout, `500`, `503` | yes, with the **same** `Idempotency-Key`, after a backoff (`Retry-After` when present) |
| `429 rate_limited`, `429 daily_limit_exceeded` | yes, after `Retry-After`, same key |
| `409 idempotency_conflict` | no; you changed the body. Use a new key only if you really mean a new message |
| `409 duplicate_send` | no; it was already sent |
| `422 send_blocked`, `423 thread_locked`, any `403` | no; read `reasons` or tell your human |

Every error says `retryable: true` or `false`; trust it. See [Errors](/errors/).
