Error reference

idempotency_conflict

HTTP 409 retryable: false REST and MCP

This Idempotency-Key has already been used for a different request body, so replaying it cannot return the original result and cannot safely create a new one either.

Why it happens

The idempotency record stores a SHA-256 of the canonical payload alongside the key digest. Claiming a key is an INSERT with ON CONFLICT DO NOTHING, so exactly one request wins the key; a request that loses the race then locks the existing row and compares payload hashes. Matching hashes replay the stored response snapshot. A mismatch is this error. The comparison is over the canonical payload, so a reordered recipient list or a whitespace difference that survives canonicalisation is a different payload as far as the check is concerned.

Every condition below was read in the implementation at the file and line given. They are the branches that raise this code, not a summary of what the name suggests.

How to fix it

  1. Generate a fresh key for a genuinely new message. A key identifies one logical send, not one retry loop and not one session.
  2. Derive the key from the content, not from a counter. A UUID stored alongside the message you are about to send survives a process restart; an incrementing integer does not.
  3. Send the byte-identical body when retrying. Regenerating a timestamp inside the payload changes the hash and turns a safe retry into this conflict.
  4. Keep the key at or under 128 characters. A longer one is rejected as invalid_request before it reaches the idempotency record at all.

Why it works this way

Refusing is the safe branch. The alternative, treating a same-key different-payload request as a new send, is exactly how a retry turns into a duplicate email, and the whole reason the header exists is to make that impossible.

Messages you will see

The message field is written for a human reading a log and is not a contract value. Branch on code instead. These are the exact strings this code is sent with today, listed so that a search for one of them lands here.

Where it appears

These are the documented surfaces that raise it. The REST base is https://api.emailforagents.ai/v1, and the remote MCP endpoint is https://mcp.emailforagents.ai/mcp.

The response

Read request_id off the body or the x-request-id header before doing anything else. It is the only handle on one specific attempt, and for a 503 it is the only handle at all, because the underlying detail stays server-side.

HTTP/1.1 409
content-type: application/json
x-request-id: req_...

{
  "error": {
    "code": "idempotency_conflict",
    "message": "Idempotency-Key was reused with different content.",
    "request_id": "req_...",
    "retryable": false
  }
}

Verified against packages/domain/src/services.ts:1616. The status comes from ERROR_STATUS in packages/contracts/src/errors.ts, which is the one place a code is mapped to a status.

All error codes · Documentation · Quickstart