Relay Overview
The Poweur ID relay (apps/api) is the core server component of the system. It is a Go HTTP server that implements the relay side of the protocol: accepting, verifying, routing, and delivering signed messages; ingesting delivery acks; registering identities (hosted or DNS); serving identity documents over /.well-known/poweur/; and serving public/system files through a temporary storage adapter while storage v2 is built.
A relay is the home for the identities it locally hosts. It does not act as a generic open relay for unrelated parties — see the at-least-one-local rule in the API Reference.
Responsibilities
Message ingress. Accept signed, encrypted messages at POST /messages. Rate limiting, signature verification, and the at-least-one-local rule decide whether the relay accepts.
Signature verification. Verify envelopes against the sender's public key from the resolver chain (well-known first, DNS fallback), or against a cached session key / attached session_proof.
Message routing. For privacy-proxy / --via-home-relay sends where the sender is local and the recipient is remote, resolve the recipient's relay and forward the signed envelope.
Message delivery. Keep messages for local identities in a durable spool on disk until their devices collect them, and push new mail to connected devices over SSE.
Ack ingestion. Accept signed delivered_client acks at POST /acks under the same locality rules.
Identity registration.
- Hosted — no DNS token; identity under
HOSTED_DOMAINS; persist signedidentity_documentunderPOWEUR_DATA. - DNS — client-supplied provider token; relay writes
TXT+ routing records, then discards the token.
Drives, well-known + system files. Serve each identity's end-to-end encrypted drive (/drive/…), Host-routed /.well-known/poweur/… and owner-authenticated system files; see Storage v2.
Durable vs ephemeral state
The relay still holds no identity private keys. With a durable store (POWEUR_DATA or an S3 bucket) it keeps everything durable in that store and nowhere else:
| State | Purpose | Survives restart? |
|---|---|---|
Identity index (relay/identities/, mirrored to .poweur/public/id.json) | Hosted identity publication | Yes |
Drives (drives/<id>/) | Encrypted files, append logs and .poweur system files (profile, contacts, policy, devices, group rosters) | Yes |
Inbox spool and ack queue (relay/spool/) | Messages and receipts waiting for devices | Yes |
Key backups (relay/keystore/) | Wrapped enrollment keys | Yes |
| Pending contact requests | The requests tray | No |
| Rate limit counters | Per-sender + global buckets | No |
| DNS / resolve caches | Peer addresses, identity resolve TTL | No |
| Session caches | Short-lived credentials | No (clients sign in again) |
Messages, files and settings survive a restart; losing the store does not — back it up (see Self-hosting).
Discovery and DNS
- Verify a sender → resolve via well-known, then DNS TXT if needed
- Route to a recipient → identity document
relayfield and/or DNSA/CNAME - Register (DNS mode) → write TXT + A/CNAME via provider API with an ephemeral client token
- Register (hosted) → store document only; wildcard DNS already points the parent at this relay
Security properties
- No private keys at rest for identities.
- No long-lived DNS write credentials required for hosted registration; DNS tokens in DNS mode are ephemeral.
- Verification at every hop using the resolver chain.
- Payloads are end-to-end encrypted; the relay sees ciphertext plus routing metadata.
- Metadata visibility depends on direct-send vs
--via-home-relay— see Security Model.
Deployment
The relay ships as one Docker image (apps/api/Dockerfile) that also serves the web app at
/app/. It speaks HTTP and runs behind a TLS reverse proxy. You need:
- A domain, with a wildcard record if you host identities under it (
*.example.com) - TLS covering the domain and every hosted name (see TLS)
- A
POWEUR_DATAvolume for durable state - Environment variables from Configuration
Self-hosting a relay walks through all of it. The production setup
behind poweur.net (Docker Compose, Caddy, Ansible, Grafana) is in deploy/ in the repository.