Multi-agent systems fail in ways that are hard to debug. Unlike a single-agent failure where the cause is usually obvious, multi-agent failures are often silent and intermittent. One of the most common sources of silent failure is shared email infrastructure. When two agents share an inbox, they will eventually pick up each other's OTPs. The failure rate is probabilistic, depends on timing, and leaves no obvious error trace.
The Race Condition
Imagine an orchestrator that spawns ten agents in parallel. Each agent signs up for the same service with a different username. All ten use the same shared inbox address. The service sends ten OTPs within seconds of each other.
Now each agent is polling the inbox looking for its OTP. They filter by recency — only OTPs that arrived after the agent started. But all ten agents started at roughly the same time. All ten OTPs arrived within the same window. Agent 1 grabs the first OTP it sees, which might be Agent 7's code. Agent 7's verification fails. Agent 1's verification also fails because it used the wrong code. Eight agents succeed. Two fail. The failure rate is 20%, the cause is completely non-obvious, and it is irreproducible on demand.
Time-windowed filtering, sender-based filtering, and subject-line filtering all have edge cases that produce the same class of failure. The only correct fix is inbox isolation.
Inbox Per Task: The Pattern
Each agent task that involves email must have its own inbox. Not shared. Not pooled. One inbox per task, created at task start, deleted at task completion. With this pattern, OTP routing is trivially correct by construction — each inbox receives exactly the emails destined for that agent's session.
import AgentMailr from "agentmailr";
const client = new AgentMailr({ apiKey: process.env.AGENTMAILR_API_KEY });
async function runAgentTask(taskId: string) {
// Each task gets its own inbox
const inbox = await client.inboxes.create({ username: `task-${taskId}` });
try {
// Pass inbox address to the agent's browser context
const result = await runSignupFlow(inbox.address);
// OTP is guaranteed to be for THIS agent's session
const { otp } = await client.messages.waitForOTP({
inboxId: inbox.id,
timeout: 30_000,
});
await completeVerification(otp);
return result;
} finally {
// Always clean up, even on failure
await client.inboxes.delete(inbox.id);
}
}
// 10 agents in parallel, each with its own isolated inbox
const results = await Promise.all(
Array.from({ length: 10 }, (_, i) => runAgentTask(`agent-${i}`))
);
// Zero OTP collisions. Guaranteed.
Inbox Lifecycle Rules
Following these rules eliminates the entire class of email-related multi-agent failures:
- Create at task start: provision the inbox before any email-related action in the task
- Use only within that task: never pass an inbox ID between tasks or agents
- Delete on completion: clean up in a finally block so deletion happens even on failure
- Never share: no two concurrent tasks should reference the same inbox ID
- One inbox per verification flow: if a task involves multiple services, create one inbox per service
These rules are simple. Following them consistently eliminates an entire class of intermittent, hard-to-debug failures from your multi-agent system.
Start Free
AgentMailr makes inbox isolation trivial. Create unlimited isolated inboxes via API. Free to start, no credit card required.