For agents · Published 2026-09-18 · Updated 2026-09-28 · AgentiSend
Agent email loop detection
To stop an AI agent from sending the same email in a loop, send through a key whose recent history is checked before every send. The guard refuses the repeat, records it for a human, and reports the key's loop state.
To stop an AI agent from sending the same email in a loop, send through a key whose recent sends are read and compared before every new one. The loop guard runs on every send from an API key, refuses the repeat with approval_required, records the held message so a human can decide, and reports the key's standing loop state on GET /limits/keys. A new key watches repetition. Set loop_guard on the key to watch recipient collapse as well, or to watch neither.
The two patterns it refuses
Repetition. The guard compares the candidate against this key's sends from the last 60 minutes. A prior send counts as near-identical when it shares the recipient and both subject and body similarity are at least 85%. The candidate is refused once it would be the 4th such message.
Recipient collapse. Content that changes slightly each time defeats similarity matching, so recipients are measured too, on a key whose loop_guard is full and on a marketing send from a key with no setting. Once the window holds at least 20 sends and the effective number of distinct recipients has fallen below 3, the next send to one of the inboxes that window piled onto is refused. Mail to anyone else still sends. Cc counts as a recipient, the same as To.
A new key is repetition-only: the collapse rule does not run on its transactional sends. Set loop_guard to full to watch both, or to off to watch neither. whoami reports the setting.
What the agent gets back
{
"error": {
"code": "approval_required",
"message": "Blocked: this agent has sent 3 near-identical emails to customer@example.com within 60 minutes, which matches a retry or script loop; review the agent before sending more.",
"fix": "Review this key's recent sends via GET /emails?api_key_id=:id to find the loop, then fix the agent before sending again.",
"docs_url": "https://agentisend.com/docs/errors",
"retryable": false
}
}retryable is false. Retrying is the behaviour that produced the loop, and it will be refused again. The refusal carries the evidence the guard used: the counts, the window, and the entropy reading.
Nothing is dropped
The held message is written to the approval queue, so a person can send it after looking at it: GET /agent-actions lists it with held_reason naming which signal fired, and POST /agent-actions/{id}/approve sends it. See /agent-email-approvals.
Watching a key before it trips
GET /limits/keys returns loop_state for every key, one of:
clear— the window shows no repetition and no recipient collapse.watch— the pattern is forming but no send has been refused yet.tripped— the next send that matches the pattern this key watches would be held.
loop_evidence on the same row carries the numbers behind the state. The middle state is the useful one: it is where an agent can be fixed before anything is held.
Testing without sending
POST /emails/preflight runs every send gate, the loop guard included, and reports what POST /emails would do. It does not send, and it does not record a hold a person could approve.
What a budget will not do here
A budget caps volume, so an agent looping under its ceiling stays under its ceiling. That is why the loop guard is a separate check rather than a smaller number. Both apply: /agent-email-budgets.
Read next
- /email-for-ai-agents — every agent control in one page.
- /docs/guides/budgets-and-the-kill-switch — budgets, the kill switch, and how they sit next to this.