# ────────────────────────────────────────────────────────────────────────────── # MailFlow — Environment Configuration # # 1. Copy this file: cp .env.example .env # 2. Fill in the values below # 3. Generate the TLS cert (see README) or use --profile https for Let's Encrypt # 4. Run: docker compose up -d --build # ────────────────────────────────────────────────────────────────────────────── # ── Access ──────────────────────────────────────────────────────────────────── # Port the app listens on (HTTPS). Default is 443. APP_PORT=443 # Port the app listens on (HTTP). Default is 80. # Use this when running behind a reverse proxy that terminates TLS upstream. # Your proxy should forward X-Forwarded-Proto: https so session cookies work correctly. APP_HTTP_PORT=80 # Reverse proxies between the browser and the backend, counting MailFlow's own # nginx. The backend takes each client's address from this many hops back in # X-Forwarded-For, for the login rate limit and Login Activity. Empty = 1 # (nginx only), or 2 with --profile https, where Caddy is the second proxy. # Behind your own reverse proxy, set 2, but only if clients cannot reach # MailFlow's ports except through it. Otherwise a client that connects directly # can choose the address it is rate-limited and logged under. Docker publishes # APP_PORT and APP_HTTP_PORT on every interface, and firewalls such as ufw do # not filter them, so first bind both to an address only your proxy can reach, # e.g. APP_HTTP_PORT=127.0.0.1:8080 and APP_PORT=127.0.0.1:8443 for a proxy on # the same host. TRUST_PROXY_HOPS= # Full URL users see in the browser — used in invite emails and CORS. APP_URL=https://your-domain-or-ip # ── Secrets ─────────────────────────────────────────────────────────────────── # REQUIRED — generate with: openssl rand -hex 32 SESSION_SECRET= # REQUIRED — generate with: openssl rand -hex 16 DB_PASSWORD= # REQUIRED — generate with: openssl rand -hex 32 # Used to encrypt stored email credentials (passwords, OAuth tokens) at rest. # WARNING: If this key is lost or changed, all stored credentials become unreadable # and users will need to re-enter passwords / re-authenticate OAuth accounts. ENCRYPTION_KEY= # ── Database & Redis (advanced — external servers) ──────────────────────────── # By default MailFlow runs its own bundled Postgres and Redis, and these are # already wired up. Only set these to use EXISTING servers elsewhere on your # network (e.g. on a non-standard port). If you do, stop/remove the bundled # `postgres` and `redis` services so they don't run unused. # DB_HOST=postgres # DB_PORT=5432 # DB_NAME=mailflow # DB_USER=mailflow # REDIS_URL=redis://redis:6379 # ── Storage & permissions (bind mounts / Unraid) ────────────────────────────── # By default the Postgres and Redis data live in Docker-managed named volumes. # To store them on a host path instead (e.g. an Unraid appdata share), point these # at that path, create/chown it to PUID:PGID first, then set PUID/PGID so the # containers write files as that owner. On Unraid, PUID=99 and PGID=100. # Leaving PUID/PGID unset keeps the default (root) behaviour with named volumes. # POSTGRES_DATA=/mnt/user/appdata/mailflow/postgres # REDIS_DATA=/mnt/user/appdata/mailflow/redis # PUID=99 # PGID=100 # ── Google OAuth for Gmail / Workspace accounts (optional) ─────────────────── # Lets you connect Gmail and Google Workspace mailboxes with OAuth instead of an # app password. Workspace admins can disable app passwords entirely, in which # case OAuth is the only way to reach those mailboxes. # # Setup, in Google Cloud Console → APIs & Services → Credentials: # 1. Create an OAuth 2.0 Client ID of type "Web application" # 2. Add this authorized redirect URI (must match GOOGLE_REDIRECT_URI exactly): # https://your-domain/oauth/google/callback # 3. On the OAuth consent screen, add the scope https://mail.google.com/ # # https://mail.google.com/ is a RESTRICTED scope, so consent screen setup matters: # # Internal (project owned by a Google Workspace org) # No verification needed, refresh tokens do not expire. Only accounts in that # org can connect. This is the option that works for self-hosting. # # External + Testing # Works immediately, but Google revokes refresh tokens after 7 DAYS, so every # connected account must be reconnected weekly. Fine for trying it out only. # # External + In production # Long-lived tokens, but restricted scopes require full Google verification # including a security audit. # # Consumer @gmail.com accounts cannot be covered by an Internal app. Use an app # password for those; this flow is aimed at Workspace mailboxes, where admins can # disable app passwords entirely. # # Then visit https://your-domain/oauth/google while logged in to connect a mailbox. GOOGLE_CLIENT_ID= GOOGLE_CLIENT_SECRET= GOOGLE_REDIRECT_URI=https://your-domain/oauth/google/callback # ── Web Push Notifications (optional) ──────────────────────────────────────── # Enables native OS / PWA push notifications for new mail. # Generate a key pair once with: npx web-push generate-vapid-keys # VAPID_SUBJECT must be either a mailto: address or your APP_URL. VAPID_PUBLIC_KEY= VAPID_PRIVATE_KEY= VAPID_SUBJECT=mailto:admin@yourdomain.com # ── Let's Encrypt (optional — only needed with --profile https) ─────────────── # Only used when running: docker compose --profile https up -d --build # Requires a real public domain with DNS pointing at this server. DOMAIN=mail.yourdomain.com ACME_EMAIL=you@yourdomain.com # ── Source-build tuning (advanced) ──────────────────────────────────────────── # Base image for the frontend (nginx) stage when building from source. Some # hosts with older kernels (e.g. NAS units) cannot execute binaries from the # newest Alpine package repo — `apk add` in the build dies with "killed by # signal 9". Point this at an older tag to fix that. Default: nginx:alpine. # NGINX_BASE=nginx:1.27-alpine # ── IMAP tuning ─────────────────────────────────────────────────────────────── # Max always-on IMAP connections MailFlow keeps per provider host. Each enabled # account normally holds one persistent (IDLE) connection, so many accounts on # ONE mail server can exceed the server's per-user/per-IP connection limit (e.g. # Dovecot's mail_max_userip_connections, default 10), causing sync errors and a # snippet-indexer backoff loop. Set this to your provider's limit and MailFlow # keeps that many accounts on live push and polls the rest on the sync interval. # Empty or 0 = unlimited (default; unchanged behavior). Only set it if you run # many accounts on one connection-limited server. IMAP_MAX_PERSISTENT_PER_HOST= # ── Experimental ────────────────────────────────────────────────────────────── # Div-based email renderer (replaces iframe). Set to true on a dev instance to # test before the iframe path is removed. Do not enable in production until the # full email test matrix has been validated. VITE_EMAIL_DIV_RENDER=false