Modern email can be built from borrowed parts
web
activitypub
python
:quality(85)/https://andros.dev/media/thumbnails/kristina-tripkovic-8Zs5H6CnYJo-unsplash.jpg)
Let's design the successor to email on top of HTTP, fixing the design flaws SMTP has been dragging along for 40 years. Piece by piece. The goal is not to replace the current mail system, heaven forbid!, but to learn, have fun and discover current technologies to swap out every element: sending, receiving, gateway, keys, and so on. The system will never talk to Gmail or any classic email provider: it only talks to itself. The only thing we are going to keep is the shape of the addresses, user@domain. Everything else gets reinvented.
Since it is a mail system over HTTP, a good name for the protocol would be HMTP: Hypertext Mail Transfer Protocol (the S for Simple in SMTP gives up its seat to the H for HTTP).
Now it's time to prepare the tech stack: HTTP, TLS, WebFinger, ActivityPub, Webmention, Ed25519, HPKE, sigchains, etc.
The bill of materials
This design doesn't invent a single technology: everything already exists.
Every piece we are going to use is standardized, deployed and battle-tested at scale by millions of people; the only new thing is the assembly.
Choosing HTTP is not just pragmatism. It solves, right out of the gate, several things any new protocol would have to build at some point:
TLS, virtual hosting and SNI: for free.
Status codes: the catalog a mail protocol needs already exists in HTTP. 202 Accepted (queued for delivery), 429 Too Many Requests + Retry-After (rate control), 404/410 (mailbox unknown/gone), 3xx (mailbox moved), 413 (too large). Even paid anti-spam has had a code reserved since 1997: 402 Payment Required.
Existing infrastructure: proxies, load balancers, Nginx, libraries in every language. The protocol stops being a new server and becomes a convention over HTTP, like Webmention or Micropub.
Let's move to the next level: how do we discover a user, verify their identity, deliver the message, sign it and encrypt it, all over HTTP?
1. Discovery and delegation (a better MX)
Delegation is solved with a static document:
The inbox can live on another host: that is the MX record, but without touching DNS. A static blog on GitHub Pages can delegate its mail to a provider by serving a JSON file. And it allows something MX never did: per-user delegation (each mailbox of a domain on a different provider). We wouldn't even need to invent the route: WebFinger (RFC 7033) does exactly this, and Mastodon has already proven it scales.
2. Identity that survives key rotation
Identity cannot be a key (they get lost, they expire); it has to be something that keys back. A proposal with two anchors:
Cryptographic continuity: the discovery document publishes the current key and a chain of rotations, where each new key is signed by the previous one. Anyone who knew you with key N can verify key N+1 without trusting anybody. It's a sigchain, what ATProto does with DIDs or what Keybase used to do.
Domain control as a fallback: if you lose the key without a signed rotation (your laptop gets stolen), the domain declares a new key without a chain, with a mandatory announcement period (say 30 days) during which the servers that knew you show the warning "identity re-anchored by domain, not by signature".
However, domain-anchored identity has its own Achilles heel: a domain is not owned, it is rented. If you stop paying, it expires and someone registers it, the new owner publishes their keys in your .well-known and from that moment on they receive your mail and sign as you. Nobody can tell the legitimate heir from the squatter. It's the problem ATProto tries to solve by separating identity from the domain with DIDs, at the price of another piece of infrastructure. We know it's a real problem with a known solution. Now let's move on.
3. Delivery with a queue (store-and-forward)
Delivery is a POST to the recipient's inbox:
The key is not the request, it's who makes it. Your client doesn't deliver directly to the recipient, but to your own server (an authenticated POST to your outbox), and it is your server that queues, retries with exponential backoff and honors Retry-After. It's admitting that SMTP's MUA/MSA/MTA separation was right. One of email's quiet strokes of genius is that if the destination server is down, your server retries for days and you forget about it.
But let's add an improvement SMTP never had. Every message carries an ID that is the hash of its content, so retries are idempotent. The receiving server deduplicates by ID and the classic "duplicate email because the ACK failed" disappears by construction.
The full cycle of a delivery, with the destination node down on the first attempt:
Notice that the diagram contains the entire protocol: the two GETs to .well-known are discovery and verification, the POST is the delivery, and the queue lives where it should, on the sender's server.
4. Signing and encrypting with the keys we already have
The message is a signed object, not loose text:
This buys us a lot of things:
Authenticity at rest: stored mail carries its cryptographic proof. A forward keeps the original signature. Forging the sender becomes impossible even through chains of forwards.
Sender verification without DKIM: the receiving server GETs the .well-known of the from domain and checks that the key signs. It's the same move as Webmention verification. The proof is fetched at the source.
E2E: the discovery document publishes encryption keys (X25519); the body is sealed with HPKE. The envelope (from, to, id, date) stays visible for routing and filtering; the content, only for the recipient.
Threads: in_reply_to and references by content hash. Conversations reconstructed without heuristics.
Attachments: outside the message. An attachment is {hash, url, size} pointing to the sender's server; the receiver downloads it on demand and their server can mirror it. No more base64 bloating mailboxes.
5. Layered anti-spam
Like in any system, this is a complex problem that needs several layers of defense. With HMTP we could start with three:
Identity cost: signing as ana@example.com requires serving the key document at example.com. Identity is anchored to a domain, and domains cost money. It's the sybil cost that self-signed identities don't have. However, a domain gives you infinite subdomains and mailboxes, so the cost slows down the mass creation of independent identities, not of addresses. We work at the root level.
First-contact consent: an unknown sender doesn't get into the mailbox; they land in a "requests" box with their first message visible (like Signal's message requests). You accept, and the thread opens forever. A stranger can knock on your door, which is the essential property of mail, but they can't fill up your living room.
Optional postage for strangers: the server can answer a first contact with a 402. Configurable per mailbox; the cost of cold spamming stops being zero.
6. Reading gets specified too
Email standardized sending (SMTP) and reading (IMAP/POP) as separate worlds. We don't need to invent anything: reading, synchronizing and searching mailboxes over JSON/HTTP is already solved and standardized by the IETF. It's called JMAP. HMTP would define delivery; reading is JMAP with one new object type. Push to the client with SSE or WebPush. The full cycle (send, deliver, read, sync) stays on HTTP.
The conclusion
Every problem of email has a solution deployed and working: discovery in Mastodon, delivery in ActivityPub, verification in the IndieWeb, rotatable identity in Bluesky, reading in Fastmail, consent in Signal. An upgrade to email already exists, but nobody has assembled it.
We solve many problems with elegant, modern solutions:
Reputation and social permission (SPF, DKIM, DMARC, reverse PTR, blocklists, months of warming up an IP): replaced by cryptographic verification on every message, one signature and one GET to the sender's .well-known. A property that can be verified doesn't need reputation.
Sender spoofing: impossible by construction. The receiver checks the signature against the key published on the from domain, and the signature travels with the message even when forwarded.
Mail delegation (the MX record): a static document in .well-known, with per-user delegation and no DNS changes.
Content privacy (the PGP nobody ever got around to configuring): end-to-end encryption by default; the receiving server stores a body it cannot read.
Spam (statistical filters after the fact): first-contact consent (strangers knock on the door, they don't walk into the mailbox), costly identity anchored to a domain, and optional postage with 402.
Duplicates on retry: the message id is the hash of its content, so retries are idempotent and the receiver deduplicates by construction.
Threads rebuilt with heuristics: in_reply_to points to the hash of the parent message; the conversation is a verifiable graph.
Light mailboxes: attachments by reference (hash + URL), downloaded on demand.
Simple, familiar infrastructure: everything travels over standard HTTPS, behind the same Nginx and the same certificate that already serve your website.
Key rotation (DKIM's operational nightmare): one command. Since nobody pins your key, the new one is trusted instantly and the stolen one becomes useless just as fast.
Talk is cheap, so I implemented a working prototype in Python: github.com/tanrax/hmtp. All in a single file. The prototype covers the full transport: signed delivery, verification at the source, end-to-end encryption, first-contact consent, deduplication, threads, a queue with exponential backoff and key rotation in one command. It deliberately leaves out the pieces at the periphery: the chain of signed rotations (receivers fetch your key live on every delivery instead of pinning it, so the chain only starts paying off once nodes cache keys), the 402 postage, attachments by reference and JMAP reading. The README has the quickstart (your first message in two minutes, mailing yourself), a demo of two nodes exchanging encrypted mail on your machine, and the full production guide, from DNS to systemd. Remember it's a design experiment with unaudited cryptography; don't use it for secrets anyone depends on. Although it could be a lightweight communication system for whatever ecosystem comes to your mind.
I hope you enjoyed the ride. And if you ever send your first HMTP message, I'd love to hear about it.
The bill of materials
- Discovery and delegation (a better MX)
1. Discovery and delegation (a better MX)
- Identity that survives key rotation
2. Identity that survives key rotation
- Delivery with a queue (store-and-forward)
3. Delivery with a queue (store-and-forward)
- Signing and encrypting with the keys we already have
4. Signing and encrypting with the keys we already have
- Layered anti-spam
- Reading gets specified too
The conclusion
This work is under a Attribution-NonCommercial-NoDerivatives 4.0 International license.
Attribution-NonCommercial-NoDerivatives 4.0 International license.
:quality(85)/https://andros.dev/static/img/components/license/by-nc-nd.png)
Do you want to read what I never publish?
I send what does not fit in the articles: full configurations, messy notes and whatever I am testing right now. No spam, no sales funnels.
Send an email to newsletter@andros.dev with the subject SUBSCRIBE and you are in.
To unsubscribe, send UNSUBSCRIBE to the same address.
Will you buy me a coffee?
This is how I keep writing without ads or paywalls.

Comments
There are no comments yet.
news.ycombinator.com
July 27, 2026
lemmy.bestiver.se
July 27, 2026