Skip to content

MailFlow v4.4.0

Latest

Choose a tag to compare

@maathimself maathimself released this 12 Nov 15:09
· 2 commits to main since this release

MailFlow v4.4.0

MailFlow now runs in fewer containers: Redis is gone, and the frontend serves HTTPS itself, with Let's Encrypt built in. This release also fixes three security issues in two-factor sign-in, so please update. Thanks to @orshih6, @itguy4321 and @pass2016298746-lgtm for reporting them responsibly. Thanks also to @dcoffin88 for the composer toolbar, @Monkey7539 for the search scope note and the AI sign-off fix, @SabyaXSachi for the Android SSO fix, @Azathoth88 for FIRST_USER_REGISTRATION, @clarjon1 for the menu report, @waxsocratic4 for the Proton Mail Bridge report, and @Raed-Alghamdi for EASBridge.

Security

Two-factor sign-in had three weaknesses, in every release up to 4.3.1. Details are in the advisories:

  • An emailed code could replace the authenticator app (GHSA-v67x-j275-jpxj, GHSA-mwj9-qc6h-h734). An account with an authenticator app could finish signing in with a code sent to its recovery email. Anyone who controlled that mailbox could reset the password and get past two-factor. Each sign-in now accepts only the second factor it requires.
  • Single sign-on skipped MailFlow's two-factor (GHSA-48gw-3xw6-9v67). Signing in through SSO never asked for the user's authenticator code, and ignored the admin's "2FA required" setting. It now takes the same second step as password sign-in.

Related changes:

  • Changing the recovery email needs confirmation: your password, your authenticator code, or a fresh SSO sign-in. The old address is told about the change.
  • If two-factor is required and the sign-in code can't be emailed, the sign-in now waits for a code. Before, it let whoever had the password set up their own authenticator. An admin can issue a code from the server (see Upgrading).
  • SSO users with an authenticator confirm API tokens and identity links with a fresh SSO sign-in plus their code.

New

  • Fewer containers. Sessions and short-lived keys moved from Redis to Postgres. One Caddy-based frontend container serves the app and HTTPS (TLS_MODE: self-signed, the default; letsencrypt; or off behind your own proxy). It replaces nginx and the separate Caddy container of the old https profile.
  • Mark as spam on phones. Select messages, then use the ⋯ button on the selection bar to mark them as spam, or as not spam inside your spam folder.
  • "Provider enforces MFA" in Settings → Single Sign-On, per provider. For an identity provider that already requires a second factor, it skips MailFlow's own step for that provider's sign-ins.
  • Sign-in recovery from the server: sign-in-code issues a one-time code, and disable-2fa turns off a lost authenticator, for when no admin can get in through the web.
  • ActiveSync-only Exchange is documented, through the separate EASBridge project (#604).
  • FIRST_USER_REGISTRATION=false (#773, #729). For servers whose admins come from single sign-on: the first account to register is an ordinary one instead of the admin, and registration starts closed. Without it, the first account becomes admin as before.

Fixes

  • The composer toolbar stays on one row and puts the rest behind its + button, AI Assist included (#686).
  • A search tells you it covers only the open folder, and offers to search all folders (#735).
  • The AI writing actions no longer add a sign-off of their own (#734).
  • Right-click menus and dialogs fit the window at larger font sizes and scroll to their last item (#777). In the GTD lists, the menu now opens at the pointer at those sizes too.
  • The message list header moves its controls to a second row when they don't fit beside the folder name (#766), without leaving one control alone on that row.
  • Proton Mail Bridge accounts get their message previews indexed instead of retrying one batch forever (#768).
  • On Android, an SSO re-confirmation that fails returns to the app with the error instead of the sign-in page (#772, #753).

Upgrading

Everyone is signed out once after upgrading, as sessions move to Postgres. Migrations 0076 and 0077 run automatically.

Pre-built images (Option A): download the new docker-compose.ghcr.yml, then:

docker compose pull
docker compose up -d --remove-orphans

--remove-orphans removes the Redis container, and the old Caddy container if you used the https profile. Without it, that old container holds ports 80 and 443 and the frontend can't start. Then remove the Redis volume, which nothing uses any more: docker volume rm <project>_redis_data (find the name with docker volume ls).

Built from source (Option B): git pull, then docker compose up -d --build --remove-orphans, and remove the Redis volume as above.

If you used the https profile (--profile https or -f docker-compose.https.yml): before updating, add TLS_MODE=letsencrypt to .env next to DOMAIN and ACME_EMAIL, and remove TRUST_PROXY_HOPS=2 if you set it for that profile. Without TLS_MODE=letsencrypt the frontend starts with a self-signed certificate. Browsers that have visited your site refuse that certificate because of HSTS, so the site is unreachable. Keeping -f docker-compose.https.yml in your commands also works, since it sets the same for you.

A custom nginx.conf mount and NGINX_BASE no longer apply, because the frontend runs Caddy. FRONTEND_BASE replaces NGINX_BASE. NGINX_TLS=off still works, and so does a certificate mounted at the old /etc/nginx/ssl path.

Two-factor and single sign-on: after the update, SSO users who turned on an authenticator in MailFlow are asked for its code. If password sign-in is on and the admin requires 2FA, SSO users without one are asked for an emailed code, or to set up an authenticator. If your identity provider already enforces MFA, turn on Provider enforces MFA for it in Settings → Single Sign-On. While password sign-in is off, the 2FA requirement does not apply to SSO, as before. A user's own authenticator still does.

Sign-in recovery, if no admin can sign in through the web:

docker compose exec backend node src/cli/authRecovery.js sign-in-code <username>
docker compose exec backend node src/cli/authRecovery.js disable-2fa <username>

sign-in-code prints a one-time code for the "Check your email" sign-in step, valid for 10 minutes. disable-2fa turns off a user's authenticator app. Both are written to the security log.

Known limitation: on an SSO-only server (password sign-in off), changing the recovery email needs an SSO confirmation. Providers that send no auth_time, such as Entra ID without that optional claim, can't confirm. API tokens already work this way.

Going back to 4.3.1: use the 4.3.1 compose files, which include Redis again. Everyone is signed out once more.

ghcr.io/maathimself/mailflow-backend:latest points at 4.4.0, published for amd64 and arm64.


MailFlow is built and maintained by one person. If it's useful to you, you can support it on GitHub Sponsors or Ko-fi. Using MailFlow in a business? A business license supports development too. Thank you to everyone who already does.