Questions · Updated 2026-09-28

How do I stop my code from sending the same email twice?

Stop your code from sending the same email twice by sending an Idempotency-Key header on POST /emails. The same key with the same body replays the original response instead of running the send again, so a retry after a timeout cannot mail anyone twice.

Stop your code from sending the same email twice by putting an Idempotency-Key header on POST /emails. The parameter description says "Send the same key with the same body and the original response is replayed instead of the work happening twice. Keys are 1-256 characters and are remembered for 7 days." A retry after a timeout therefore returns the first send's id rather than creating a second message. Resend-Idempotency-Key is accepted as the same header, and Idempotency-Key wins if both are sent.

Choosing the key

Derive the key from the thing being done, never from the moment: welcome/<user id>, invoice/<invoice id>/sent, reset/<token id>. A fresh key on every attempt is no protection, because a retry then looks like a new request.

Keys are remembered per account and per route, so the same string on POST /emails and on POST /webhooks are two different keys. An empty or overlong key is refused with invalid_idempotency_key; the catalogue message is "Idempotency-Key must be 1-256 characters." and the fix is "Send a non-empty Idempotency-Key header of at most 256 characters."

curl -sS -X POST https://api.agentisend.com/emails \
  -H "Authorization: Bearer $AGENTISEND_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{"from":"billing@yourdomain.com","to":"customer@example.com"}'
201
{
  "id": "9c8f8f0e-3d1a-4d3f-9a1e-2b7c1a0f5e42",
  "simulated": true,
  "warnings": []
}
Response

The two refusals

idempotency_in_flight means the first attempt has not finished. The catalogue message is "A request with this Idempotency-Key is still in progress." and the fix is "Wait and retry with the same Idempotency-Key to receive the original response." It is retryable, so wait, then send the identical request again.

idempotency_payload_mismatch means the key was reused with a different body. The message is "Same Idempotency-Key was used with a different payload." and the fix is "Reuse the exact same body for retries, or send a new Idempotency-Key for a new request." It is not retryable, so stop and decide which of the two requests you meant.

A first attempt that fails with an error releases its key, so a retry runs again instead of replaying the failure. A replayed response never re-shows a one-time secret: a retried POST /api-keys returns "Shown once. Create a new key if you no longer have it." in place of the token.

Where else the header applies

Every POST, PATCH and DELETE accepts it. On POST /emails/batch one key covers the whole array, so a retried batch replays every row rather than re-sending the accepted ones. The catalogue leans on it too: the fix for internal_server_error is "Retry ONCE after a short pause, with the same Idempotency-Key so the retry cannot double-send.", and the fix for sending_paused_everywhere is "Retry the same request, with the same Idempotency-Key, after the seconds given in Retry-After."

The key protects one call against its own retries. A sender that keeps composing near-identical messages to the same person is a different problem, caught by the loop guard rather than by this header.

Next