Postboi PRE-RELEASE
← All posts

Email for AI agents: an inbox that goes, and one that stays

tempboi.email hands an agent a throwaway inbox in one request. agentboi.email gives it an address it keeps, that tells your team from a stranger and answers in the thread.

An agent that does real work runs into email almost at once. It signs up for a service and the service sends a code. It books something and the confirmation goes to an inbox. Someone wants to hand it a job, and the most natural way to do that is to write to it.

Those are two different jobs. One wants an address for five minutes, to read a code and throw away. The other wants an address that is still there next month, that people can write to and get an answer from. So there are two sites now, both run by Postboi: tempboi.email for the address that goes, and agentboi.email for the address that stays.

tempboi.email: an address that goes

One request makes an inbox. No account, no token, no sign-up:

terminal
curl -X POST https://tempboi.email/v1/inboxes

The answer is the address and a tb_… token, which is the only thing that can read it. Then wait. The request holds open until a matching email lands, and answers with the one-time code and the verify link already pulled out, tracking redirects unwrapped:

terminal
curl "https://tempboi.email/v1/inboxes/$ADDRESS/wait?subject=verify&timeout=60" \
  -H "Authorization: Bearer $TOKEN"

An inbox lasts an hour unless you ask for longer (a day at most without an account), and when it expires it is gone within the minute: the mail, the attachments, the lot. The same inboxes work in a test, which is where a sign-up flow finally gets checked end to end rather than mocked:

signup.test.ts
import { temp } from "postboi/inbox"

await using inbox = await temp()
await page.fill("#email", inbox.tag("signup"))
const { code } = await inbox.wait({ subject: /verify/i })

Waiting is a long poll rather than a WebSocket, on purpose. Agent sandboxes and CI runners sit behind proxies that eat sockets, and a plain HTTP request that takes a while gets through all of them. And there's a page for people, too: open tempboi.email and you have an address before you've finished reading it, with mail appearing as it lands.

agentboi.email: an address that stays

The same shape, one request:

terminal
curl -X POST "agentboi.email?address=orders"

And what comes back:

what it prints
orders-k3f9@agentboi.email
key:    mb_…
claim:  https://postboi.app/claim/…
        (open this to let it send; until then it only receives)
wait:   curl "https://agentboi.email/v1/mailboxes/orders-k3f9@agentboi.email/wait" \
          -H "Authorization: Bearer mb_…"

The four random characters mean nobody can take another agent's address, and an address that was ever somebody's is never handed to anyone else, even after it's deleted. Names that read as somebody you might trust (support, billing, security, a bank) are refused outright.

A new mailbox receives straight away and sends once a person claims it. That's the one rule that matters most here. An address nobody has vouched for, sending mail, is exactly what people complain about when they talk about agents and email, so until somebody opens the claim link and signs in, it can read but not write. Claimed, it joins their team and sends under the team's name and limits. Unclaimed, it deletes itself after 14 days.

If you already have a Postboi account, skip the claim: a mailbox made with your team's API key is the team's from the start, and can live on your own receiving domain instead of agentboi.email:

agent.ts
import { mailbox } from "postboi/mailbox"

// With POSTBOI_TOKEN set, the mailbox is your team's and can send straight away.
// domain puts it on one of your receiving domains: orders@reply.acme.com
const box = await mailbox.create({ address: "orders", domain: "reply.acme.com" })

Every message says who is talking

The hard part of giving an agent an inbox isn't receiving the mail. It's that anyone on earth can write to it. So every message arrives with a trust label, decided by Postboi when it landed:

  • owner: a member of the team that owns the mailbox, and the mail passed DMARC.
  • thread: a reply to something your team sent.
  • stranger: anyone else.
  • suspect: read as junk, refused as spam by the receiving server, or failing DMARC.

The DMARC verdict is the receiving server's, never anything the sender wrote into a header, so owner can't be claimed by putting your boss's address in the From line. Which makes the loop an agent actually wants a few lines long:

agent.ts
import { mailbox } from "postboi/mailbox"

// Opens POSTBOI_MAILBOX_KEY, or makes a new mailbox
const box = await mailbox()

for await (const mail of box.watch()) {
	if (mail.trust === "suspect") continue

	// reply_text is what they wrote, minus the quoted thread and their signature
	const answer = await agent.respond(mail.reply_text, { trust: mail.trust })
	await mail.reply({ text: answer })
}

The rest of a message is read out once, when it arrives, so the agent doesn't have to. reply_text is what the person wrote, with the quoted conversation and their signature cut off (conservatively: where nothing marks a quote, the text is kept whole). Codes and links are pulled out the way Tempboi's are. Threads come back as conversations, received and sent together. A reply keeps the recipient, the subject and the headers that hold it in the sender's thread. And plus-addressing works: orders-k3f9+refunds@agentboi.email lands in the same mailbox with the tag refunds, for the agent to sort by.

It's your team's mail

A mailbox isn't a separate product with its own dashboard. Mail to it is filed in your Received log like any other, fires the same email.received webhook (now carrying the trust label and the reply text), and shows up in the same notifications. Sends from it go through the same pipeline as every other send: the Sent log, the plan's quota, idempotency keys. The Mailboxes page in the dashboard lists them, and only whole-team members can make one, because a mailbox key reads the team's mail.

There's no count on any plan. An idle mailbox is one row in a database, and what costs anything (the mail, its storage, a held request) grows with use. The free plan can make 10 new mailboxes a day, which keeps the shared domain off the disposable-address lists, and paid plans have no limit at all.

Get started

From a terminal, with the latest postboi:

terminal
# make a mailbox and keep its key in the environment
eval "$(npx postboi mailbox new --env)"

# print each mail as it arrives, with who is talking
npx postboi mailbox watch

# wait for a one-time code, then print just the code
CODE=$(npx postboi mailbox wait --code)

Or point your agent at the domain and nothing else. curl agentboi.email prints a card of the commands that matter, and agentboi.email/llms.txt is the whole API in one page. The docs are at Agent mailboxes, Tempboi and Email for agents, which walks an agent through setting up sending with nobody signing in at all.

POSTBOI · CONSIDER IT DELIVERED · POSTBOI · CONSIDER IT DELIVERED · The Postboi mascot

Put the boi to work.

One command between you and your first delivered message.

Start sending, free

NO CARD · NO DNS · 3,000 EMAILS A MONTH, FOREVER