Executive summary
Vault is a closed-source product build for private messaging infrastructure. The target customer is not a casual chat user. It is a professional organisation where the risk is not only message content, but also who owns the infrastructure, who can be compelled to produce data, and what metadata the provider can observe.
The core shape is simple: a Go relay server, PostgreSQL, and an Android client. The Android client handles MLS through OpenMLS. The server routes ciphertext, applies storage encryption before writing to PostgreSQL, and never handles plaintext message content.
This is still an active build, not a public launch. The technical foundation is documented in detail, but the repository does not prove a commercial deployment, a completed security audit, iOS support, or public self-serve access.
The problem
Mainstream messaging apps were built for consumer-scale communication. For a law firm, medical practice, accounting office, founder, or executive team, the issue is not only whether messages are encrypted. The issue is that the communication still depends on a third-party provider, a centralised policy surface, and infrastructure the customer does not control.
The business brief frames this as a liability and ownership problem. If a provider holds metadata, account information, infrastructure control, or any recoverable data, that provider becomes part of the customer's confidentiality surface. For some professional relationships, that is exactly what the customer is trying to avoid.
Self-hosted tools partly solve the ownership issue, but they create another problem: deployment complexity. A small professional services firm usually does not want to assemble and operate a complex privacy stack from open-source components. Vault is exploring whether that can be turned into a packaged, install-and-handoff product.
The thesis
Vault's product bet is that privacy-sensitive professional teams need infrastructure ownership, not just another encrypted app. The customer hosts the system. The vendor installs it, hands over access, and does not keep operational access or customer data after handoff in the self-managed model.
Technically, the design keeps the server out of end-to-end encryption. MLS runs on the Android client through OpenMLS. The Go server only receives opaque MLS ciphertext, wraps it with AES-256-GCM at the PostgreSQL storage boundary, and removes that wrapper before outbound delivery. That storage layer protects database-at-rest data, but it is not presented as an extra end-to-end encryption layer.
The message archive design follows the same constraint. For non-ephemeral conversations, the client re-encrypts decrypted messages with a user-held archive key and uploads opaque blobs to the server. The server can store history, but it does not have the key needed to read it. Decrypted history is rendered from RAM and is not written to the phone filesystem.
What I built
Go relay server
The server is a Go service that exposes the core Vault API: registration, challenge-response auth, session creation, direct conversations, WebSocket messaging, key package upload/fetch, group APIs, media upload/fetch, archive upload/fetch, message wipe, destroy conversation, push registration, and kill switch support.
The server uses PostgreSQL for durable storage. Stored message payloads are wrapped with AES-256-GCM using a server storage key loaded from a secret file. Offline delivery is handled with message recipient rows in PostgreSQL. If a recipient is not connected over WebSocket, the message remains undelivered until the next authenticated WebSocket connection flushes the queue.
Presence is deliberately narrow. Direct-chat presence is live-only and based on the in-memory WebSocket hub. There is no persisted last_seen field in the documented v1 design.
Android client
The Android client is Kotlin + Jetpack Compose. It uses OpenMLS through a Rust JNI library, with MLS signing delegated to Android Keystore. The documented stack includes Hilt, Room, SQLCipher, OkHttp WebSocket, and Compose Material 3.
The Android README marks the following areas as done: registration and enrollment, auth and WebSocket, direct messaging for stored and ephemeral conversations, local nicknames and chat rename, direct-chat online presence, wake notifications through UnifiedPush and ntfy, message archive, FLAG_SECURE, certificate pinning, and overlay protection.
Groups are not fully done on Android. The server documentation and progress notes describe group endpoints and server-side group work, but the Android README still marks groups as pending. That matters for the build entry because the product should not be described as a finished group messaging app yet.
Archive and retention model
Vault has two conversation modes.
Non-ephemeral conversations support message history. After the client decrypts a message with MLS, it re-encrypts the plaintext with a 256-bit user archive key and uploads an opaque blob to the server. On reconnect, the client fetches archive blobs, decrypts them in RAM, renders them, and does not write decrypted content to the phone filesystem.
Ephemeral conversations are different by design. They are not archived. Messages exist in RAM only during the active chat session. Offline members can miss ephemeral messages because the database is skipped for that conversation mode.
The ADR documents real tradeoffs. If a user loses a device without backing up the archive key, history is permanently unreadable. There is no multi-device history sync in the MVP. History also depends on the server being reachable, because there is intentionally no local plaintext cache.
Self-hosting and handoff model
The business model is a licensed product with setup and per-device annual licensing. The repository describes customer-hosted deployment, where the vendor installs Vault on the customer's own server, hands over credentials, and removes access in the self-managed model.
The deploy folder includes Docker Compose and staging deployment scripts. The server README documents local development, Docker startup, health checks, staging deploy commands, and proxy configuration options. That proves deployment work exists, but it does not prove a public customer deployment.
Architecture
Android client
- Kotlin + Jetpack Compose
- OpenMLS through Rust JNI
- Android Keystore for credential signing and archive key wrapping
- Room + SQLCipher for encrypted local cryptographic state
- message plaintext rendered in RAM only
Go relay server
- registration and challenge-response auth
- WebSocket hub for live delivery and direct-chat presence
- opaque MLS ciphertext routing
- AES-256-GCM at PostgreSQL storage boundary
- offline queue and reconnect flush
- optional UnifiedPush / ntfy wake notifications
PostgreSQL
- encrypted message payloads
- recipient delivery rows
- conversations, memberships, key packages, archives, media metadata, and pending WebSocket events