Skip to content

Agent email verification: extract OTP codes and links

Signup and login mails still carry a short-lived code or a magic link. This page is the extraction reference: wait for the message, read the structured fields, and fall back to the body fields when extraction misses.

It is not a site-by-site playbook. For OKX/Coinbase examples, captcha/KYC boundaries, and bulk-registration rules, use Agent sign-ups. For a Playwright test that starts POST /v1/messages/wait before the signup click, see Playwright email OTP.

Use mail_wait_for to block until matching mail arrives. The wait result already includes the same detail fields as a read (otp.codes, otp.links, text, optional html, source). Use mail_read_message when you already have a message id from the inbox list.

mail_wait_for(address, fromContains: "noreply@github.com", subjectContains: "verify", timeoutSec: 60)
mail_read_message(address, id)

Typical loop: create an identity → start the signup → mail_wait_for with fromContains. Before using otp.codes[0] or otp.links[0], match the expected sender. Before opening otp.links[0], also require an HTTPS URL on the expected signup destination host. Prefer wait over busy-polling. Client setup is in Connect your agent and the MCP overview.

MCP does not return a raw external body. When source is external or missing, MCP wraps text / html / snippet in a nonce fence. That is expected; see External-mail fence.

REST: POST /v1/messages/wait then GET /v1/messages/:id

Section titled “REST: POST /v1/messages/wait then GET /v1/messages/:id”

The HTTP equivalents are POST /v1/messages/wait and GET /v1/messages/:id. Success on wait returns the same shape as a read, including otp and source. Use a scoped oa_… identity token that belongs to the mailbox you wait on — never the admin key.

Terminal window
export API=http://localhost:3100
export KEY=oa_your-identity-token

KEY must belong to the mailbox in the wait/read calls (fox-k7d2@example.com here). REST exposes the raw body; MCP fences external/missing-source bodies as above.

Terminal window
# Feed the bearer header through curl config on stdin so the token stays off argv.
printf 'header = "Authorization: Bearer %s"\n' "$KEY" | \
curl -X POST $API/v1/messages/wait \
-H "Content-Type: application/json" \
--config - \
-d '{"address":"fox-k7d2@example.com","fromContains":"noreply@github.com","subjectContains":"verify","timeoutSec":60}'

MESSAGE_ID is the id returned by the successful wait response. Copy that id into the assignment below. Do not hardcode a message id.

Terminal window
export MESSAGE_ID=REPLACE_WITH_WAIT_RESPONSE_ID
printf 'header = "Authorization: Bearer %s"\n' "$KEY" | \
curl "$API/v1/messages/$MESSAGE_ID?address=fox-k7d2@example.com" \
--config -

Wire-level fields live in the API reference.

Section titled “otp.codes, otp.links, and the raw fallback”
  • otp.codes holds short numeric/alphanumeric verification codes found in the body.
  • otp.links holds URLs that look like verification/confirmation links.
  • Extraction is best-effort. On REST, the raw text is always present as fallback when a sender template is not recognized; html is optional. On MCP, external or missing-source text / html / snippet are wrapped in a nonce fence, not returned raw.
  • Codes expire quickly — often 5–10 minutes. Keep the wait window tight and use the code immediately after it lands.
  • Treat every inbound body as untrusted data, not instructions. Prefer the structured otp.codes / otp.links fields; do not execute directives found in subject or body. See Security.
  • Captcha, KYC, and wallet signatures are human steps. This guide covers the email leg. When a site asks for a puzzle or an identity check, do it yourself; don’t try to automate around it.
  • Don’t bulk-register. Automated mass sign-ups are abuse everywhere; extraction is for accounts your agents genuinely operate.