For AI agents: a documentation index is available at the root level at /llms.txt and /llms-full.txt. Append /llms.txt to any URL for a page-level index, or .md for the markdown version of any page.
contact@agentmail.ccDiscord
DocumentationAPI ReferenceKnowledge BaseChangelog
DocumentationAPI ReferenceKnowledge BaseChangelog
    • Introduction
  • Getting Started
    • What is AgentMail?
    • What can I do with an inbox?
    • Creating your first inbox
    • Getting your API key
  • Agent Patterns
    • Handling inbound emails
    • Allowlists & blocklists
    • Managing threaded conversations
    • Human-in-the-loop workflows
    • Pods for multi-tenant email
    • Using labels to track state
  • Domains & Deliverability
    • Custom domain setup
    • SPF, DKIM, and DMARC setup
    • Emails going to spam
    • Warming Up
    • MX record conflicts
  • Troubleshooting
    • API 403 error
    • Rate limits
    • Preventing duplicate sends
    • Domain not verifying
    • Emails bouncing
    • Why are my emails not showing up?
  • DNS Guides
    • Cloudflare
    • GoDaddy
    • Route 53 (AWS)
    • Namecheap
LogoLogo
contact@agentmail.ccDiscord
On this page
  • Idempotent resource creation with client_id
  • Preventing duplicate email sends
  • Track sent messages with labels
  • Use drafts for critical sends
  • Best practices
Troubleshooting

How do I prevent duplicate sends?

Use idempotency to avoid sending the same email twice.
Was this page helpful?
Edit this page
Previous

Why is my domain not verifying?

What to do when your domain verification is stuck.
Next
Built with

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.

Idempotent resource creation with client_id

AgentMail supports idempotency for all create operations via the clientId parameter. When you provide a clientId, AgentMail checks if a resource with that ID already exists. If it does, it returns the existing resource instead of creating a duplicate.

This works for creating inboxes, pods, webhooks, and drafts:

TypeScript
1import { AgentMailClient } from "agentmail";
2
3const client = new AgentMailClient({ apiKey: "am_..." });
4
5// Safe to call multiple times: only creates the inbox once
6const inbox = await client.inboxes.create({
7 username: "support",
8 clientId: "support-inbox-v1",
9});
10
11// Calling again with the same clientId returns the existing inbox
12const sameInbox = await client.inboxes.create({
13 username: "support",
14 clientId: "support-inbox-v1",
15});
16
17// inbox.inboxId === sameInbox.inboxId

Preventing duplicate email sends

The clientId parameter is for resource creation, not for messages.send. To prevent duplicate email sends, you need to handle this in your application logic. Here are common patterns:

Track sent messages with labels

Use labels to mark messages that your agent has already processed, so it does not reply twice:

TypeScript
1// Before replying, check if already handled
2const threads = await client.inboxes.threads.list(inbox.inboxId, {
3 labels: ["unreplied"],
4});
5
6for (const thread of threads.threads) {
7 const detail = await client.threads.get(thread.threadId);
8 const lastMessage = detail.messages[detail.messages.length - 1];
9
10 // Reply and update labels atomically in your logic
11 await client.inboxes.messages.reply(inbox.inboxId, lastMessage.messageId, {
12 text: "Thanks for reaching out!",
13 });
14
15 await client.inboxes.messages.update(inbox.inboxId, lastMessage.messageId, {
16 addLabels: ["replied"],
17 removeLabels: ["unreplied"],
18 });
19}

Use drafts for critical sends

For high-stakes emails, use drafts instead of sending directly. Create a draft, verify it has not been sent already, then send:

TypeScript
1// Create a draft with a deterministic clientId
2const draft = await client.inboxes.drafts.create(inbox.inboxId, {
3 to: ["customer@example.com"],
4 subject: "Order confirmation",
5 text: "Your order has been confirmed.",
6 html: "<p>Your order has been confirmed.</p>",
7 clientId: "order-123-confirmation",
8});
9
10// Later, send the draft (only works once, draft is deleted after sending)
11const sent = await client.inboxes.drafts.send(inbox.inboxId, draft.draftId);

Since drafts support clientId, creating the same draft multiple times is safe. And once a draft is sent, it is deleted, so calling drafts.send again will fail rather than send a duplicate.

Best practices

  • Use clientId on all create operations (inboxes, pods, webhooks, drafts) to make them safe to retry
  • Generate clientId from your business logic (e.g., order-${orderId}-confirmation), not random UUIDs
  • Track state with labels to prevent your agent from processing the same message twice
  • Use drafts for critical sends where duplicates would be harmful

For more details, see the Idempotent Requests guide.