跳到正文
DEV Community· Redouane Achouri·· 4 小時前AI 評分61

如何用 Hail MCP 降低 AI 智能體重複發送電郵的風險

How to avoid duplicate email sends with Hail MCP

AI 導讀

Hail MCP 的教程説明,如何為每封預定電郵保存冪等鍵及外部狀態記錄,降低 AI 智能體逾時重試造成重複發送的風險。Hail 的 send_email 支援 idempotency_key;重試時應沿用同一鍵與原始內容,並將沒有收到結果的操作標記為未知,而非未發送。批次處理時應逐封保存記錄,重新發送前核查結果未知的操作;多個工作者則可透過原子資料庫更新認領操作。

正文

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.

A timeout does not mean the email failed

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.

Save one key for each planned message

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.

Pass the key with the approved message

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.

Keep a record outside the chat

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:

  • The campaign and lead IDs.
  • The exact sender, recipient, subject, and body.
  • The retry key.
  • The current state.
  • The Hail email ID, when available.
  • The last attempt time.

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.

Check the result before retrying

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, pause 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.

Resume batches one message at a time

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.

Test the lost-response case first

Before using a real lead list, simulate this sequence:

  1. The service accepts the email.
  2. The connection drops before the result reaches your worker.
  3. The worker retries with the original key and message.
  4. The service returns the saved result.
  5. Only one email has been accepted.

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.

來源:DEV Community · dev.to