When was the last time you deleted an email account on purpose?
For most teams the answer is never, and that tells you something. We treat mailboxes the way we treated servers in 2008: hand-built, carefully named, kept alive indefinitely because recreating one is painful. They're pets. Meanwhile every other piece of our infrastructure — compute, queues, databases — became cattle: numbered, provisioned by code, destroyed without sentiment when the job ends.
Email is finally catching up. With Nylas Agent Accounts (in beta), a mailbox is created with one call and destroyed with another, and that symmetry is the whole point.
Provisioning, from the CLI:
nylas agent account create signup-agent@agents.yourdomain.com
Or via POST /v3/connect/custom
with "provider": "nylas"
— no OAuth, no refresh token, just an address on a domain you've registered. Teardown is equally unceremonious:
nylas agent account delete signup-agent@agents.yourdomain.com --yes
(The API equivalent is a DELETE
on the grant.) The signup automation recipe treats this as a loop: provision a fresh inbox, point a third-party signup form at it, catch the verification email through a message.created
webhook, follow the confirmation link, delete the grant. No human inbox involved at any step, and nothing left behind.
The middle of that loop is about twenty lines of webhook handler, and the recipe's version filters hard before acting — right grant, right sender, right URL shape:
const { grant_id, id: messageId, from } = 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;
const match =
/https:\/\/saas-you-care-about\.example\.com\/confirm\?token=[^"\s<]+/.exec(
message.body,
);
if (match) await fetch(match[0]);
Nylas fires message.created
within a second or two of mail arriving, so the whole signup round-trip typically finishes before a human would have found the email.
The one-time pet in this story is the domain, not the mailbox. You register a domain once per organization — picking a US or EU data center region — publish the MX and TXT records, and then mint as many addresses under it as your plan allows. Trial *.nylas.email
subdomains skip even that for prototyping. See provisioning and domains for the flow.
Per-run test inboxes. E2E tests that assert on real email delivery have always been awkward — shared test accounts accumulate state, and yesterday's run pollutes today's assertions. A fresh mailbox per CI run resets the world. The signup recipe's advice applies directly: if you're running a large test matrix, provision multiple grants rather than reusing one, because quotas are per account — 200 messages per account per day on the free plan.
Per-workflow identities. A research agent that needs a developer account on a data source, a QA bot that registers for a SaaS every run, a purchasing agent that needs a marketplace profile. Each gets an address, does its job, and gets reaped.
Per-environment separation. The provisioning docs describe running agents.staging.yourcompany.com
and agents.yourcompany.com
in the same application, so staging traffic never touches the production domain's reputation.
Per-customer pools. Sender-reputation isolation by splitting volume across sales-a.yourcompany.com
, sales-b.yourcompany.com
, and so on — a deliverability problem on one domain doesn't contaminate the rest.
Disposable doesn't mean unmanaged — arguably the opposite. Two practices from the docs are worth treating as non-negotiable.
First, reap your herd. The recipe is blunt: don't ship per-run agents without teardown, because inactive grants accumulate. Delete on completion or failure — the failure path is where orphans come from. If your CI provisions a mailbox, the cleanup step belongs in a finally
block, not at the happy-path end.
Second, fence the inbox. A disposable address that leaks into a spam list keeps receiving junk until you delete it. Pair an allowlist of expected sender domains with a block
rule for everything else, so the inbox only accepts mail from the service you're actually testing against. And don't trust the first message that arrives — some services send a "Welcome" email before the verification email, so match the sender and the expected URL pattern before your code clicks anything.
Third, fence the herd, not the animals. Per-account configuration defeats the point of cattle. Workspaces fix that: pass a workspace_id
when you create the grant — or let auto_group
place accounts into a workspace by matching their domain — and attach a policy there once. Every disposable mailbox inherits the same send quota, retention window, and block rules with zero per-account setup, and moving an account later is a single PATCH
on the grant.
One more option if humans occasionally need to peek inside a longer-lived account: set an app_password
at creation (18–40 printable ASCII characters, with an uppercase letter, a lowercase letter, and a digit) and connect a normal IMAP client. Nylas stores it as a bcrypt hash, so you can't retrieve it later — only reset it. Skip it for true cattle; protocol access stays disabled by default.
Here's where the metaphor honestly breaks down. A container's identity doesn't matter; an email address's identity is the product in some workflows. An address that customers know, that's whitelisted in their systems, that has months of threading history — that's a pet, and it should be. Reputation also accrues over time: a brand-new address sending cold outreach behaves differently from an established one.
So the pets-vs-cattle question for email isn't "which model is right" but "which mailboxes are which." My rule of thumb: if a human would notice the address changing, it's a pet. If only your code ever reads it, it's cattle — and keeping cattle alive out of habit is just unmanaged inventory.
Audit your own herd this week: list every automation-owned mailbox your team has, and ask of each one, "if this were deleted tonight, what breaks?" If the answer is "nothing," you've found your first candidate for a teardown script. How many pets are you feeding that should've been cattle?