
idempotent agent email actions: how to prevent duplicate messages
When your agent retries a failed email send, idempotency prevents duplicate messages. Here's how to implement it.
Your agent sent a follow-up email to a customer. The API returned a timeout. The agent retried. Now the customer has two identical messages in their inbox and a growing suspicion that your product is broken.
This is the duplicate message problem, and it hits agent email workflows harder than human-driven ones. Humans notice when they click "send" twice. Agents don't notice anything. They retry on failure because that's what reliable systems do. Without idempotent handling, every retry becomes a potential duplicate.
Why agents send duplicate emails#
Three things conspire to create duplicates in agent email workflows.
The most common culprit is network timeouts paired with successful delivery. Your agent calls the send API. The email gets delivered to the recipient's mailbox, but the HTTP response times out or drops before the agent sees it. The agent interprets this as a failure, retries, and now the recipient has two copies of the same message.
Next is at-least-once delivery semantics. If your agent pulls tasks from a message queue (SQS, Redis, RabbitMQ, or anything similar), the queue guarantees each message will be delivered at least once. That "at least" part means your handler might process the same task twice, especially during deployments, crashes, or partition events. At-most-once delivery avoids this problem but risks dropping messages entirely, which is worse for most email workflows where a missed send is more dangerous than a duplicate.
The third cause is specific to AI agents and almost nobody talks about it: LLM non-determinism on retry. When an agent retries a failed email action, the language model might regenerate a slightly different body. Same subject line, same recipient, different wording. Traditional message-ID deduplication won't catch this because the payload looks different even though the intent is identical.
If you're building an agent that triages your support inbox, duplicate processing means duplicate replies to customers, duplicate escalations to your team, and duplicate entries in your CRM. The downstream damage compounds fast.
Idempotency vs deduplication#
These terms get used interchangeably, but they solve different problems.
Deduplication filters out messages you've already seen. It checks an identifier (usually a message ID or content hash) and drops anything it recognizes. This works well for incoming email processing, where the email provider assigns a stable Message-ID header. It breaks down when IDs aren't reliably unique across providers, or when the same logical action produces different payloads on retry because the LLM rewrote the content.
Idempotency ensures that performing an action multiple times produces the same result as performing it once. An idempotent email send means: call "send this email" three times with the same idempotency key, and exactly one email goes out. All three calls return the same stored result.
For agent email actions, idempotency is almost always what you want over deduplication. Deduplication answers "have I seen this before?" Idempotency answers "have I already done this?" When your agent is the one initiating actions rather than consuming them, the second question matters more.
| Approach | Operates on | Keyed by | Handles LLM regeneration | Handles retries |
|---|---|---|---|---|
| Deduplication | Incoming messages | Message ID or content hash | No | Partially |
| Idempotency | Outgoing actions | Action intent + payload hash | Yes, if keyed on intent | Fully |
How to make agent email actions idempotent: step-by-step#
Here's how to handle duplicate email messages in agent workflows using idempotency keys and a processed-actions store.
- Generate a stable idempotency key before the agent enqueues the email action. Combine the action type, recipient, and a hash of the intent:
sha256("send" + "recipient@example.com" + "quarterly-report-q1-2026"). This key stays the same even if the LLM rewrites the body on retry. - Query your processed-actions store for the key before executing. If the key exists, return the stored result without sending again.
- Execute the email send and record the key atomically. If you record the key first and the send fails, you've blocked all future retries. If you send first and recording fails, you're back to duplicates.
- Store the full outcome alongside the key: message ID, timestamp, delivery status. Future retries return this cached outcome directly.
- Set a TTL on idempotency keys. For most email workflows, 24 to 72 hours is reasonable. Permanent keys bloat your datastore and block legitimate re-sends weeks later.
- Handle partial failures explicitly. If the send succeeds but your database write fails, log the orphaned send so a reconciliation process can catch it. Don't silently ignore the inconsistency.
Tip
An idempotency key is not a message ID. The message ID identifies the sent email after delivery. The idempotency key identifies the intent to send before anything happens. Generate the key first, get the message ID after.
Agent layer vs infrastructure layer#
You can enforce idempotency in two places, and the tradeoffs matter.
At the agent layer, your orchestration code (the workflow engine, task queue, or agent framework) manages idempotency keys and the processed-actions store. You get full control over key generation, TTL windows, and storage backends. The downside is that every agent builder has to implement this correctly. One missed edge case, one race condition in the store lookup, and duplicates slip through.
At the infrastructure layer, the email API itself accepts an idempotency key on send requests and handles deduplication server-side. The agent passes a key, and the API guarantees at-most-once delivery for that key within a TTL window. This shifts the correctness burden from individual developers to the platform, which is simpler and more reliable when you're running many agents in parallel.
LobsterMail's send endpoint accepts idempotency keys natively. Pass a key with your send request, and the infrastructure handles the deduplication. If you're using webhooks to receive email, each delivery includes a unique event ID so your handler can deduplicate inbound notifications the same way.
The best approach is defense in depth. Generate stable keys at the agent layer, pass them to infrastructure that enforces them, and log any discrepancies for reconciliation. Neither layer alone covers every failure mode, but together they catch nearly everything.
Solving the LLM regeneration problem#
This is the scenario most idempotency guides miss entirely.
Your agent tries to send a quarterly report. The first attempt times out. On retry, the LLM regenerates the email body. The first draft said "Revenue grew 12% quarter over quarter" and the second says "We saw 12% QoQ revenue growth." Same facts, different words.
If your idempotency key includes a hash of the full email body, these look like two distinct actions. Both get sent. The recipient now has two slightly different versions of the same report.
The fix: key on intent, not content. Your idempotency key should capture what the agent is trying to do ("send Q1 report to finance-team@company.com"), not the exact words it generated. Strip the LLM output from the key entirely and let the first successful execution's content be the one that ships.
Even better: capture the generated email body before the first send attempt and reuse that exact body on retry, rather than letting the LLM regenerate. Treat the email content as an artifact of the first attempt, not something that gets recreated each time. This approach also saves you tokens and makes debugging simpler, since you can inspect exactly what was sent.
Multi-agent coordination#
When multiple agents share access to the same inbox or operate on the same recipient list, deduplication gets harder. Agent A might send a welcome email while Agent B, consuming the same signup event from a different queue partition, tries to send the same welcome.
The solution is a shared idempotency store. Both agents generate keys using the same scheme and check the same datastore before sending. This is where testing in a sandbox environment becomes essential, because multi-agent race conditions are nearly impossible to reproduce in production without causing real user-facing damage first.
If your agents share a LobsterMail account, the infrastructure-level key enforcement works across all agents automatically. One key, one send, regardless of which agent made the request.
Start with the key#
If you take one thing from this article: generate your idempotency key before anything else. Before the LLM drafts the email, before the task enters the queue, before any network call. The key anchors the entire flow. Everything downstream (the store lookup, the send, the response cache) follows from having a stable, intent-based key that survives retries, content regeneration, and multi-agent contention.
Frequently asked questions
What is idempotent message handling and why is it critical for AI agent email workflows?
Idempotent message handling means processing a message produces the same outcome whether it runs once or ten times. For agents, this prevents duplicate emails when retries occur after timeouts, crashes, or queue redeliveries.
How can an AI agent accidentally send the same email twice?
The most common scenario: the agent sends an email, the API response times out, and the agent retries because it thinks the send failed. The original email was already delivered, so the recipient gets two copies. Queue-based architectures with at-least-once delivery also trigger duplicates during rebalancing or crash recovery.
What is the difference between deduplication and idempotency for agent email actions?
Deduplication filters out messages you've already seen, keyed by message ID or content hash. Idempotency ensures an action produces the same result no matter how many times it's called, keyed by intent. For outgoing agent emails, idempotency is more reliable because it handles cases where retries produce different content.
How do you assign a stable idempotency key to an email action before enqueuing it?
Combine the action type, recipient address, and a hash of the business intent (not the LLM-generated body). For example: sha256("send" + recipient + "q1-report-2026"). Generate this key before any network call or LLM invocation so it remains stable across retries.
What happens when an AI agent retries a failed email send without idempotency?
The recipient receives duplicate emails. If the original send actually succeeded (but the response was lost), the retry creates a second delivery. With idempotency, the retry hits the processed-actions store, finds the existing result, and returns it without sending again.
Should idempotency be enforced at the agent orchestration layer or at the email infrastructure layer?
Both. The agent layer generates stable keys and can short-circuit retries locally. The infrastructure layer acts as the final guard, rejecting duplicate keys server-side. Defense in depth catches failures that either layer alone would miss.
What is a PROCESSED_MESSAGES table and how does it apply to agent email pipelines?
It's a database table (or cache) that stores idempotency keys for actions already executed. Before sending an email, the agent checks this table. If the key exists, it skips the send and returns the stored result. The table should have a TTL to avoid unbounded growth.
How does at-least-once delivery differ from at-most-once delivery for agent email systems?
At-least-once guarantees every message reaches the consumer but may deliver it multiple times. At-most-once delivers each message zero or one times, risking dropped messages. Most agent email pipelines use at-least-once delivery and rely on idempotency to handle the resulting duplicates.
Can idempotency keys expire, and what risk does expiry create for long-running workflows?
Yes, keys should expire (typically 24-72 hours). The risk: if an agent workflow spans longer than the TTL, a late retry might not find the original key and re-execute the action. For long-running workflows, use longer TTLs or persist keys until the workflow completes.
How do multi-agent systems coordinate to avoid sending duplicate emails from different agents?
Use a shared idempotency store that all agents check before sending. Both agents must generate keys using the same scheme (same hash inputs, same algorithm). LobsterMail enforces idempotency keys at the API level, so agents sharing an account get cross-agent deduplication automatically.
How do you handle idempotency when an LLM regenerates a semantically different email body on retry?
Key on intent, not content. Strip the LLM-generated body from your idempotency key and use only the action type, recipient, and business context. Better yet, cache the generated body from the first attempt and reuse it on retry instead of asking the LLM to regenerate.
Does LobsterMail support idempotency keys on email sends?
Yes. Pass an idempotency key with your send request and the API guarantees exactly-once delivery per key within the TTL window. This works across all agents sharing the same account.
What is the difference between natural idempotency and explicit deduplication?
Naturally idempotent operations (like setting a value to "active") produce the same state regardless of repetition. Email sends are not naturally idempotent because each call creates a new message. Explicit deduplication uses keys or stores to make non-idempotent operations behave idempotently.
What deduplication strategies work when incoming email message IDs are not reliably unique?
Fall back to content-based hashing: combine the sender, subject, timestamp (rounded to a window), and a hash of the body. This catches most duplicates even when providers assign different Message-ID headers to redelivered copies. For incoming email handling, see our guide on webhooks vs polling.


