Message in a Bottle
Letters to your kids, sealed until the day they're old enough
Overview
Write letters to your children that stay sealed until a specific day. A parent signs in, adds a child with a birthday and a bottle timer, and writes to them. The bottle cannot be opened until the child reaches the age set on their profile, at which point a private self-authenticating link lets them read it without needing an account.
Sealing is final by design: once a letter is sent, the author can never view, edit, or delete it. Everything downstream of that promise - encryption at rest, a server-side age gate that sends no content until it passes, a token that is itself the credential - exists to make it true rather than merely claimed.
Key features
- Encrypted at rest
- A letter's title and body are encrypted with AES-256-GCM at the application layer before they reach Postgres. The key lives outside the database, so a stolen backup or a compromised read replica reveals nothing on its own.
- Fail-closed decryption
- With no key configured, reading an encrypted letter or writing a new one fails loudly rather than silently storing or serving plaintext. A value that will not decrypt throws instead of rendering as garbage.
- Sealing is irreversible
- Once sent, a letter can never be viewed, edited, or deleted by its author. Only drafts have a page of their own - the guarantee is the point of the product, so it is enforced by routing, not by a hidden button.
- Time-locked by the child, not the letter
- Each child has a required bottle timer - an age older than they are now - and every letter to them opens on that schedule. Nothing is scheduled per letter, so a collection written over a decade arrives as one delivery.
- Self-authenticating open links
- A child opens their bottles through a private tokenised link, with no account and no sign-in. An unknown token and a child with no timer return the same response, so the page cannot be used as an oracle for guessing tokens.
- The gate runs on the server
- A locked bottle comes back with a count and a date and nothing else. There is no letter content in the browser waiting to be revealed, so the lock cannot be picked from the client.
- Drafts, rich text, and inline photos
- Letters can be saved and reworked before sealing, with bold and italic and photos placed inline. Images live in a private blob store and are served only through a route that authorises every request.
Screenshots
Your bottles: Start where the writing starts. Tap a child's face to open a letter already addressed to them, and pick up any draft you left unfinished - the sealed ones are gone from here on purpose, because you cannot reopen them.
Your kids: Set the day once, per child, and every letter you ever write them inherits it. The private link underneath is the whole delivery mechanism - hand it over when they're old enough and they need no account to read what's waiting.
Writing a letter: Say the thing while you remember it. A draft stays yours to rework for as long as you want - and the moment you seal it, it leaves your hands for good, which is the trade the whole product is built around.
Still sealed: What a child sees when they follow their link too early - the size of the collection and the date, and nothing else. The letters themselves never leave the server until the gate passes, so there is nothing here to uncover.
Washed ashore: The payoff. Every letter written across the years arrives at once, read one at a time in the order it was written - a decade of a parent thinking about you, delivered on the day you were finally old enough for it.
The child's phone, before and after: The link is made to be handed to a kid, so the phone is the real device. The same URL is a countdown for years and then, one morning, a stack of letters - nothing to install and nothing to sign into on either side of that day.
Stack
- Next.js
- TypeScript
- Prisma
- PostgreSQL
- Auth.js
- Vercel Blob
- Tailwind CSS