Delivery & relays

How ciphertext moves through social relays: store-and-forward mailboxes, optional onion entry hops, and metadata splitting.

Phase 1: single-hop delivery

The simplest path. Alice posts directly to Bob's mailbox relay. The relay sees the mailbox tag and ciphertext size, but not who sent the message or what it says.

sequenceDiagram
  participant Alice
  participant Relay as Bobs Mailbox Relay
  participant Bob
  Alice->>Relay: POST /v1/blobs tag, expiry, ciphertext
  Relay->>Relay: validate and store opaque blob
  Note over Bob: offline...
  Bob->>Relay: GET /v1/blobs by tag
  Relay-->>Bob: ciphertext blob list
  Bob->>Bob: decrypt and check valid_until

Phase 2: two-hop onion

No single honest relay observes both the upload and the fetch. Alice sends to an entry relay, which decrypts one onion layer and forwards to the mailbox relay without learning the mailbox tag.

flowchart LR
  A[Alice] -->|POST /v1/relay<br/>onion packet| E["Entry Relay<br/>Carol"]
  E -->|decrypt layer 1<br/>POST /v1/ingest| M["Mailbox Relay<br/>Dave"]
  M -->|store blob| DB[(Blob DB)]
  B[Bob] -->|GET /v1/blobs/tag| M
Metadata split: Entry relay sees Alice's upload timing. Mailbox relay sees Bob's fetch timing and the mailbox tag. Neither sees both ends of the same message.

Phase 3: path rotation

Each message may use a different entry→mailbox relay pair from the contact's profile descriptors, reducing long-term correlation on a single route.

flowchart TB
  P["Contact delivery profile<br/>relay_descriptors"] --> R1["Route A: entry1 to mailbox1"]
  P --> R2["Route B: entry2 to mailbox2"]
  P --> R3["Route C: entry3 to mailbox3"]
  MSG1[Message seq=1] --> R1
  MSG2[Message seq=2] --> R2
  MSG3[Message seq=3] --> R3

Phase 5: direct P2P + profile fallback

When both peers are online on the same LAN, the client tries direct HTTP delivery first. If that fails within two seconds, it falls back to relay descriptors from the signed profile.