Amir Salmani

Project 01 · live · source-available

Postern

Personal email on Cloudflare’s free tier. Receiving, storage, reading and replying — on Workers, D1 and R2, with a web client I wrote.

01

What it is

live · source-available

Personal email on Cloudflare’s free tier. Mail for this domain is received, stored in my own D1 database and R2 bucket, and read through a web client I wrote — roughly 1,400 lines of vanilla JavaScript, no framework and no build step. No provider holds my archive, and at personal volume it costs nothing to run.

What it is not: an SMTP server. Resend is the MTA — it terminates SMTP inbound and outbound, and it can see what passes through. Everything after that handoff is mine: storage, the index, full-text search, the client, auth, forwarding, backups. Calling this “self-hosted email” without that sentence would be a claim rather than a fact, which is the opposite of the point.

Status
In daily use
Since
August 2026 · my only mailbox
Runs on
Workers · D1 · R2 · Resend for SMTP
Cost
€0 / month · free tier throughout
  • Constraint · 10 ms of CPU The server parses nothing

    Workers Free allows 10 ms of CPU per invocation, and parsing a multi-megabyte MIME message does not fit — a handler that exhausts its CPU loses the message. So the server stores the raw .eml and the browser parses it on read. The useful side effect: the server never handles a decoded body.

    Architecture decided by a quota, not a preference
  • Failure mode A bug here cannot cost an email

    A handler that returns without storing, forwarding or rejecting drops the mail silently, and silent loss is the worst failure a mailbox has. Every path ends in one of those three — and mail is forwarded to my existing mailbox before it is stored.

    Write-ahead, applied to a mailbox
  • Threat model Bodies are untrusted code

    Message bodies are code written by strangers, so they render in a sandboxed iframe with no allow-scripts under default-src 'none'. Remote images are read receipts, so they are blocked per message until I ask for them. Attachments are served from my bucket and never executed.

    Cloudflare Access gates the UI; the webhook proves itself by HMAC

The gaps are published too. The roadmap names what is unfinished and what is risky — the send-quota guard that does not exist yet, and a nightly backup that has never been read back. An untested backup is a belief, not a backup.

Back to the rack