Architecture overview
PhoneMail is three small services behind nginx on a Raspberry Pi: a Node api that clients talk to, a Go mail service that owns everything about mail, and PostgreSQL. The web client is static files. Twilio carries SMS in both directions, and the host's Postfix connects PhoneMail to every other email provider. It follows the team's Mail Service Spec (26 Sep 2026) table-for-table.
Components
| Component | Technology | Responsibility |
|---|---|---|
| Web client | React 19, Vite 8, TypeScript; socket.io-client, qrcode | Gmail-like mail app, one-screen sign-in, /register/ portal, settings (name, language, password, aliases). Built to static files served by nginx. |
| Mobile apps | Swift/SwiftUI (iPhone), Kotlin/Jetpack Compose (Android) | WhatsApp-style chats over the same api. |
| api | Node 22, Express 4, pg, jsonwebtoken, bcryptjs, Socket.IO 4 | Sign-up and login (OTP via Twilio Verify, JOIN by SMS, password), profiles, password change, aliases, Twilio webhook, live push, SMS alerts. Passes every mail call to the mail service. Writes only users, aliases, join_codes. |
| mail service | Go 1.23, net/http, pgx v5, go-imap, go-message; SMTP in and out on the standard library | The spec's flows: deliver, reply, chats, groups, threads, pointers and flags, drafts, search. Also: SMTP in from Postfix (:2525), the outbox worker that sends mail to outside addresses, and IMAP (:1143), authenticated SMTP submission (:1587) and a password endpoint (:1180) for Roundcube. Runs the schema migrations. |
| PostgreSQL | 16 (Alpine image), ltree extension | All data. Tuned for the Pi (shared_buffers=256MB, 40 connections). |
| nginx | 1.22 on the host | TLS, static files, routes /api/, /api/socket.io/, /twilio/, /webmail/, /docs/; blocks /api/internal/. |
| Postfix + OpenDKIM | On the host | MX for phonemail.net. Asks PhoneMail whether a recipient exists before accepting (unknown addresses get 550), hands PhoneMail mail to :2525, and signs and sends PhoneMail's outgoing mail (DKIM selector mail, SPF, DMARC). |
| Roundcube | Container on the host | Webmail. A small plugin sends PhoneMail logins to PhoneMail's IMAP/SMTP/password ports. |
| Twilio | Full account, US number | Inbound JOIN texts (webhook), Verify OTPs (SMS only) and SMS alerts. |
Outside people
Anyone with an email address can write to a PhoneMail user, and PhoneMail users can write to anyone. People outside PhoneMail are stored as external users (users.external_address, no phone). They never see chats: they receive ordinary email, threaded with In-Reply-To/References, and their replies land in the same PhoneMail chat. Outgoing copies go through an outbox table; a worker sends each through Postfix, retrying with back-off and giving up on a permanent (5xx) refusal.
Why this shape
- Clients see one api. The mail service is never exposed; the api authenticates the person and forwards their id. Either side can be replaced as long as the contract holds (see API).
- One database. Each message is stored once; each person sees it through a pointer row that carries their flags. Chats, groups and threads are plain tables (see Data model).
- Static web client. No web server process on the Pi; nginx serves files.
- Everything self-hosted. Only the domain registrar and Twilio are outside. Measured idle footprint about 210 MB (api 78 MB, PostgreSQL 119 MB, mail service 10 MB) on a Pi with 8 GB.
Code layout
phonemail/
├── mail/ Go mail service (spec §6–7)
│ ├── migrations/ SQL schema (spec §5 + join_codes, outside mail)
│ ├── deliver.go send (6.1) and reply (6.2)
│ ├── read.go home list, chat, thread, message, flags, search, lookup
│ ├── groups.go create, add, remove/leave, roles
│ ├── inbound.go SMTP in from Postfix (:2525)
│ ├── outbound.go outbox worker, render.go builds the email
│ ├── imap.go IMAP for Roundcube; submission.go, password.go
│ └── *_test.go the spec's "tests to write first" and more
├── api/src/ Node api: auth, JOIN, Twilio, profile, proxy, Socket.IO
├── web/ React web client (+ e2e/ browser tests and screenshot tour)
├── android/, ios/ native apps
├── docs-site/ this documentation (Docusaurus) and openapi.yaml
├── deploy/ nginx, Postfix, Roundcube and deploy scripts
├── scripts/ build, test and staging helpers
└── docker-compose.yml postgres + mail + api