Self-hostable email for humans and agents on Cloudflare: send, receive, search, MCP, IMAP, and webmail.
Email, for humans and agents. Postern is a self-hostable mailbox on Cloudflare: it sends and receives mail, stores every message in a searchable store, and exposes one structured API that agents and human clients (IMAP/webmail) both speak. Cloudflare Email is the default transport on each seam, never a hard dependency.
Humans and agents each send as themselves, under per-identity credentials
rather than a shared mailbox (see docs/SEND-IDENTITIES.md),
and agents speak the mailbox natively over MCP
(@skyphusion/postern-mcp) as
a first-class door, not an afterthought.
From a fresh clone, with only your own domain, you can deploy it, send a message, and receive + read it back. Outbound send needs Cloudflare Email Sending on the Workers Paid plan (USD 5/month); inbound via Email Routing is free-plan OK. See DEPLOY.md for the clean-install quickstart and inbound/smoke.mjs for the scripted v1.0 acceptance smoke (issue #25).
Six surfaces in one repo (one store, one API):
| Path | Role |
|---|---|
inbound/ |
Core Cloudflare Worker: ingest, store (D1 + R2 + Vectorize), mailbox API, send |
relay/ |
Optional Go SMTP daemon: loopback ingest, submission 587/465, BYO dispatch |
mcp/ |
MCP server for agents (@skyphusion/postern-mcp on npm) |
webmail/ |
Browser UI embedded at /webmail: read, plus compose/reply when the token is send-capable |
imap/ |
IMAP proxy (read, \Seen, delete) for Thunderbird / mutt / iOS Mail |
clients/python/ |
Stdlib HTTP client + CLI (postern-client on PyPI) |
flowchart TD subgraph transports[Transport seams] cfIn[CF Email Routing] cfOut[CF Email Sending] relay[postern-relay SMTP] end subgraph core[inbound Worker] store[(D1 + R2 + Vectorize)] api[Mailbox API + RPC] end subgraph clients[Clients] webmail[webmail] imap[imap] mcp[mcp] py[clients/python] end cfIn --> store relay --> store api --> cfOut api --> relay webmail --> api imap --> api mcp --> api py --> apiLoading
See docs/architecture.md for the full visual map (inbound/outbound sequences, client doors). docs/CONTRACT.md is the authoritative data model; docs/INTEGRATION.md covers RPC + REST caller setup.
Agents speak the structured API; humans get two doors onto the same mailbox, both clients of that API (never a second store):
webmail/): a single self-contained page (vanilla HTML/CSS/JS, no
build step) served by the worker at /webmail. Paste your API origin and
token and browse the mailbox (list, read, threads, search); compose and reply
unlock when the token is send-capable.imap/): a small Twisted server that fronts the API as IMAP
(read, the \Seen read/unread flag, and delete via a both-scoped token), so
Thunderbird / mutt / iOS Mail can open the mailbox too.Webmail adds compose/reply when the token is send-capable; IMAP adds read/unread
(\Seen) and delete via a both-scoped token. Both are API clients, never a second
store, and all sending funnels through the structured API.
An HTML email rendered in the webmail (safely, in a sandboxed iframe; no scripts, no remote trackers running):
The inbox list, a message read view, and search:
| Read a message (trust verdict + attachments) | Search the mailbox |
|---|---|
The shots above use synthetic example data. See webmail/README.md for setup and the security model (BYO-token, token in
sessionStorageonly, noinnerHTMLof message content, locked-down CSP).
Full steps in DEPLOY.md. In short:
cd inbound npx wrangler d1 create postern # paste database_id into wrangler.jsonc npx wrangler r2 bucket create postern-attachments # edit wrangler.jsonc: database_id + DEFAULT_FROM / ALLOWED_FROM_DOMAIN npx wrangler d1 execute postern --remote --file=schema.sql npx wrangler secret put POSTERN_API_TOKEN # openssl rand -hex 32 npm install && npm run deploy
Then route inbound mail to the Worker (Email Routing -> Routing Rules -> catch-all to the Worker), and run the smoke (see DEPLOY.md).
After deploy, connect agents and scripts without cloning the repo. Both packages talk to the same token-gated mailbox API; they are clients of the store, not a second copy of it.
| Package | Registry | Install | Docs |
|---|---|---|---|
| @skyphusion/postern-mcp | npm | npx -y @skyphusion/postern-mcp |
mcp/README.md |
| postern-client | PyPI | pip install postern-client |
clients/python/README.md |
Configure with your deployed origin and token:
export POSTERN_API_URL=https://postern.<your-account>.workers.dev export POSTERN_API_TOKEN=<read-scoped token>
MCP (Cursor / Claude Code): add an MCP server entry with command: npx,
args: ["-y", "@skyphusion/postern-mcp"], and the env vars above in env. Send tools
(mailbox_send, mailbox_reply) register only when POSTERN_SEND_TOKEN is also
set (opt-in; see docs/SEND-IDENTITIES.md).
Python CLI: postern ping, postern list, postern search, postern send,
and the rest; see clients/python/README.md.
Release tags: postern-mcp-v* (npm CI) and GitHub Release v* matching
clients/python/pyproject.toml (PyPI CI). See docs/INTEGRATION.md.
MailboxService RPC entrypoint (or legacy EmailService alias), tokenless.Authorization: Bearer <POSTERN_API_TOKEN>, constant-time
compared./ingest, relay /dispatch) use a separate
POSTERN_TRANSPORT_TOKEN, never the API token, so an API-token leak cannot
inject mail and vice versa.Go >= 1.22:
cd relay go mod tidy go build -o postern-relay .
Configure via env (no values are baked in): POSTERN_INGEST_URL (or the legacy
EMAIL_WORKER_URL), POSTERN_TRANSPORT_TOKEN, and DEFAULT_FROM / FROM_DOMAIN
for off-domain sender rewriting. The relay uses the envelope RCPT TO for
recipients; if a message's From is off the allowed domain it is rewritten to
DEFAULT_FROM with the original preserved as Reply-To.
No em/en-dashes in source, commits, or docs. Commits use conventional-commits
(feat(inbound): ..., fix(relay): ...).
These notes are specific to the maintainers' own deployment and are not required to run Postern. A stranger should follow DEPLOY.md instead.
The reference instance sends from skyphusion.org (and .net), both onboarded
to Email Sending, and deploys the worker to Cloudflare via CI on every green
build of main. No secrets live in the tree: POSTERN_API_TOKEN is a Worker
secret, untouched by deploy.
Self-hosters, agent builders, and mail admins who want a mailbox they own on Cloudflare: one searchable store, one API for humans and agents, webmail and IMAP included.