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

> Source: <https://dev.to/elkiks/how-can-an-ai-agent-read-an-email-verification-code-without-writing-a-regex-2e3k>
> Published: 2026-09-29 11:37:43+00:00

*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.*

**TL;DR** Give the agent its own inbox, note the time, trigger the email, then call `GET /v1/inboxes/:id/verification` (SDK `waitForVerification`, MCP `get_verification_code`). It waits up to 60 seconds and returns the code or link with a confidence score. The extraction runs on our server the moment the email arrives, and an AI check confirms it is really a verification email.

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.

``` js
// 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.

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.

`482 913` → `482913`, `KQ7-M2X` → `KQ7M2X`, `G-583920` → `583920`).
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](https://agentboxd.com/blog/classifying-inbound-email-for-ai-agents-jev?utm_source=devto&utm_medium=social&utm_campaign=xpost-email-verification-codes).

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.

``` js
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 -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"
{
  "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.

| Confidence | What it means | Suggested action | 
|---|---|---|
| 0.95 | Pattern found and the classifier confirmed a verification email | Use the code or link | 
| 0.5 – 0.7 | Pattern 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.2 | The classifier says this is not a verification email | Don’t use it; read the message | 

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.

```
// 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.

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

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.

Yes: the `get_verification_code` tool does the same wait, and `create_temporary_inbox` makes a throwaway address. See the [MCP server docs](https://agentboxd.com/docs/mcp?utm_source=devto&utm_medium=social&utm_campaign=xpost-email-verification-codes).

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](https://agentboxd.com/docs/ai-processing?utm_source=devto&utm_medium=social&utm_campaign=xpost-email-verification-codes).

Start with the [quickstart](https://agentboxd.com/docs/quickstart?utm_source=devto&utm_medium=social&utm_campaign=xpost-email-verification-codes): an inbox is one API call, and the free plan is enough to try this today.

*Originally published on the [Agentboxd blog](https://agentboxd.com/blog/email-verification-codes-ai-agents-without-regex?utm_source=devto&utm_medium=social&utm_campaign=xpost-email-verification-codes).*
