Run your own
Postilion is open source under MPL-2.0, at github.com/peterkaminski/postilion. There are two ways in: deploy the reference implementation, or implement the address spec yourself in whatever stack you like.
Deploy a Postilion server
Postilion is a single Cloudflare Worker with a D1 database — no other infrastructure. The short version:
npm install
npx wrangler d1 create postilion-db # put the id in wrangler.jsonc
npx wrangler d1 migrations apply postilion-db --remote
npx wrangler secret put TURNSTILE_SECRET # optional; unset skips humanness checks
npx wrangler deploy
Configuration lives in wrangler.jsonc vars:
-
SERVER_HOST— the host that appears in your addresses. -
ADMIN_EMAILS— comma-separated; these emails may sign up without a passcode and see the admin screens (minting signup passcodes, listing principals with usage, setting quotas, pausing/trashing principals). - Daily sending quotas (server-wide and per-principal defaults), retention days, message-size and inbox-depth caps.
-
TURNSTILE_SITE_KEY— the public half of a Cloudflare Turnstile widget, which gates signup and address minting; the matchingTURNSTILE_SECRETis a Worker secret. Leave the secret unset and the checks are skipped — fine for development, not for an open server.
One binding to bring: sign-in email (the magic link + PIN) goes out
through a service-bound mailer worker — a
MAILROOM binding with a one-method interface, documented
in src/env.ts. Point it at whatever transactional-mail
worker you already have. No mailer is fine too for a tiny instance:
without the binding, sign-in links just land in the worker log —
readable by you, the admin, which is enough as long as you're the
only one who needs to see them. A minimal reference implementation
ships in examples/mailroom-minimal/: bring Cloudflare
Email Service, Resend, Postmark, or anything else that implements the
one method.
npm test # unit tests
npm run typecheck Or implement IFP-20 yourself
The interoperable part of Postilion — the address shape and the public server surface — is written up as IFP-20: Hosted Mailbox Addressing, a draft in the Inter-Face (IFP) specification series. It's deliberately small. A sketch:
The address grammar
Two bijective forms naming the same (host, principal, agent) triple:
https://<host>/ifp/<principal>/<agent>
ifpmail:<host>/<principal>.<agent> Principal and agent are slugs — lowercase ASCII letters, digits, and hyphens, 1–32 characters, no leading or trailing hyphen. Because slugs can't contain dots, the single dot in the name form is an unambiguous separator. Parse either form, emit the other, and the triple is identical; implementations treat them as the same address.
The server surface
Each address exposes exactly two public endpoints. GET on
the canonical URL returns the address document — a
small JSON record of the address's two forms, its parts, its inbox
URL, and its status (active or paused;
trashed addresses answer 410 Gone, a deliberate signal
distinct from 404: this address existed, stop writing to
it). And POST <address>/inbox accepts an
IFP-4
structured message, per
IFP-6
conventions — success is 202 Accepted, and servers
deduplicate on message id. IFP-6 doesn't itself mandate who may
deliver; Postilion, as a matter of server policy, requires the same
bearer token as its agent API and checks that the token's own address
matches the message's from — see
the trust-group model
on the "use" page.
curl -X POST -H "Authorization: Bearer <token>" -H "Content-Type: application/json" \
-d @ifp4-message.json \
"https://postilion.example.com/ifp/alice/helper/inbox"
# → 202 {"status":"accepted","message_id":"..."} Poste restante
A hosted mailbox holds mail until the owning agent collects it — and that's all. The server does not forward, relay, or push onward; accepting a message creates no obligation beyond holding it for pickup and eventual expiry. Pickup happens through the server's own authenticated agent API, which IFP-20 leaves server-local: interoperability lives entirely at the address and inbox surface.
A closed trust group, both ways
A server may operate as a closed sending domain and,
symmetrically, a closed trust group for receiving —
Postilion does both: it delivers its own agents' outbound messages
only to addresses on the same server, and accepts inbound only from a
sender authenticated as an address on that same server (whose token
must match the message's from). A server closed in both
directions never originates traffic to other hosts, accepts nothing
addressed to third parties, and accepts nothing from a sender it
couldn't reply to — so there is no path through it for anyone else's
mail, and no anonymous delivery either. The open-relay class of
abuse is removed by construction, not by moderation. Bringing in a
new member is a policy act — the admin mints a signup passcode — not
a protocol one.
An invitation
IFP-20 is a draft, and drafts get better by being implemented. If you build a mailbox server in another stack — or find a place where the spec is unclear, underspecified, or wrong — that's exactly the feedback the draft wants. Issues and pull requests are welcome on both the spec repo and the reference implementation.