Webmail. One static binary, no build step.
starry_go_mail is a webmail client written in Go, htmx and html/template. It talks IMAP and SMTP to a mail server you already run, keeps its own small SQLite database, and ships as a single static binary with every template, stylesheet and script compiled into it. No JavaScript build step, no node_modules, no CDN: the one dependency the browser loads is a vendored copy of htmx, served from the binary.
/config: the configuration file and the database, and nothing else
mailctl; or a two-stage Docker image built on scratch, carrying a CA bundle and the two binaries
Repository: github.com/mmqr-developer/starry_go_mail
Everything a mailbox needs
mailctl. A sign-in throttle blocks an address after too many failures, and an account being tried from many machines at once blocks every address that took part.
/admin is unreachable, and mailctl does everything the panel does from a shell on the machine.
Deliberately not built: tags, Sieve filters, S/MIME, drag-and-drop, keyboard shortcuts and IMAP IDLE. They are absent rather than present and inert.
Small, explicit, encrypted at rest
Secrets stay encrypted
Stored mail passwords and TOTP secrets are encrypted with AES-256-GCM. The key is derived from a value in the configuration file and, optionally, a second one compiled into the binary at build time — so a copy of the configuration directory alone is not enough to decrypt them. Both keys are generated fresh per deployment, which is why the example config deliberately carries neither.
Two sanitisers, not one
Sessions are a JWT in an HttpOnly cookie. Incoming HTML is sanitised before display, and the policy applied to a stranger's markup is a different, stricter one than the policy applied to outgoing mail.
Two ways to manage accounts
Set a superuser — a name and a bcrypt hash in the config file, with no row in the database — and create accounts at /admin. Or leave it empty and use mailctl at a shell: one fewer password to look after and one fewer page facing the network.
Build, run, fill in two fields
Go 1.26 or newer, plus brotli and gzip on PATH for the asset step. build.sh is the supported way to build: a bare go build produces a working server but skips the generated stylesheet and the pre-compressed assets embedded alongside it.
git clone https://github.com/mmqr-developer/starry_go_mail.git cd starry_go_mail ./build.sh # gofmt, vet, docs check, assets, tests, then both binaries ./starry_go_mail -debug # configuration and database in ./dev_config; open :8080
The first run writes mail_client.json with freshly generated keys and nothing else, which is a server nobody can sign in to yet. Two fields make it one: email_domains, the allowlist of mail domains with the IMAP and SMTP host, port and security for each; and either a superuser_password_hash from mailctl hash, or no superuser and the accounts added from the shell.
./mailctl user add alice # prompts for a password; never taken as an argument ./mailctl totp enable alice # issue a secret and print a QR code ./mailctl blocks unblock 203.0.113.9 ./mailctl checkjson # read mail_client.json the way the server does, and say what it understood ./build.sh --docker && docker compose up -d # /config is the only volume a deployment needs
A short list, on purpose
Questions about the project go to developer@mmqr.ca. For anything else, start a project.