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.

View on GitHub Get started
starry_go_mail — project data MMQR-06 Rev A Sheet 06 of 06
01 Language Go · htmx Go 1.26 or newer; html/template on the server, htmx in the browser, nothing else
02 Licence MIT Offered as-is, without support or compatibility guarantees
03 Talks to IMAP · SMTP A mail server you already run, for the domains the deployment is configured to serve
04 Keeps SQLite One directory, /config: the configuration file and the database, and nothing else
05 Ships as One binary Plus mailctl; or a two-stage Docker image built on scratch, carrying a CA bundle and the two binaries
06 Status Used daily Written for a single deployment

Repository: github.com/mmqr-developer/starry_go_mail

02 · What it does

Everything a mailbox needs

01 Signs in two ways Against application accounts in the local database, or with a mailbox's own password against a mail server for a domain the deployment is configured to serve. An address whose domain is not listed cannot sign in, so a typo in a domain is never a login attempt against a stranger's server with a real password attached.
02 Mailboxes and folders Multiple mailboxes per account, with a switcher. Folders created, renamed, deleted and subscribed. Flags, move and delete.
03 Reading A message list with search, sorting and paging. HTML rendered through a sanitiser, attachments downloaded, inline images shown.
04 Writing Compose, reply, forward and send, with a rich-text or plain composer. Attachments, inline images and drafts. Contacts, learned from the Sent folder.
05 OpenPGP Sign, encrypt, decrypt and verify, as PGP/MIME (RFC 3156).
06 Two-factor sign-in TOTP, issued from the shell with 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.
07 Writing assistants Optional, backed by a local Ollama or the Claude API. One set of prompts, built once and shared by both providers, so the copy nobody was looking at cannot become the one producing the worse email.
08 Administration An admin panel behind a superuser, or no panel at all: leave the superuser empty and /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.

03 · How it is built

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.

04 · Get started

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
05 · Built with

A short list, on purpose

Go
The server and mailctl, one static binary each
HTMX
Server-driven interface, vendored and embedded
html/template
Every page, compiled into the binary
SQLite
modernc.org/sqlite, cgo-free, so the build stays static
OpenPGP
PGP/MIME, RFC 3156

Questions about the project go to developer@mmqr.ca. For anything else, start a project.