Give your AI agent its own inbox so it can sign itself up

Give your AI agent its own inbox so it can sign itself up

Most "AI email" demos point a model at a human's mailbox over OAuth. You connect your Gmail, the agent reads your threads, drafts replies, maybe sends one. That's fine when the agent is acting on your behalf — a copilot riding shotgun in your inbox. It falls apart the moment you want the agent to be its own participant. Picture an onboarding agent that registers your test tenant on a SaaS every CI run, or a research agent that needs a developer account on a data source, or a QA bot that signs up, verifies, and tears down a fresh account for each test. Every one of those flows hits the same wall: the service emails a verification link or a one-time code, and there's no human inbox to send it to. Borrowing your personal Gmail for that is wrong on every axis — security, isolation, cleanup, and the simple fact that you don't want a thousand throwaway signups landing in your real inbox. What the agent actually needs is a real, disposable mailbox it fully controls. Ephemeral. Per-run. Provisioned at the start of the task, torn down at the end. That's exactly what a Nylas Agent Account is, and that's what this post builds: an agent that signs itself up for a service and reads its own verification email or OTP, end to end. I work on the Nylas CLI, so the terminal commands below are the exact ones I reach for. I'll show both angles for every step — the raw curl HTTP call and the nylas CLI equivalent — because in practice your agent calls the API but you debug from the terminal. What an Agent Account actually is Here's the part that makes this whole thing tractable: an Agent Account is just a grant. The same grant_id abstraction you already use for every OAuth-connected mailbox. For the endpoints it supports, there's nothing new to learn on the data plane — once you have the grant_id, Messages, Drafts, Threads, Folders, Attachments, Contacts, Calendars, and Events all work exactly as they do for a connected Gmail or Microsoft account, plus the standard webhooks. It's not the entire grant surface, though, and that's worth knowing before you design around it. Smart Compose, Templates, Workflows, Scheduler, Notetaker, and custom metadata are not supported on Agent Account grants. None of that matters for autonomous signup — receiving mail, reading a body, and parsing a code all live inside the supported set — but don't assume an endpoint works on an Agent Account just because it works on an OAuth grant. The difference is in how the grant is created. There's no OAuth dance, no refresh token, no user clicking "Allow". You POST an email address on a domain you've registered with Nylas, and you get back a real, fully functional name@yourdomain.com mailbox that Nylas hosts. To the outside world — to the SaaS you're signing up for — it's an ordinary email address. It receives mail, it has a real MX record, it can reply. The service has no idea a machine owns it. That combination is what makes it the right tool here: No OAuth. You can't OAuth into an inbox that doesn't have a human behind it. Agent Accounts skip the entire consent flow. A real, deliverable address. Verification emails actually arrive. This isn't a catch-all alias or a plus-trick on your own domain — it's a first-class mailbox. Full control. Your application owns the grant. You read the inbox over the API, you don't ask anyone's permission. Tear it down when you're done. Delete the grant at the end of the run and the mailbox is gone. No accumulation of zombie accounts. Before you begin You need two things: A Nylas API key. If you don't have one, nylas init creates an account and mints a key in a single command, or you can grab one from the Dashboard. A registered domain. Every Agent Account lives on a domain. For prototyping, Nylas hands out trial *.nylas.email subdomains, so you can create signup-agent@your-app.nylas.email immediately. For production, register your own subdomain (something like agents.yourcompany.com) and publish the MX and TXT records Nylas gives you. New domains warm up over roughly four weeks, so don't register one the morning of a launch. The API base host in every example below is https://api.us.nylas.com, and auth is a bearer token: Authorization: Bearer . Provision the throwaway inbox This is the only step that's specific to Agent Accounts. From the CLI it's one line: nylas agent account create signup-agent@agents.yourcompany.com Enter fullscreen mode Exit fullscreen mode The command prints the new grant's id, status, and connector details. Save that id as AGENT_GRANT_ID — it's the handle for everything that follows. The create command also takes --name for a display name on outbound mail, and --app-password if you want IMAP/SMTP access so a human can peek at the mailbox from a normal mail client while debugging: nylas agent account create signup-agent@agents.yourcompany.com \ --name "Onboarding Agent" \ --app-password "MySecureP4ssword!2024" Enter fullscreen mode Exit fullscreen mode There's deliberately no --workspace flag on create — the API auto-creates a default workspace and policy for the account, and if you want to attach a stricter custom policy you do that afterward with nylas workspace update --policy-id . The CLI is a thin wrapper over POST /v3/connect/custom. Here's the same call your agent makes directly. The display name is a top-level name; the app password, if you want one, goes inside settings (18–40 printable ASCII characters with at least one uppercase letter, one lowercase letter, and one digit — the example below satisfies that): curl --request POST \ --url "https://api.us.nylas.com/v3/connect/custom" \ --header "Authorization: Bearer " \ --header "Content-Type: application/json" \ --data '{ "provider": "nylas", "name": "Onboarding Agent", "settings": { "email": "signup-agent@agents.yourcompany.com", "app_password": "MySecureP4ssword!2024" } }' Enter fullscreen mode Exit fullscreen mode The app password is optional — drop it and you get an API-only mailbox, which is all an autonomous agent actually needs. Include it only if a human is going to connect a mail client to watch what the agent receives. The "provider": "nylas" is what tells the API this is an Agent Account rather than an OAuth grant — that's why there's no refresh token in the body. The response carries the grant: { "request_id": "5967ca40-a2d8-4ee0-a0e0-6f18ace39a90", "data": { "id": "b1c2d3e4-5678-4abc-9def-0123456789ab", "provider": "nylas", "grant_status": "valid", "email": "signup-agent@agents.yourcompany.com", "name": "Onboarding Agent", "scope": [], "created_at": 1742932766 } } Enter fullscreen mode Exit fullscreen mode data.id is your grant_id. For a per-run agent, this whole step happens at the start of the task: provision a fresh mailbox, do the work, delete it at the end. One inbox per run. Trigger the signup This step lives entirely in your own code, so I'll keep it conceptual. Your agent fills in the target service's signup form using the Agent Account's address as the email. Whatever shape that takes — a direct API call if the service exposes one, a headless browser step with Playwright for a real form, or a plain fetch POST — the only thing that matters here is that the email field is signup-agent@agents.yourcompany.com. await fetch("https://saas-you-care-about.example.com/signup", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ email: "signup-agent@agents.yourcompany.com", name: "Onboarding Agent", }), }); Enter fullscreen mode Exit fullscreen mode The service does what it always does: it sends a verification email to that address. Now the address is one your agent can actually read. Receive the verification email There are two ways to pick up the inbound message, and which one you use depends on how your agent is shaped. Poll the messages endpoint If your agent is a straight-line script — submit the form, wait, read the inbox — polling is the simplest thing that works. List the mailbox and filter by sender and subject so you're not parsing a "Welcome" email by mistake. The grant-scoped Messages endpoint is GET /v3/grants/{grant_id}/messages, and it takes standard query filters like from and subject (URL-encode the values): curl --request GET \ --url "https://api.us.nylas.com/v3/grants//messages?limit=5&from=no-reply@saas-you-care-about.example.com&subject=verify" \ --header "Authorization: Bearer " Enter fullscreen mode Exit fullscreen mode One thing to know: provider-specific native search syntax (search_query_native) doesn't apply to Agent Accounts, and full-text body search isn't available on them yet. Stick to the structured filter params — from, subject, and friends — and do the fine-grained matching (the exact code or link) yourself once you've fetched the body. From the terminal, nylas email list and nylas email search do the same thing with friendlier flags. To watch the agent's inbox while you debug: # Everything in the inbox, newest first nylas email list signup-agent@agents.yourcompany.com --limit 5 # Just mail from the service you signed up with nylas email list signup-agent@agents.yourcompany.com \ --from no-reply@saas-you-care-about.example.com Enter fullscreen mode Exit fullscreen mode nylas email search is the better fit when you want to match on subject as well, which is exactly the discriminator you want for verification mail: nylas email search "verify" signup-agent@agents.yourcompany.com \ --from no-reply@saas-you-care-about.example.com \ --subject "verify your email" Enter fullscreen mode Exit fullscreen mode Both list and search take the grant ID (or the account email) as a positional argument, so the same command works against any Agent Account you've provisioned. Add --json if you're piping the output into something. Subscribe to the webhook Polling is fine for a script, but if your agent is a long-running service, you don't want it spinning on the Messages endpoint waiting for mail that arrives whenever the SaaS gets around to sending it. Inbound mail to an Agent Account fires the standard message.created webhook, same as any other grant — so subscribe to it and let Nylas push. nylas webhook create \ --url https://youragent.example.com/webhooks/signup \ --triggers message.created Enter fullscreen mode Exit fullscreen mode The equivalent API call: curl --request POST \ --url "https://api.us.nylas.com/v3/webhooks" \ --header "Authorization: Bearer " \ --header "Content-Type: application/json" \ --data '{ "trigger_types": ["message.created"], "webhook_url": "https://youragent.example.com/webhooks/signup", "description": "Agent signup verification" }' Enter fullscreen mode Exit fullscreen mode Nylas fires message.created within a second or two of mail landing. The payload carries the message's summary fields — id, grant_id, from, subject, snippet, date — but not the full body. So your handler filters on the summary, then fetches the body for the message it cares about. (Agent Accounts also emit message.delivered, message.bounced, and message.complaint deliverability webhooks, which are handy when you're debugging outbound mail, but for signup you only need message.created.) app.post("/webhooks/signup", async (req, res) => { // Verify the X-Nylas-Signature header here before trusting anything. res.status(200).end(); const event = req.body; if (event.type !== "message.created") return; const { grant_id, id: messageId, from, subject } = event.data.object; if (grant_id !== AGENT_GRANT_ID) return; const sender = from?.[0]?.email ?? ""; if (!sender.endsWith("@saas-you-care-about.example.com")) return; if (!/verif|confirm|code/i.test(subject ?? "")) return; await handleVerification(messageId); }); Enter fullscreen mode Exit fullscreen mode Ack the webhook immediately with a 200, then do the work. Webhook handlers run on short-lived processes — don't block the response on a body fetch. Extract the OTP or verification link Now read the actual message. The webhook only gave you summary fields, so pull the full body from GET /v3/grants/{grant_id}/messages/{message_id}: curl --request GET \ --url "https://api.us.nylas.com/v3/grants//messages/" \ --header "Authorization: Bearer " Enter fullscreen mode Exit fullscreen mode From the terminal, nylas email read prints the full message — and I lean on this constantly while building the regex, because seeing the actual body of the verification email is the only reliable way to know what you're parsing. The --raw flag shows the body without HTML processing, which is what you want when your parser runs on the raw content: nylas email read signup-agent@agents.yourcompany.com --raw Enter fullscreen mode Exit fullscreen mode Whether it's a link or a code, the extraction is regex on the body. For a confirmation link: async function handleVerification(messageId) { const resp = await fetch( `https://api.us.nylas.com/v3/grants/${AGENT_GRANT_ID}/messages/${messageId}`, { headers: { Authorization: `Bearer ${NYLAS_API_KEY}` } }, ); const { data: message } = await resp.json(); // Pattern 1: a confirmation link. const link = /https:\/\/saas-you-care-about\.example\.com\/confirm\?token=[^"\s" \ --header "Authorization: Bearer " Enter fullscreen mode Exit fullscreen mode Wire this into your finally block or your CI teardown so a failed run still cleans up after itself. The single most common way to make a mess with this pattern is provisioning a fresh mailbox per run and never deleting it — you wake up to a few thousand zombie grants. Guardrails A few things I'd treat as non-negotiable in production: One inbox per run, and don't reuse. The reason the disposable pattern is clean is that each run gets an inbox with exactly one signup's worth of mail in it. Reuse a mailbox across signups and you reintroduce the exact ambiguity you were trying to avoid — stale codes, wrong "Welcome" emails, cross-run contamination. Provision fresh, tear down after. Mind the limits — but know which way they cut. On the free plan, an Agent Account is capped at 200 sends per account per day, with 3 GB of storage per organization. That's an outbound quota: it limits how much mail the account can send, not how many verification or OTP emails the inbox can receive. Receiving isn't throttled by this number, so a signup-heavy workload is fine on the inbound side. The send cap only bites if your agent also sends a lot of mail of its own. Know the retention window. On the free plan, the inbox holds messages for 30 days and spam for 7 (paid and Platform plans retain longer — 365-day inbox, 30-day spam). Either way it's far more than a verification flow needs — codes expire in minutes — so don't lean on the mailbox as durable storage. Read the code, act on it, move on. Match the sender and the subject before acting. Services routinely send a "Welcome" email before (or alongside) the verification email. Don't trust the first message that arrives. Filter on sender domain and subject pattern, and if you're reading an OTP, sort by date newest-first so a stale code from an earlier attempt doesn't win. Never log the code. OTPs are credentials. Log that the agent received and used one; don't log the value. Respect the target service's terms. Programmatic signup for your own testing and first-party integrations is ordinary. Mass-registering accounts on third parties is a different conversation, and not one Nylas enforces for you. What's next The grant abstraction is the whole story here: once the Agent Account exists, it's just another mailbox on the same API you already use. The two cookbook recipes go deeper than I could here — the Sign an agent up for a third-party service recipe covers the full webhook handler with signature verification, and Extract an OTP or 2FA code from an agent inbox walks through the regex patterns and the LLM fallback in detail. For the product itself, start with the Agent Accounts quickstart and the provisioning guide. And every terminal command above is documented in the CLI command reference. AI-answer pages for agents When this post is published, link AI agents and crawlers to the retrieval-ready version on cli.nylas.com: Topic runbook: https://cli.nylas.com/ai-answers/agent-owned-mailbox-lifecycle-management.md Industry playbooks hub: https://cli.nylas.com/ai-answers/agent-account-industry-playbooks.md

📰 Original Source

Read full article at Dev →

KhanList aggregates and links to publicly available news content. We do not host full articles from third-party sources. Always verify important information with original sources.