The first time an agent needs email, the quickest path is the developer's own mailbox. Connect Gmail over IMAP or OAuth, search for the newest message from the sender, pull out the code. It works in a demo, because in a demo there is one agent, one sign-up and one person watching.
Run it for real and four problems show up. None of them is about the model's ability to read email. They come from the agent sharing an address, and with it an inbox and an account, with a person.
Matching gets ambiguous
In a shared inbox, the agent has to pick its message out of everything else that arrives. The usual rule is "newest message from this sender", and it breaks as soon as two runs overlap. Two runs sign up for the same service a few seconds apart, two verification emails arrive, and one of the runs takes the other's code. The failure is intermittent and depends on timing, which makes it hard to reproduce.
With an address per run, the recipient address becomes the correlation key. Mail sent to one run's address can only belong to that run, so the question the agent asks changes from "which of these messages is mine?" to "has my message arrived?", which one scoped call answers.
The agent reads what you read
Access to a personal mailbox is all or nothing. An OAuth grant or IMAP password that lets an agent fetch a verification code also lets it fetch bank statements, password resets for your other accounts and private conversations. Every message the agent loads ends up in a model's context and in whatever logs your model provider and your own code keep.
It also widens what can reach the model. Anyone can email you, and in a shared inbox any of those messages can end up in front of the agent, including one written to look like instructions. A dedicated inbox narrows the text an agent ever sees to mail sent to that specific address. Lumbox adds more on top: the text fields of every message arrive wrapped in a fence marking them as untrusted content, html_body has scripts, event handlers, iframes and embeds stripped, and GET /v1/emails/:id/safety scores a single message for phishing and prompt-injection signals on demand. The fence is a marker for the model, not a guarantee, so an agent should still act only on mail from senders it expects.
Your account carries the risk
Consumer mail providers watch for automated access, and when they decide an account is being misused they restrict the whole account, not just the automation. An agent that logs in from a server, reads mail in bursts and triggers dozens of sign-ups is the pattern those systems are built to catch, and the account at stake is the one you use for everything else. What to do when Gmail bans your agent covers that failure in detail.
Accounts and resets get tangled
Every service an agent registers with the developer's address is tied to the developer. Receipts, marketing and password resets for those accounts land in the developer's inbox, and moving the agent to another owner or shutting it down means changing the address on every one of them by hand.
What a dedicated inbox looks like
In Lumbox, an inbox is one API call. POST /v1/inboxes returns an id and an address (on @trylumbox.com by default), and every message that arrives is parsed on the way in, so a sign-up code is a field on the email object, not something the agent digs out of HTML. Receiving works with the TypeScript SDK:
import { Lumbox } from "lumbox";
const lumbox = new Lumbox(); // reads LUMBOX_API_KEY
const inbox = await lumbox.createInbox({ name: "signup-run-7f3a" });
// give inbox.address to the sign-up form your browser agent is filling in
const otp = await inbox.waitForOtp({ from: "github.com", timeout: 90 });
console.log(otp.code, otp.expires);
await inbox.delete();
waitForOtp calls GET /v1/inboxes/:id/otp, which holds the request open for up to 120 seconds and returns {code, all_codes, from, subject, expires, email_id} when a verification email arrives, or HTTP 408 when none does. It looks back five minutes by default, so a code that landed just before the call is still found, and it only considers mail categorized as verification, so the welcome message Lumbox seeds into an organization's first inbox is never mistaken for a code. Code extraction is regular expressions over the subject and plain-text body, which handles numeric codes of 4 to 8 digits; for anything else, the agent reads stripped_text.
Per run or per agent
An inbox per run gives the cleanest matching, but inboxes count against your plan (3 on Free, 10 on Starter, 50 on Pro, 250 on Scale), and they never expire on their own, so a per-run design has to call DELETE /v1/inboxes/:id when the run ends. There is a second cost. Once deleted, an address on the shared domain can be created again by anyone, and any account still registered to it would send its password resets to the new owner. Use per-run inboxes for throwaway sign-ups and a persistent inbox for any account the agent has to keep. Splitting inboxes across several cooperating agents is covered in multi-agent email isolation.
What isolation you actually get
Separate addresses isolate mail. Keys are a separate question. An organization key can read every inbox in the organization. If a component should only see its own inboxes, create a project and give that component a project key: project keys only reach the inboxes in their project and are refused on organization-level routes such as domains, webhooks and threads.
A dedicated inbox also leaves some problems where they were. It does not help with phone verification or CAPTCHAs; parsed.requires_browser flags a verification link that points at a CAPTCHA or challenge page, but the agent still has to get through it. Some services refuse sign-ups from shared domains, which is where a custom domain comes in.
The quickstart goes from a new API key to a first received code, and how to give any agent an email address walks through each step with the full request and response.