Webmail
The email subsystem UI half: SnappyMail served by php-fpm, with optional Matrix SSO sign-in.
Optional, off by default. ENABLE_EMAIL=true turns on the email subsystem;
this is the UI half: SnappyMail served by php-fpm
inside the Debian userland on a loopback FastCGI pool, fronted by Caddy at
webmail.${DOMAIN}. It is the front door to the mailbox (the Maddy mail server,
the other half of ENABLE_EMAIL), reached over loopback IMAP/SMTP — nothing
ever leaves the host in cleartext.
Installed by scripts/steps/86-install-webmail.sh (self-gates on
ENABLE_EMAIL).
What it gives you
- A full webmail client at
webmail.${DOMAIN}, pinned to the single mailbox domainmail.${DOMAIN}(the bundled public providers — gmail/outlook/… — are removed, so it only ever talks to your own mailbox). - The data folder lives outside the webroot (
/opt/snappymail-data, set byinclude.php) on the large volume (${DATA_DIR}/snappymail), so a userland rebuild keeps your mail config. - Optional Matrix SSO (only when
ENABLE_AUTH_GATEWAY=true): a “Sign in with OIDC” button on the login screen runs an OIDC front-door against the Matrix-auth gateway; the gateway hands back the mailbox address plus a server-managed per-user IMAP password the user never sees, and the plugin does a normal IMAP login with it. New mailboxes are JIT-provisioned on first login and get a one-time welcome email. - Optional native admin panel (
ENABLE_WEBMAIL_ADMIN=true): SnappyMail’s own admin UI, host-locked to a separatewebmail-admin.${DOMAIN}vhost that fails closed unless it arrives through Cloudflare Access.
Auth model
The auth boundary is SnappyMail’s own IMAP login (no stacked Caddy
forward_auth cookie gate — SnappyMail is a SPA and a 302-to-login gate breaks
SPAs, the Pingvin/Linkding lesson). SSO, when enabled, is offered in-app
(the OIDC button), not via a Caddy gate.
Configuration (.env)
ENABLE_EMAIL=false # the whole email subsystem (mail server + this UI)
SNAPPYMAIL_FPM_PORT=9092 # loopback php-fpm pool port
MAIL_IMAP_PORT=9143 # the Maddy IMAP listener (loopback; set in the mail-server half)
MAIL_SUBMISSION_PORT=9587 # the Maddy outbound submission listener (loopback)
ENABLE_WEBMAIL_ADMIN=false # SnappyMail native admin panel on webmail-admin.${DOMAIN}
# Advanced overrides (sensible defaults derived from DOMAIN):
# SNAPPYMAIL_OIDC_CLIENT_ID, SNAPPYMAIL_OIDC_AUTHORIZE_URL,
# SNAPPYMAIL_OIDC_TOKEN_URL, SNAPPYMAIL_OIDC_REDIRECT_URI, SNAPPYMAIL_BRAND,
# SNAPPYMAIL_WELCOME_FROM, SNAPPYMAIL_VERSION / _URL / _SHA256
The auth-gateway snappymail OIDC client
When SSO is on, the gateway needs a snappymail OIDC client whose token
response is extended with two extra fields the plugin consumes:
email=<localpart>@mail.${DOMAIN}(the mailbox address)imap_password= the server-managed per-user IMAP password, derived ashex(HMAC-SHA256(IMAP_HMAC_KEY, canonical_localpart))(64 hex chars) — the JIT mailbox provisioner creates the Maddy account with the byte-identical value, so Maddy’spass_tableverifies it (Maddy has no OAUTHBEARER). It is returned only over the loopback, client-secret-gated/tokenexchange, never to the browser.
The IMAP_HMAC_KEY is owned by the auth gateway: scripts/steps/60-install-auth-gw.sh
generates ${DATA_DIR}/auth-gw/mail-imap-secret.key (0600) and the gateway is its
only reader — so there is exactly one key, with no cross-component duplication. The
gateway’s mail extension (OIDC_MAIL_CLIENTS / imap_password() in
scripts/gateway/matrix-auth-gw.py) is inert until the install step registers the
snappymail client; both the gateway extension and the SnappyMail plugin’s
secret-handling are security-critical and maintainer-written + verified.
Upgrading
Bump SNAPPYMAIL_VERSION + regenerate SNAPPYMAIL_SHA256
(curl -fsSL "$SNAPPYMAIL_URL" | sha256sum) and re-run the install step. The
plugin and any application.ini customizations must be re-applied on upgrade
(SnappyMail can rewrite config on a fresh extract) — re-running the step does
this fail-closed.