An agent signing up for a service gets maybe forty seconds of useful work done before it hits a screen that says we sent a code to your email. Everything after that point depends on machinery the agent usually doesn't have: an address that accepts mail, a way to know the mail arrived, and a way to pull six digits out of an HTML soup written for a human eye.
There are three jobs hiding in that sentence, and they're worth separating, because most of the pain comes from solving them in the wrong order.
1. An address that exists before you need it
The naive approach is to wire the agent into a real mailbox — a Gmail account with an app password, or an IMAP connection to a shared team inbox. This works exactly until you have two agents. Then they read each other's codes, and the failure is non-deterministic and miserable to debug.
What you actually want is one throwaway address per agent per task, created at the moment it's needed and thrown away after. That means provisioning has to be cheap enough to do inline — no dashboard, no API key, no human in the loop:
INBOX=$(curl -sS -X POST https://api.apumail.com/api/v1/inboxes)
ADDR=$(echo "$INBOX" | jq -r .address)
TOK=$(echo "$INBOX" | jq -r .token)
echo "$ADDR" # vivid-llama-4a2c@apumail.com
No auth on that call. The token that comes back is the credential for that inbox and nothing else, which is the property that makes it safe to hand to an agent: the blast radius of a leaked token is one disposable inbox.
2. Knowing the mail arrived, without a polling loop
This is where most homegrown implementations go wrong. The instinct is to poll: check the inbox, sleep two seconds, check again. It works on your laptop and it's a liability in production — you're burning tokens on every wake-up, and you're racing a code that typically expires in ten minutes.
Long-polling inverts it. The agent makes one request that blocks until the mail lands or the timeout expires:
curl -sS -H "Authorization: Bearer $TOK" \
"https://api.apumail.com/api/v1/inbox/$ADDR/wait?timeout=120"
One request. One wake-up. The connection sits open and returns the moment the SMTP listener accepts the message. For an agent this matters more than for a normal service: every extra poll is a model round-trip, and model round-trips cost real money and real latency.
3. Getting the digits out
Here's the part that quietly eats the most engineering time. A verification mail is a marketing artifact — nested tables, tracking pixels, a code wrapped in three spans and a font tag. Handing that to an LLM to "find the code" works about 95% of the time, which is a terrible number when it's in your critical path.
The tractable version is to extract server-side, at save time, before the agent
ever sees the message. apumail runs the extraction when the mail is accepted and puts
the result in an otp field on the message:
{
"messages": [{
"from": "noreply@stripe.com",
"subject": "Your Stripe verification code",
"otp": "482910"
}]
}
The agent reads .messages[0].otp. No parsing, no prompt, no
95%. The whole flow — provision, wait, extract — is three calls and no
model involvement at all, which means it can't hallucinate a code.
The whole thing
# 1. provision
INBOX=$(curl -sS -X POST https://api.apumail.com/api/v1/inboxes)
ADDR=$(echo "$INBOX" | jq -r .address)
TOK=$(echo "$INBOX" | jq -r .token)
# 2. use $ADDR in the signup form, then block until the mail lands
CODE=$(curl -sS -H "Authorization: Bearer $TOK" \
"https://api.apumail.com/api/v1/inbox/$ADDR/wait?timeout=120" \
| jq -r '.messages[0].otp')
# 3. submit $CODE
echo "$CODE" # 482910
If your agent speaks MCP, the same three steps are three tool calls and you can skip the HTTP entirely — see temp email as an MCP server. And if the wall your agent hit was an SMS code rather than an email one, the same wait/extract layer covers phone numbers.