How do I prevent duplicate sends?
AI agents can sometimes retry requests due to network errors, timeouts, or logic bugs. Without safeguards, this can cause the same email to be sent multiple times. Here is how to prevent that.
AgentMail has two separate mechanisms:
Idempotent resource creation with client_id
Creating an inbox, pod, webhook, or draft accepts an optional client_id. When you provide one, AgentMail checks whether a resource with that client_id already exists. If it does, it returns the existing resource instead of creating a duplicate.
Not every create operation takes a client_id. List entries and standard (bearer) API keys, for example, do not, and sends use the Idempotency-Key header described below.
Preventing duplicate email sends
client_id is for resource creation, not for sends. Sends (messages.send, replies, reply-alls, forwards, and drafts.send) are made idempotent with an Idempotency-Key HTTP header instead. The SDKs expose it as idempotency_key in Python and as the idempotencyKey request option in TypeScript.
How the key behaves:
- First request with a key: AgentMail sends the email and records the result against the key.
- Retry with the same key: returns the original
message_idandthread_idand sends no second email. A retry that arrives while the first request is still being processed returns409 Conflict; wait briefly and retry the identical request. - Same key, different request (different message content, a different sending inbox, or a different send endpoint): returns
409 Conflict. - Format: 1 to 256 characters from
A-Z a-z 0-9 - . _ ~. An emptyIdempotency-Keyheader is rejected with a400. - Lifetime: keys are scoped to your organization and expire 24 hours after the send completes, after which the key can be reused.
Use a key that is unique per logical send and reuse it, with the same request body, across every retry of that send, for example one derived from your own data (order-4821-confirmation) or a UUID you generate once and store. If your agent regenerates the email text on each attempt, store the first version and resend that, since a retry with different text is a different request.
See the Idempotent Requests guide for the full semantics. The application-side patterns below still help when you want to dedupe on your own business state (for example, “have I already replied to this message?”).
Track handled messages with labels
AgentMail adds an unread label to every message it receives. Your agent can work through unread messages and swap the label for one of its own (such as replied) once it has handled each one.
Label-based dedupe is not atomic, so do not rely on it alone to stop a second email:
- Two workers can both read a message as unhandled before either one updates its labels.
- A crash between the reply and the label update leaves a replied message still labeled
unread. - A list filtered by label can briefly return a message right after you relabel it.
Use labels to track workflow state, and put an Idempotency-Key on the reply itself so a repeat within the key’s 24-hour lifetime cannot become a second email. Message IDs contain characters such as < and @ that a key cannot, so derive the key from a hash of the inbox and message ID. Include the inbox because keys are shared across your organization, and an email sent to two of your inboxes arrives in both with the same message ID.
Use drafts for critical sends
For high-stakes emails, use drafts instead of sending directly. Create the draft with a deterministic client_id, then send it with an Idempotency-Key:
Since drafts support client_id, creating the same draft multiple times is safe. Once a draft is sent it is deleted, so a second drafts.send without a key fails with a 404 rather than sending a duplicate. With an Idempotency-Key, a retry after a timeout returns the original message instead, so your agent can tell that the email already went out.
Best practices
- Use an
Idempotency-Keyheader on sends (messages.send, replies, reply-alls, forwards,drafts.send) so retries never duplicate an email - Use
client_idwhen creating inboxes, pods, webhooks, and drafts to make those creates safe to retry - Derive keys and
client_idvalues from your business logic (for example,order-4821-confirmation) so every retry of the same operation reuses the same value - Track state with labels to organize your agent’s work, but do not rely on labels alone to prevent a second email
- Use drafts for critical sends where duplicates would be harmful
For more details, see the Idempotent Requests guide.
