bottlemail
A Messages in a Bottle mechanic where the addressing key is an email address instead of a name — which is what makes a genuinely private inbox possible. Senders never need an account. Every letter is public with the recipient's address cryptographically hidden, and if that address ever signs in, their letters are waiting.
Retired in September 2026. The code remains available and the architecture is still the clearest example of our approach to identity and privacy.
This product has been retired
bottlemail is no longer in service. The repository is intact and the product is documented here as part of our shipping history — the cryptographic identity model below is the part worth reading.
[In the box]
What it actually does
- No account ever required to send a letter
- Recipient addresses encrypted with AES-256-GCM
- HMAC-SHA256 blind index for O(1) lookup without plaintext
- Hard 500-character notebook-page constraint
- Draw mode with seven paper styles and 16-step undo
- Multi-layer safety engine with crisis resource interception
By the numbers
- Status
- Retired (September 2026)
- Routes
- 1 page + 6 API endpoints
- Database
- libSQL — Turso in production
- Symmetric cipher
- AES-256-GCM
- Blind index
- HMAC-SHA256 + pepper
- Letter limit
- 500 characters
- Drawing payload cap
- 350 KB data URI
- OTP lifetime
- 10 minutes, 5 attempts
- Session
- 32-byte token, 7 days, HTTP-only
- Public feed projection
- 7 columns
[Features]
Inside bottlemail
Every item below is implemented in the codebase, not planned for a future release.
AES-256-GCM recipient encryption
The recipient address is encrypted as iv:tag:ciphertext. There is no code path that renders it publicly.
HMAC blind index
A peppered HMAC-SHA256 index makes inbox lookup a single indexed query without ever decrypting the address column.
Text and draw modes
Three brush sizes, three inks, 16-step undo history, DPI-aware canvas, pointer events for mouse/touch/stylus, and ink that inverts automatically on dark paper.
Seven paper styles
Plain white, manila parchment, lined notebook with a red margin, legal yellow, graph paper, vintage kraft and midnight slate — each a real background texture.
Multi-layer safety engine
Crisis keyword interception with 988 and findahelpline.com resources, leetspeak-normalised slur blocking, email/phone/SSN/card filters and profanity masking that preserves first and last letters.
Passwordless private inbox
Six-digit OTP by email with a ten-minute expiry, five-attempt lockout and a seven-day HTTP-only session cookie.
Brutalist retro aesthetic
Hard 1px borders, offset key-press shadows, Windows-style title bars, bracketed labels and a scrolling archive marquee. Tactile on purpose.
[Architecture]
How it is built.
The decisions that shaped the codebase, and what each one buys.
Identity as a cryptographic primitive
Encrypting the recipient and indexing a hash of it means the server can route a letter to an inbox it cannot read. The addressing key is the identity, and the identity never appears in plaintext.
The database cannot leak what it cannot project
The public feed query selects seven named columns. recipient_hash and recipient_encrypted are not in the projection, so no application bug can serialise them into a response.
Safety as infrastructure
A dedicated module intercepts crisis language and returns real helplines, normalises leetspeak before matching slurs, and blocks addresses and card numbers — before anything reaches the database.
One notebook page, no more
The 500-character limit is enforced server-side and mirrored client-side with a live page-fill meter. The constraint is the product: it forces the letter to be short enough to actually be sent.
[Who it is for]
Built for
- People with something unsaid to a specific person
- Anonymous senders who will never make an account
- Late-night and reflective readers
- Anyone browsing the public drift
[Non-negotiables]
The rules we held
- No registration path exists in the code at all
- The public feed query projects seven columns — the hash and ciphertext cannot leak
- Anonymous by default, private by construction
- A real safety layer, not boilerplate
[Stack]
Built with
Delivered
- Anonymous send with no registration path
- Encrypted addressing with a peppered blind index
- Public feed with all/text/draw filters and pagination
- Drawing canvas with undo and paper textures
- OTP login and private inbox
- Safety engine with crisis interception
Not done yet
Listed rather than hidden.
- Reported letters are excluded from the feed at the schema level, but no report endpoint was exposed
- There was never a monetisation path — no pricing, tier or checkout
- Retired in September 2026
[Timeline]
How it got here.
Where this came from, in order.
Design
Swap the name for an address
The whole product turns on one substitution: letters are addressed to an email rather than a name, which makes a real, private inbox possible without asking anyone to register.
Build
Crypto, then everything else
AES-256-GCM for the address, an HMAC blind index for lookup, a seven-column public projection, then the canvas, the papers and the safety engine.
Ship
Hosted at bottlemail.vercel.app
Deployed on Vercel with Turso behind it, with a three-provider email cascade so one outage does not lock anyone out.
Retirement
Taken offline
Retired in September 2026. The repository is preserved and the identity model is documented above.
[FAQ]
Common questions.
The questions we would expect to be asked.
Why was it retired?
It was retired in September 2026 rather than kept running as a public service. The code is preserved, and this page documents the identity model because it is the sharpest example of the engineering we do.
How could the inbox be private if the feed is public?
Because the server never holds the address in plaintext. It stores an encrypted value and a peppered HMAC of it. Routing uses the hash; reading requires the recipient to prove control of the address by email. The public feed selects seven columns and the hash is not one of them.
Did it make any money?
No. There was no pricing, no tier and no checkout anywhere in it — it was a portfolio project.
Can I still run it?
The repository is intact and self-contained. It needs a libSQL database, an encryption key and a pepper key, and it runs as a single Docker container or on Vercel with Turso.
The rest of the catalogue
[Get in touch]
Questions about bottlemail?
Want to know more about bottlemail — timelines, availability or how it works under the hood? Send us a note.
Taking on select projects for 2026