Test Inboxes for AI Agents
An agent that exercises your signup flow needs to receive real email: the verification code, the confirmation link, the welcome message. In development that mail shouldn't land in your inbox, and the agent shouldn't be anywhere near production mailboxes. A sandbox domain gives every run its own address, and the API gives the agent a way to read what arrived.
Setup
Create a catch-all account on a dev domain
Username * on e.g. dev.example.com - how catch-all works. Every address on the domain is now deliverable without creating accounts.
Create an API token for the agent
On the API Keys page. The agent authenticates every request with the X-API-KEY header.
A Fresh Address per Run
Derive the address from the run, same as the QA pattern:
const address = `agent-${crypto.randomUUID()}@dev.example.com`
The agent signs up with it, and the mail lands in the catch-all's INBOX.
Reading the OTP
inboxOf and waitForMessage are the polling helpers from the CI guide. The catch-all holds mail for the whole domain, so match on the recipient:
const inbox = await inboxOf('*@dev.example.com')
const message = await waitForMessage(
inbox,
m => m.to.some(t => t.address === address),
)
const otp = message.text.match(/\b\d{6}\b/)?.[0]
const link = message.text.match(/https?:\/\/\S*confirm\S*/)?.[0]
For a long-running agent, skip polling: subscribe over SSE and hold one connection instead of hammering the list endpoint.
For the Agent Itself
The docs are agent-readable. https://smtp.dev/llms.txt indexes the site, and any docs page serves markdown when you append .md to its URL. The full API surface is in the OpenAPI document.
Boundaries
Receiving is unrestricted: sandbox domains have real MX records, so any service the agent signs up for can deliver to them. Outbound is the opposite - mail from the sandbox only delivers to accounts inside it. An agent with sandbox credentials can read everything on your test domain and email nobody else, which is exactly the blast radius you want while it's under development.