Relay
A Relay That Holds Nothing: Ephemeral Delivery
6 min read
The most valuable thing a message relay can own is a database of undelivered messages. It is also the most dangerous thing it can own. So we decided the relay would not own one.
We have hidden the content, the sender, the linkage, and the type. This article takes away the last thing you would assume any relay must have: a mailbox.
1. The honeypot you get for free
Every straightforward messenger has a store-and-forward queue: when Bob is offline, his incoming messages pile up in a server-side inbox until his phone collects them. It is the natural design, and it quietly builds the single most sensitive object in the system.
A durable inbox is a standing record of who is about to hear from whom. It sits at rest, indexed and backed up, for as long as delivery is pending — and often, through logs and replicas, long after. That makes it:
- breachable — one database dump is the whole undelivered social graph,
- subpoenable — it exists, so it can be compelled,
- mineable — an honest-but-curious operator can watch it fill and drain.
Note that content encryption does not help here at all. The inbox rows can have perfectly encrypted bodies and still be a catastrophe, because the damage is in the existence and shape of the rows — recipient, size, arrival time, rate. We spent four articles removing metadata from the envelope; keeping a durable inbox would hand almost all of it back, at rest, in a queryable table.
The cleanest way to not leak a database is to not have a database.
2. Publish, subscribe, and drop
The relay is built as pure publish/subscribe with no persistence. Concretely, it runs on a message bus (NATS) in its plain core mode — not the durable "JetStream" mode that would persist anything. When a message for Bob arrives, the relay publishes it to Bob's subject (a routing channel keyed by his opaque recipient key). If one of Bob's devices is subscribed at that instant — the app is foregrounded, or freshly woken and reconnected — the bytes stream straight to it. If no device is subscribed, the publish matches nothing and the message is dropped on the floor. The relay keeps no copy, writes no row, remembers nothing.
The mental model is a live phone call versus voicemail. Voicemail records; a live call that no one picks up leaves no recording anywhere. The relay is the live call: connect at the same time and you talk; miss each other and there is simply nothing left behind to seize.
The only thing the relay reports back to the sender is a count: delivered = how many of
the recipient's devices were live and got it, right now. Zero means "nobody was home." It
is a delivery signal, not a stored state — the instant the publish returns, the relay's
involvement is over.
3. If the relay forgets, who remembers?
Someone has to hold the message until Bob actually gets it, and we just said it isn't the relay. The answer: the sender does. Durability lives entirely in Alice's outbox — a local queue on her own device.
And here is the rule that makes it correct: an outbox entry is cleared only when Bob's
device sends back an acknowledgement — not when the relay says delivered, and
certainly not when Alice merely hit send. delivered > 0 means "a device was live and the
bytes reached it," which is almost delivery but not proof the app processed and stored
it; only Bob's explicit ack means "it is safely his now." Until that ack comes back, Alice's
phone keeps the message and keeps trying.
This flips the trust model in the user's favor. The pending message lives on the device of the person who wrote it, protected by that device's own encryption, under that person's control — not in a server the person has to trust to hold their unsent thoughts.
4. The zombie-socket problem
Pure pub/sub has a nasty failure mode that is worth telling because the fix is the kind of detail that gets glossed.
On iOS, a backgrounded app's network connection does not cleanly close — it freezes. The
socket looks alive to the server for a while, so the relay counts Bob as "subscribed" and
happily publishes to a connection that will never deliver. delivered reads 1, Alice's
outbox relaxes, and the message quietly evaporates — the worst outcome, a silent loss.
The fix is a liveness ping: the relay pings each connection on a short interval (every
few seconds) and reaps any that fails to answer within a tight timeout. A frozen,
suspended app fails the ping and gets pruned, so the live-subscriber count deflates to
its truth — zero — which correctly reports "nobody home" and triggers the fallback that
actually reaches Bob: waking his phone. The whole
no-mailbox design depends on delivered being honest, and the liveness ping is what
keeps it honest.
5. Not losing messages without a mailbox
"No server storage" must not mean "lossy." The sender's outbox is engineered to survive the gap between Bob is offline and Bob is back:
- When the relay reports
delivered == 0, it triggers a push wake; the outbox then re-ships with a short retry burst timed to land in the window when the woken app (or its notification extension) reconnects and is listening. - Retries are also event-driven: Alice's own reconnection, or a presence beacon that says Bob just came online, immediately reflushes the outbox rather than waiting on a timer.
- A slow periodic backstop (every ~30 seconds) catches anything the events missed, and entries are given up only after a long horizon (~30 days) so a genuinely-gone recipient doesn't wedge the queue forever.
The result behaves like a reliable queue from the users' point of view, while the server remains a stateless pipe. The cost is real and we accept it: the sender's device does more work and spends a little more battery retrying, and a message to a long-offline recipient waits on the outbox rather than a server. We considered that a fair price for deleting the single most dangerous object in the system.
6. The one deliberate exception
There is exactly one place the relay holds bytes, and it is bounded and purposeful. To make the pull side private — so that Bob catching up on missed messages doesn't tell the relay which conversation he's reading — the relay keeps recent sealed envelopes in a short (about a minute) windowed pool and serves them through Private Information Retrieval. It is not a mailbox: it is a rolling, content-addressed window with a one-minute horizon, and it exists specifically so the read can be oblivious. That mechanism is its own article: Reading Without Being Watched.
7. The honest residual
With no durable store, the relay's footprint shrinks to the transient:
- it sees the recipient routing key and a live-subscriber count at the moment of publish,
- it handles the sealed bytes in transit (opaque, and not retained),
- and it holds the one-minute PIR window described above.
Nothing survives at rest to be dumped, compelled, or mined. The relay cannot answer "who was Bob about to hear from last Tuesday?" because it never wrote it down. That is the whole point: the strongest guarantee about a database is the one you get by not keeping it.
References & further reading
- "Publish–subscribe pattern" and NATS core vs. JetStream — the persistence choice this article turns on.
- "Forward secrecy" — the same "delete it so it can't be taken later" instinct, applied to storage instead of keys.
- Waking a Sleeping Phone — the fallback that makes drop-if-offline safe.
- Reading Without Being Watched — the one bounded pool, and why.