Relay indexes ciphertext; Inbox stays local
Scheduled9 min read By NT²
A cloud can help deliver sealed packages without becoming the place where pending shares live as readable mail. Relay indexes ciphertext. Inbox stays on the device.
Relay indexes ciphertext; Inbox stays local
Claim: delivery is not custody of the Inbox
Sharing products often collapse three jobs into one cloud surface: transport, staging, and storage.
Transport moves bytes from sender to recipient. Staging holds “something arrived; decide what to do.” Storage is where accepted material becomes durable vault state.
NT² separates those jobs on purpose.
When vault-to-vault Share uses the edge for delivery, the relay may hold an opaque ciphertext blob, a short delivery index (who addressed whom, expiry, revoke state, opaque identifiers), and enough auth to prove a Vault Key DID is allowed to upload, list, fetch, or acknowledge. It does not hold a readable vault mailbox. It does not decrypt fields. It does not become the Inbox the recipient opens in the PWA.
The Vault Inbox is a client SQLite table on the unlocked device. Transport adapters pull candidates—from the blind relay, a .nt2share file, QR / in-band paste, and related paths—normalize them, and stage encrypted Inbox envelopes locally. Rows are pending messages, not assets. Accept is the explicit act that turns a pending share into a new item under the recipient’s own envelopes. Until then, the package is staged ciphertext plus non-sensitive preview, not a silent write into the vault’s asset list.
The claim in one line: the relay indexes ciphertext for delivery; the Inbox that asks for Accept stays local.
That is the same honesty as blind replica sync applied to handoffs. Sync relays opaque replica frames without becoming a searchable cloud vault. Share relays opaque packages without becoming a server-side Inbox of readable mail.
Constraint: a “pending shares” API is a temptation
Engineers building multi-device Share usually want a server table named something like inbox_messages. It is convenient. Support can query it. Every device can poll the same rows. Analytics can count pending shares. A web console can show “what Alice sent Bob.”
That convenience is the constraint we refuse to treat as inevitable.
If the server owns the Inbox rows, the provider can read the staging layer. Even when payloads are encrypted, product pressure grows to store plaintext previews, searchable titles, sender display names, category labels, and “helpful” support dumps. A pending package is often more sensitive than a finished item: it is the moment before the recipient decided the material belongs in their records. Making that moment a first-class cloud document invites logging, indexing, and internal tooling.
If “list my pending shares” returns field-shaped JSON, the relay stopped being blind. A delivery index that includes passport numbers, note bodies, or credential usernames is not an index—it is a second vault schema. HTTPS and disk encryption do not fix an application that receives plaintext.
If Accept happens on the server, custody flipped. Accept is a local crypto and storage decision: verify, decrypt under the recipient’s keys, re-encrypt into per-object envelopes, write a new asset. That work needs the unlocked vault key. The unlocked vault key must not live on the edge. Therefore Accept cannot be a cloud procedure that “imports for you.”
If every transport dumps into one server mailbox, offline and file paths become second-class. Shares also arrive as files, QR payloads, and in-band pastes. A product that only understands “poll the cloud Inbox” will invent awkward uploads just to normalize delivery—or will leave file recipients without the same Accept UX. Transport must be pluggable; the local Inbox is the normalization layer.
Naming collision is a real hazard. An API that lists pending relay blobs for a recipient Key DID can honestly be called an “inbox” in engineering slang. The product Inbox is different: it is the UI and local store where humans review staged messages. Confusing the two words rebuilds the wrong architecture under a familiar label. In NT², the edge may expose a relay pending index. The app’s Inbox is local.
The constraint is therefore dual. Delivery may use the edge. Staging decisions must not require a provider-readable mailbox.
Design: two schemas, many transports, one Accept door
NT² keeps two deliberately boring schemas on the edge, and one richer schema on the device.
| Layer | What it understands | What it must not understand |
|---|---|---|
| Relay index + blob store | Opaque share ids, sender/recipient Key DIDs, kind, TTL, revoke, view limits, ciphertext bytes | Item fields, master password, share passphrase, decrypted Inbox envelopes |
| Vault Inbox (local SQLite) | Pending message type, status, transport kind, transport dedupe key, encrypted envelope at rest, non-sensitive preview | Automatic asset writes without Accept |
| Assets (local SQLite + BlobStore) | Structured items and attachment metadata after Accept | Silent import from relay poll |
flowchart LR
subgraph edge [Edge relay]
Idx[Delivery index]
Blob[Ciphertext blob]
Idx --- Blob
end
subgraph device [Unlocked device]
Adapters[Transport adapters]
Inbox[Vault Inbox SQLite]
Assets[Assets after Accept]
Adapters --> Inbox
Inbox -->|Accept| Assets
end
Blob -->|fetch opaque bytes| Adapters
Idx -->|list pending refs| Adapters
Relay path (vault-to-vault). The sender encrypts a Share package to the recipient’s peer relationship, signs with the sender’s Vault Key DID, and uploads ciphertext. Auth is challenge–response over Key DID—the same identity model as optional cloud sync, not an email bearer session. The edge stores the blob and a metadata row sufficient to deliver and expire. The recipient’s client, while unlocked, polls the pending index, fetches blobs it has not already ingested, and writes local Inbox rows. Acknowledgement and decline talk to the relay about delivery disposition, not about rewriting a server mailbox of readable messages.
Local envelope at rest. Once staged, the Inbox stores an envelope encrypted under the recipient’s vault AES key. Locked devices do not leave pending share plaintext sitting in a casual table. Preview fields stay non-sensitive by design: enough to recognize what arrived, not enough to recreate the package.
Other transports fill the same Inbox. A .nt2share file, a QR / deep-link payload, or an in-band paste can produce the same class of pending row through a different adapter. The UI does not care which pipe delivered the bytes. It cares that something is pending, who it claims to be from, and whether the user Accepts, declines, or lets it expire.
Feed-shaped relays follow the same blindness. Where NT² uses a feed-style pull for vault-to-vault style updates, the edge still indexes opaque objects and delivery metadata. The client still decides what becomes Inbox state and what becomes durable vault state. Blind delivery is a family of designs, not a one-off endpoint.
Premium sync replicates Inbox rows as replica data. Cross-device continuity for pending messages does not require a server Inbox table. Devices sync ciphertext envelopes and non-sensitive metadata the same way they sync other local vault facts. The relay remains a delivery aid for Mode A (and hosted ciphertext for link Share); it is not the source of truth for “what is in my Inbox.”
This design matches the product story already told for recipients: something arrived, pause, decide—see Something arrived in my Inbox and Receive Hub is not a download folder. The architecture essay is the boundary underneath that UX.
Trade-off: two inboxes in the engineer’s head
Separating relay index from Vault Inbox costs clarity in the short term.
Implementers must remember that a successful upload is not a successful receive. The sender’s outbox can show “handed to relay” while the recipient’s Inbox is still empty until a device polls and ingests. That lag is honest. Pretending the cloud already “delivered into Bob’s Inbox” would require Bob’s Inbox to be a cloud object.
Clients must dedupe. Polling a pending index twice must not create two pending rows. Transport references and local unique keys exist so Refresh is safe. That is more state than “SELECT * FROM server_inbox.”
Support cannot open a console and read pending shares. That is a product cost. It is also the point. Help text and self-serve revoke/TTL tools must work from ciphertext and metadata, not from plaintext package dumps.
Multi-device users learn that Unlock-and-Refresh matters. A sealed package waiting on the relay is not yet a local Inbox row until an unlocked replica pulls it. Premium sync then helps other unlocked devices learn about staged rows—still as replica ciphertext, still without inventing a provider mailbox.
We accept the dual model because blast radius stays narrow. A relay breach exposes opaque blobs, Key DID identifiers, and delivery metadata—not a folder of readable pending secrets. A lost laptop with a locked vault does not expose Inbox envelopes that only decrypt after unlock. A mistaken Accept is a user action on a device, not a silent server import. And Share crypto stays on the share-shaped boundary—not the master password—as argued in Share passphrase ≠ master password.
What we refuse
We refuse a server-owned Vault Inbox of readable pending mail. Delivery indexes and ciphertext blobs are allowed. A provider mailbox of staged secrets is not.
We refuse plaintext field payloads on the relay. Titles, note bodies, credential fields, and attachment bytes leave the sender encrypted. The edge stores opaque objects.
We refuse Accept-as-a-cloud-API. Accept needs the unlocked vault. Import happens on the device after the user chooses.
We refuse silent import from poll. Fetching a pending blob creates or updates an Inbox row. It does not create an asset.
We refuse to treat “inbox” API slang as the product Inbox. A pending-blob list for a Key DID is a relay index. The Inbox UI reads local SQLite.
We refuse email or SMS as Share transport. Those channels collapse identity, preview, and forwarding into provider-readable surfaces. Vault-to-vault delivery stays on Key DID auth, blind blobs, and local staging—or on user-carried files and QR when the edge is not needed.
We refuse to upload master passwords or share passphrases so the server can “help open” a package. Decrypt stays on the client. The edge moves blind bytes.
These refusals make some delivery paths slower or more explicit than a Gmail-shaped share inbox. That friction is the price of not turning pending handoffs into another cloud vault.
Close: delivery aids, local doors
Blind Share relay and local Inbox are the handoff twin of blind replica sync on the edge: the network may carry sealed objects; interpretation stays with devices that hold keys. Identity for those requests is challenge–response over a Vault Key DID, not a resettable email login. The recipient-facing claim—“sent to me” means ciphertext to you—is the same boundary in plain language.
Relay indexes ciphertext. Inbox stays local. Accept remains a door on the device.
If you want Share delivery that never becomes a provider mailbox, explore NT² Vault.
Last updated 2026-09-09
Related stories
- Revoke what you already sent
5 min read
- Replica batch under the hood — packet shapes
8 min read
- Blind replica sync on the edge
9 min read