Giving every AI agent its own mailbox: the architecture behind a self-hosted MCP mail server A developer built openagent.email, a self-hosted mail server that gives each AI agent its own mailbox on a user-owned domain. The architecture uses a single catch-all mailbox with logical identities, filtering mail by exact recipient header matching to prevent cross-agent access. The stack runs as two containers, idles at ~190 MB RAM, and includes 268 automated tests. My agents keep needing to read email. Signup codes, verification links, password resets: every automated workflow that touches a third-party service eventually hits an inbox. There are hosted APIs for this, and they work fine until you think about what's actually in those emails. In a signup flow, the message with the verification code is the account credential. Every one of them passes through someone else's servers. So I built openagent.email https://openagent.email : a self-hosted mail server where every agent gets its own mailbox on a domain you own. It runs as two containers, a catch-all Postfix/Dovecot mailbox docker-mailserver https://github.com/docker-mailserver/docker-mailserver and a small Node/Hono API, with an MCP server on top so any MCP client Claude Code, Cursor, Windsurf... can use it. The whole stack idles at about 190 MB of RAM on my production instance, and it has 268 automated tests. This post is about the design decisions that make it work, because most of them are not the obvious ones. The naive design is one mailbox per agent. Provisioning mailboxes in Dovecot per agent is slow, stateful, and pointless. Agents don't need IMAP folders; they need an address and a way to read what lands on it. So there's exactly one real mailbox: a catch-all. Postfix aliases @yourdomain to a single account, so anything@yourdomain delivers to the same place. An "identity" is just a logical address plus a token. Creating one is a POST that takes milliseconds and touches no mail server config. This makes identity creation free, which changes how you use the system. Every test run, every signup flow, every throwaway experiment gets a fresh address. Identities are cheap enough to be single-use. But it creates the real problem: if all mail lands in one mailbox, how does an identity see only its own mail? When the API reads mail for an identity, it filters the catch-all by recipient headers: the envelope To / Cc / Bcc plus the Delivered-To header that Postfix stamps on every message. The match is exact string equality on the full address. Never a substring. That sounds paranoid until you look at what substring matching does. Say agent A owns k7d2@d and agent B owns fox-k7d2@d . A substring match lets A read B's mail. Worse: Postfix stamps Delivered-To: agent@d the catch-all account on every message, so a liberal match on that header means reading the entire mailbox. The address comparison is the only thing standing between "your mail" and "everyone's mail", so it can't be approximate. A few smaller traps fell out of the same code path: Name