cd /news/ai-agents/how-to-avoid-duplicate-email-sends-w… · home › topics › ai-agents › article
[ARTICLE · art-154049] src=dev.to ↗ pub= topic=ai-agents verified=true sentiment=· neutral

How to avoid duplicate email sends with Hail MCP

A co-founder of Hail, an MCP service that gives AI agents tools for email, texts and calls, outlined a pattern for preventing duplicate sends when agent tool calls time out after the service has already accepted the request. The approach assigns a deterministic idempotency key to each planned message before the first attempt, persists per-message records with states that distinguish 'accepted' from 'unknown', and resumes batches from those records rather than restarting them. Hail's send_email tool accepts the key and can replay a saved response for the same key, including a saved failure.

by read4 min views17 publishedOct 20, 2026

An AI agent sends an email, then the service accepts it, but the connection drops before the agent gets an answer.

The agent tries again. Does your lead get the same email twice?

This is an easy problem to miss when connecting AI agents to tools. You can reduce the risk by saving a retry key before sending, then tracking each message outside the chat.

We’ll use Hail MCP as the example. It gives MCP clients tools for email, texts, and calls. The same ideas apply to other tools that perform actions.

Disclosure: I am one of the founders of Hail.

When a request times out, you know the client stopped waiting. You do not yet know what happened at the other end.

The email might have been accepted before the connection dropped. Even cancelling the request may arrive too late to stop it.

That makes “just try again” risky.

Instead, your workflow needs to recognize a repeated attempt at the same send.

An idempotency key tells a service that a request is another attempt at the same action.

Think of it as a receipt number. When you ask about the same purchase twice, you want the original receipt.

Hail’s send_email tool accepts an idempotency_key. If you leave it out, the tool creates a fresh UUID for that invocation.

For a workflow that may retry, create and save your own key before the first attempt.

For example:

campaign_042:lead_018:email:intro

Keep that key for retries of this specific message. Give a separate key to a later follow-up.

Avoid generating a new key whenever a request fails. A new key represents a new action.

Here is an example of arguments for Hail’s send_email tool.

Use an inbox you control for testing. Replace the sender with a verified address in your account.

{
  "to": ["your-test-inbox@example.com"],
  "from_": "verified-sender@example.com",
  "subject": "The details you requested",
  "body_text": "Here are the details we agreed to send.",
  "recipient_consent": true,
  "message_type": "marketing",
  "consent_source": "REPLACE_WITH_REAL_PERMISSION_RECORD",
  "idempotency_key": "campaign_042:lead_018:email:intro",
  "metadata": {
    "campaign_id": "campaign_042",
    "lead_id": "lead_018",
    "message_id": "intro"
  }
}

Set recipient_consent to true only when you can make that statement honestly. Hail requires this confirmation. It does not verify permission for you.

Marketing messages also require a real consent_source.

For outbound campaigns, write the sales copy yourself. Let the agent find leads and run approved actions. Save the finished message so a retry uses the same content.

A retry key helps with repeated requests. Your workflow also needs a lasting record of what it intended to send.

Save these fields in your database:

Use states that distinguish “accepted” from “unknown.”

If the connection drops without a result, mark the operation as unknown. Do not quietly return it to the unsent list.

Otherwise, another worker or a new chat may send it again.

If you saved a Hail email ID, use get_email to check that message.

If you have no ID, keep the original key and unchanged request. Follow the service’s retry behavior and retention limits.

Hail can replay a saved response for the same key. That response can include a saved failure. Repeating the request does not always start another attempt.

A request may also still be processing. Wait and investigate before creating a replacement operation.

If the result remains unclear, for review. Creating a fresh key just to clear an error can cause another send.

Also, keep acceptance separate from delivery. A service accepting your request does not mean the email reached the inbox.

Suppose a batch contains 100 leads. The first 40 messages succeed, then the connection fails.

Restarting all 100 messages could repeat the first 40.

Give every planned send its own record and key. Resume from those records, checking unknown results before sending anything else.

If several workers process the batch, claim each operation with an atomic database update. This prevents two workers from picking up the same ready message.

Keep the retry key too. A worker can crash after acceptance, before saving the result locally.

Before using a real lead list, simulate this sequence:

Also test a changed message, two workers claiming the same operation, and an opt-out received after approval.

These checks matter more than a successful first send. They show whether your workflow can recover when the answer disappears halfway back.

── more in #ai-agents 4 stories · sorted by recency
── more on @hail 3 stories trending now
sponsored brought to you by zahid.host 4,200+ EU-deployed projects
reading about agents? ship yours in a single git push.

Run your AI side-project on zahid.host

EU-based hosting, git-push deploys, automatic HTTPS, no cold starts. Free tier with a custom domain — perfect for shipping the agent you just read about.

$git push zahid main
→ Live at https://your-agent.zahid.host ✓
Get free account → Pricing
from €0/mo · no card required
LIVE [news/how-to-avoid-duplica…] indexed:0 read:4min 2026-10-20 · —