How an agent receives a verification code
The wall almost every agent hits first. It can fill in the form, then a service emails a six-digit code to an address it doesn't have. Here is the whole loop, in code that runs.
The shape of it: register an inbox, note how many messages are already in it, trigger the signup, poll until something new arrives from the sender you expect, then pull the code out of the body. Five steps, about twenty lines. The interesting part is the four ways it goes wrong.
The whole loop, in one script
Every call here was run against the live API. Nothing is elided and nothing is aspirational.
1 · Get an inbox
Unauthenticated POST. You get a real, deliverable address and a key shown exactly once.
Sleep before you use it. The mailbox is provisioned asynchronously and using the key in the same breath can fail. Two or three seconds is enough.
RESP=$(curl -s -X POST https://api.mailgi.xyz/v1/agents/register \
-H "Content-Type: application/json" \
-d '{"label":"signup-bot"}')
KEY=$(echo "$RESP" | jq -r .apiKey)
ADDR=$(echo "$RESP" | jq -r .emailAddress)
sleep 3 # let provisioning finish
echo "$ADDR" # => blank-reindeer@mailgi.xyz
2 · Mark your place, then sign up
Record how many messages exist before you trigger anything. This one line prevents the most common bug on this page — parsing a welcome email that arrived ahead of the code.
BEFORE=$(curl -s https://api.mailgi.xyz/v1/mail \ -H "Authorization: Bearer $KEY" | jq '.total') # ... now submit $ADDR to the service that # needs confirming ...
3 · Poll until it lands
Webhooks exist, and this is the one case where you should not use them: the code arrives within seconds and the flow is over in a minute, so standing up an HTTPS endpoint to catch a single notification is more work than asking twice. Confirmation mail is usually readable within ten seconds.
Bound the loop. Ninety seconds is generous; past a minute the signup has usually failed rather than being slow.
for i in $(seq 1 30); do
sleep 3
LIST=$(curl -s https://api.mailgi.xyz/v1/mail \
-H "Authorization: Bearer $KEY")
# only look if something NEW arrived
[ "$(echo "$LIST" | jq '.total')" -gt "$BEFORE" ] || continue
ID=$(echo "$LIST" | jq -r '
.messages
| map(select(.from[0].email | test("example\\.com$")))
| first | .id // empty')
[ -n "$ID" ] && break
done
[ -z "$ID" ] && { echo "no code arrived" >&2; exit 1; }
4 · Read the body, take the code
Bodies come back as JMAP parts. textBody is an array; each entry has a partId, and the text itself lives in bodyValues under that id. It looks indirect and it is one line of jq.
Anchor the pattern to a nearby word. Matching any six digits will eventually match a year or an order number.
MSG=$(curl -s https://api.mailgi.xyz/v1/mail/$ID \
-H "Authorization: Bearer $KEY")
BODY=$(echo "$MSG" | jq -r '
.bodyValues[.textBody[0].partId].value')
# anchored — not just any 6 digits
CODE=$(echo "$BODY" | grep -oiE \
'(code|verification)[^0-9]{0,20}[0-9]{6}' \
| grep -oE '[0-9]{6}' | head -1)
echo "$CODE" # => 481902
Four ways this goes wrong
In rough order of how often we have seen each one.
Reading the wrong message
The agent polls for any message and grabs the newest. A welcome email, a "someone signed up" notice, or a leftover message from an earlier attempt gets parsed instead of the code. Record the count before triggering the signup and require a genuinely new message, then filter by sender.
A regex that matches anything
[0-9]{6} matches a year, an invoice number, part of a phone number and the middle of a tracking ID. Anchor to the words around the code. If the service sends a confirmation link rather than a digit code, match the URL instead and request it — many services accept either.
Sending before provisioning finishes
Registering and immediately calling another endpoint can fail while the mailbox is still being created. It is a race, so it passes in testing and fails in production. Sleep two or three seconds after registering, and retry once on a 5xx.
Treating from as a string
It is an array of objects. The address is at msg.from[0].email, and the name beside it is frequently an empty string. Comparing msg.from to a string silently never matches, so the loop times out and the failure looks like the mail never arrived.
Questions, answered
01Can an AI agent sign up for a service on its own?
Yes, if it has a real mailbox. The blocker is almost never the signup form — it's the confirmation email, because an agent with no address can't receive one. Give the agent its own inbox and the flow completes without a human. Worth noting that some services deliberately prohibit automated signups in their terms; check before automating one.
02Why not use a disposable email service for this?
For a throwaway test it's a reasonable choice and costs nothing. For anything real there are three problems: disposable domains are widely blocklisted by exactly the services worth signing up for, so the signup is often rejected outright; the inboxes are usually public, so anyone who guesses the address reads the code; and there's no sending, so the agent can receive the confirmation but never reply to anything afterwards.
03How long should an agent wait for a verification email?
Poll every two to three seconds for about ninety seconds. Most confirmation mail is readable within ten; anything past a minute usually means the signup silently failed or the message was filtered, not that it's still in flight. Always bound the loop and fail with a clear error rather than polling forever.
04Why does my agent read the wrong code?
Two causes account for nearly all of it. Matching any six-digit number, which will happily match a year, an order number or a phone extension — anchor the pattern to nearby words like "code" or "verification" instead. And reading the newest message without checking it's newer than what existed before the signup, so a welcome email gets parsed instead.
05Can one agent handle signups for several services at once?
Yes, and the cleaner pattern is one agent per service. Registration is a single unauthenticated POST with no quota on how many agents you create, so spinning up a dedicated address per signup keeps the inboxes trivially separable and means you never have to filter by sender. It also limits the blast radius if one address ends up on a marketing list.
Now make it answer back
Receiving a code is the narrow case. The same inbox can run a support queue or coordinate with other agents.