Blog · Guides

How can an AI agent read an email verification code without writing a regex?

Sign-up forms stop agents with “we sent you a code”. The obvious fix, a six-digit regex over the inbox, works in a demo and breaks on real mail. Here is why, and the one call that replaces it.

Published
By
Agentboxd team
Reading time
5 min

#Why does a regex break on real sign-up emails?

Because verification emails are full of other numbers. Order numbers, dates, prices, phone numbers, copyright years and ticket IDs all look like codes to \d{6}. And many codes don’t look like six digits at all: some are four or eight digits, some are letters and digits, some are split by a space or a hyphen.

the usual first attempt
// The usual first attempt
const code = message.text.match(/\b\d{6}\b/)?.[0];

// What it finds in real sign-up emails:
//   "Order 482913 confirmed"          → 482913   (an order number)
//   "© 2026 Example Inc."             → no match, but "2026" at 4 digits would be
//   "Call +1 415 555 0199"            → no match, or part of a phone number
//   "Your code: KQ7-M2X"              → no match (letters and a hyphen)
//   "G-583920 is your Google code"    → 583920, luckily

Links are worse. A sign-up email has a confirm button, but also “log in”, “manage preferences”, “unsubscribe”, tracking pixels and a privacy policy. Taking the first link can unsubscribe the agent instead of confirming it. And an agent that polls the inbox in a loop either waits too long or reads the previous code.

#What does Agentboxd do instead?

Every inbound email is checked for a code or link the moment it is stored, before your agent asks. The rules favour precision, because a wrong code is worse than none: the agent can always read the message itself.

  • Codes near the right words. A candidate only counts close to a strong keyword (code, verification, OTP, passcode, 2FA, security code, PIN, and their equivalents in other languages) or, further in, a weaker one such as “sign in” or “confirm”.
  • The usual impostors are rejected: numbers glued to other numbers (phones, dates, card numbers), prices, labelled IDs (order, invoice, account, tracking), “error code”, “zip code”, promo codes, and years unless a strong keyword is right next to them.
  • All the shapes codes come in: 4 to 8 digits, or 6 to 10 letters and digits, with separators removed so you always get one canonical value (482 913 → 482913, KQ7-M2X → KQ7M2X, G-583920 → 583920).
  • A code in the subject wins. Otherwise the one closest to a keyword.
  • Links by what they say. The button text (verify, confirm, activate, magic link, reset) outranks the URL, so tracking-wrapped buttons still work. Unsubscribe, preferences, tracking and privacy links are never returned.

On top of the pattern match, an AI classifier reads the email and answers one question: is this a login, one-time-code or confirm-your-email message? Pattern matching alone is capped at a confidence of 0.7. A clear yes from the classifier raises it to 0.95; a clear no drops it to 0.2. The classifier’s own answer is returned too, as jev_probability. How the classifier works is in How we classify every email an AI agent receives.

#How do you wait for the code?

Three steps: note the time, trigger the email, wait. The wait endpoint holds the request open until a verification email newer than since arrives, up to 60 seconds, and returns the newest one, since the latest code is the valid one. No polling loop, no sleep.

TypeScript
import { Agentboxd } from 'agentboxd';

const mr = new Agentboxd({ apiKey: process.env.AGENTBOXD_API_KEY! });

// 1. Note the time BEFORE triggering the email, so an older code can't be picked up.
const since = new Date().toISOString();

// 2. Trigger the sign-up (browser automation, an API call, whatever your agent does).
await signUpOnTheSite({ email: inbox.address });

// 3. Wait up to 60 seconds for the code or link to arrive.
const v = await mr.messages.waitForVerification(inbox.id, { since, timeout: 60, from: 'example.com' });

if (!v) throw new Error('no verification email within 60 s');
if (v.confidence < 0.9) console.warn('low confidence: read the message', v.message_id);

await enterCode(v.code ?? undefined); // or open v.link
Python
from datetime import datetime, timezone
from agentboxd import Agentboxd

mr = Agentboxd()  # reads AGENTBOXD_API_KEY

since = datetime.now(timezone.utc).isoformat()
sign_up_on_the_site(email=inbox["address"])

v = mr.messages.wait_for_verification(inbox["id"], timeout=60, since=since, from_="example.com")
if v is None:
    raise TimeoutError("no verification email within 60 s")
print(v["code"] or v["link"], v["confidence"])
curl
curl -s "https://api.agentboxd.com/v1/inboxes/$INBOX_ID/verification?timeout=60&since=2026-09-29T10:00:00Z&from=example.com" \
  -H "Authorization: Bearer $AGENTBOXD_API_KEY"
the answer
{
  "data": {
    "code": "583920",
    "link": null,
    "confidence": 0.95,
    "jev_probability": 0.97,
    "message_id": "0b3e8f4c-…",
    "from": "Example <no-reply@example.com>",
    "subject": "Your Example verification code",
    "received_at": "2026-09-29T10:00:07.412Z"
  }
}

Two details matter. Pass since from before you triggered the email: the default is the moment your request arrives, so a code that landed a second earlier would be missed. And pass from (a case-insensitive part of the sender) when the inbox is shared, so a code from another site can’t be picked up. On timeout you get data: null, not an error.

#What should the agent do with the confidence score?

ConfidenceWhat it meansSuggested action
0.95Pattern found and the classifier confirmed a verification emailUse the code or link
0.5 – 0.7Pattern found, not confirmed yet (or AI processing is off for the workspace)Use it for a sign-up you just started; otherwise read the message
0.2The classifier says this is not a verification emailDon’t use it; read the message

#Can the agent use a throwaway address for one sign-up?

Yes. A temporary inbox is receive-only, lives on its own domain, and deletes itself and its mail when its time is up (1 minute to 24 hours, 15 minutes by default). Use it for trials and one-off sign-ups; keep a permanent inbox for accounts the agent will log back into.

one sign-up, then gone
// A receive-only inbox that deletes itself (and its mail) after 15 minutes.
const inbox = await mr.inboxes.createTemporary({ ttlSeconds: 900 });
const since = new Date().toISOString();
await signUpOnTheSite({ email: inbox.address });
const v = await mr.messages.waitForVerification(inbox.id, { since, timeout: 60 });

Some sites refuse known disposable domains. Temporary inboxes are on a separate domain for exactly that reason: if it gets listed, your permanent agent addresses are unaffected. If a site refuses it, use a permanent inbox.

#What can’t it do?

  • SMS codes. This is email only.
  • CAPTCHAs. They exist to stop software; we don’t help agents get around them.
  • Codes inside images. Only text and links are read.
  • Decide whether the code should be used. An email can claim to be a login code for a sign-up the agent never started. Only use codes and links for sign-ups the agent itself just triggered; our MCP server tells the model the same.

#FAQ

Does it work with magic links as well as codes?

Yes. The answer has code, link or both; one of them is set whenever a verification email was found.

What if two emails arrive?

You get the newest verification email after since. Codes are usually replaced when a new one is sent, so the newest is the one that works.

Can I use it from Claude, Cursor or another MCP client?

Yes: the get_verification_code tool does the same wait, and create_temporary_inbox makes a throwaway address. See the MCP server docs.

Is the email content sent to an AI model?

Only if the workspace allows AI processing (the default, categorize), and then only to the classifier, for the confirmation step. With AI processing off, the pattern match still returns the code, without the confirmation. Details in AI processing and privacy.

Start with the quickstart: an inbox is one API call, and the free plan is enough to try this today.