The short answer
Pass a deterministic idempotency key in theheaders field of messages.send(). Commune deduplicates requests with the same key within a 24-hour window — if your agent retries the same send, only one email goes out. For application-level deduplication, track (thread_id, intent) pairs in your database before sending.
Why duplicates happen
Duplicate emails are the most common production bug in agent email systems. They happen for several reasons:- Network retries. Your HTTP client retries a 503 or timeout, but the first request already succeeded. The email sends twice.
- Queue replays. Your task queue (SQS, BullMQ, Celery) re-delivers a message after a consumer crash. The handler runs again and sends another email.
- Webhook double-fires. An inbound webhook fires twice (network glitch, retry logic), and your agent generates two replies to the same message.
- Agent loops. Agent A replies to a customer. The reply triggers a webhook. The webhook handler does not check direction and fires Agent A again. Infinite loop.
- LLM non-determinism. Your agent evaluates the same thread twice (e.g., periodic polling) and decides to reply both times because the LLM does not remember it already did.
Idempotency keys
Pass anIdempotency-Key header in your send request. Commune stores the key and returns the original response for any duplicate request within 24 hours.
The key should be deterministic — derived from the inputs, not randomly generated per attempt. This way, retries of the same logical send use the same key automatically.
What makes a good idempotency key
The key must be the same across retries of the same logical operation, but different for genuinely different sends.Application-level deduplication
Idempotency keys protect against retries of the same API call. But what about your agent deciding to send the same email from two different code paths? For that, track sends in your own database.Preventing agent loops
The most dangerous duplicate is an infinite loop: your agent replies to a customer, the reply triggers a webhook, and the webhook handler fires the agent again. This burns through your send quota in minutes. Three guards to prevent loops:1. Check message direction
The most common fix. Only process inbound messages in your webhook handler.2. Track replied-to message IDs
Even after filtering by direction, an inbound webhook can fire twice. Track which message IDs you have already replied to.3. Rate-limit per thread
As a safety net, cap how many times your agent can reply to a single thread within a time window. If your agent has already sent 5 replies in the last hour to the same thread, something is probably wrong.TypeScript
Defense in depth
The strongest protection layers all three strategies:
No single layer catches everything. Use all of them.
Related
Webhooks
Webhook payload reference and signature verification for inbound events.
How does Commune thread emails?
Thread resolution mechanics — how Commune identifies which thread an inbound email belongs to.
Rate Limits
Commune’s built-in rate limits and how they protect your sender reputation.

