Quick Start
MailFlow runs entirely in Docker. You need Docker Engine 24+ and Docker Compose v2 installed on your server. No other dependencies are required.
-
1
Download the compose file and default config
These two commands fetch the latest pre-built compose file and a template
.env.bashcurl -o docker-compose.yml https://raw.githubusercontent.com/maathimself/mailflow/main/docker-compose.ghcr.yml curl -o .env https://raw.githubusercontent.com/maathimself/mailflow/main/.env.example -
2
Generate secrets and configure
.envOpen
.envin your editor. Four fields are required:bash# Generate secrets (run once for each) openssl rand -hex 32 # → SESSION_SECRET openssl rand -hex 16 # → DB_PASSWORD openssl rand -hex 32 # → ENCRYPTION_KEY # Then edit .env — minimum required fields: APP_URL=https://mail.example.com SESSION_SECRET=<output from above> DB_PASSWORD=<output from above> ENCRYPTION_KEY=<output from above> -
3
Start the containers
bashdocker compose up -d -
4
Create your admin account
Open
https://your-domain.comin a browser. The first account you register becomes the admin. After registering, go to Settings → Users to close registration or invite others. -
5
Add your email accounts
Go to Settings → Accounts → Add Account. Choose a preset (Gmail, iCloud) or enter custom IMAP/SMTP details. See Email Providers below for per-provider instructions.
Option A — Pre-built Images
The recommended installation method. Docker pulls pre-built images directly from GitHub Container Registry — no cloning or building required. MailFlow exposes HTTPS on port 443 and HTTP on port 80 by default. Choose the deployment scenario that fits your setup:
Out of the box, MailFlow binds to port 443 (HTTPS with a self-signed certificate) and port 80 (HTTP). Both serve the full application. Port 80 is useful for local access without the browser's TLS warning, or as a target for an upstream reverse proxy.
Browser ─── HTTPS:443 ──► nginx (self-signed TLS) ─┬─► Node.js API
└── HTTP:80 ──► nginx (plain HTTP) └─► WebSocket
│
┌──────┴──────┐
▼ ▼
PostgreSQL Redis
The ports are configurable in .env:
APP_PORT=443 # HTTPS port (self-signed cert)
APP_HTTP_PORT=80 # HTTP port (no TLS)
Your browser will show a security warning on first visit. Click "Advanced → Proceed" to accept the self-signed cert, or use the Let's Encrypt profile for a trusted certificate.
If you already run Nginx, Caddy, or Traefik as a reverse proxy on your server, point it at MailFlow's HTTP port (80). Your proxy handles TLS termination; MailFlow's session cookies are secured correctly via the X-Forwarded-Proto: https header.
Internet ──HTTPS──► Your Proxy ──────────────── HTTP:80 ──► nginx ──► backend
(Nginx / Caddy / Traefik)
X-Forwarded-Proto: https ◄── required for Secure cookies
Without this header, MailFlow treats the connection as plain HTTP and session cookies will not carry the Secure flag — users may not be able to stay logged in over HTTPS.
Reverse proxy configuration examples
server {
listen 443 ssl;
server_name mail.example.com;
ssl_certificate /path/to/cert.pem;
ssl_certificate_key /path/to/key.pem;
location / {
proxy_pass http://localhost:80;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-Proto https;
proxy_set_header X-Real-IP $remote_addr;
# WebSocket support
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
}
mail.example.com {
reverse_proxy localhost:80
# Caddy automatically sets X-Forwarded-Proto and handles TLS
}
Caddy adds the X-Forwarded-For and X-Forwarded-Proto headers by default — no extra configuration needed.
# Add to the frontend service in docker-compose.yml
labels:
- "traefik.enable=true"
- "traefik.http.routers.mailflow.rule=Host(`mail.example.com`)"
- "traefik.http.routers.mailflow.entrypoints=websecure"
- "traefik.http.routers.mailflow.tls.certresolver=letsencrypt"
- "traefik.http.services.mailflow.loadbalancer.server.port=80"
- "traefik.http.middlewares.mailflow-proto.headers.customrequestheaders.X-Forwarded-Proto=https"
- "traefik.http.routers.mailflow.middlewares=mailflow-proto"
The --profile https flag adds a Caddy container that automatically obtains and renews a trusted TLS certificate from Let's Encrypt. This requires a public domain with DNS pointing at your server and ports 80/443 open to the internet.
Internet ──HTTPS──► Caddy :443 ──► nginx :443 (internal, TLS) ──► backend
(auto Let's Encrypt TLS)
-
1
Download the HTTPS overlay and set your domain in
.envYou need one extra file alongside the
docker-compose.ymldownloaded in step 1 above:bashcurl -o docker-compose.https.yml https://raw.githubusercontent.com/maathimself/mailflow/main/docker-compose.https.ymlThen add these lines to your
.env:.envDOMAIN=mail.example.com ACME_EMAIL=you@example.com APP_URL=https://mail.example.com -
2
Start with the HTTPS overlay
bashdocker compose -f docker-compose.yml -f docker-compose.https.yml --profile https up -d
Caddy renews certificates automatically before they expire. No cron jobs or manual steps required.
Option B — Build from Source
Clone the repository and build the Docker images locally. Useful if you want to modify MailFlow or follow the development branch.
git clone https://github.com/maathimself/mailflow.git mailflow
cd mailflow
cp .env.example .env
# edit .env with your secrets (see Step 2 above), then:
docker compose up -d --build
The first build takes 2–3 minutes. For Let's Encrypt from source:
docker compose -f docker-compose.yml -f docker-compose.https.yml --profile https up -d --build
Email Provider Setup
MailFlow supports any standard IMAP/SMTP server. Provider-specific instructions are below.
Requires an App Password — your regular Google password will not work.
- Enable 2-Step Verification on your Google account
- Go to myaccount.google.com/apppasswords
- Create a new App Password, name it "MailFlow"
- Copy the 16-character password into MailFlow
| IMAP Host | imap.gmail.com |
| IMAP Port | 993 |
| SMTP Host | smtp.gmail.com |
| SMTP Port | 587 |
| Username | your Gmail address |
Requires an App-Specific Password from Apple ID settings.
- Go to appleid.apple.com
- Sign-In and Security → App-Specific Passwords
- Generate a password, name it "MailFlow"
| IMAP Host | imap.mail.me.com |
| IMAP Port | 993 |
| SMTP Host | smtp.mail.me.com |
| SMTP Port | 587 |
| Username | you@icloud.com |
Work and school accounts typically require OAuth2 (modern auth). Personal Outlook accounts can use IMAP with an app password.
- In MailFlow: Settings → Integrations → Microsoft 365
- Follow the Azure App Registration instructions shown there
- Save the client ID and secret, then click Connect Microsoft account
Any standard IMAP/SMTP server works. Use these port conventions:
| IMAP TLS | Port 993 |
| SMTP STARTTLS | Port 587 |
| SMTP TLS | Port 465 |
Enter your credentials exactly as you would in any desktop mail client.
Environment Variables
All configuration lives in .env. The four required fields must be set before starting; everything else is optional.
| Variable | Default | Required | Description |
|---|---|---|---|
| APP_URL | — | Required | Full URL users see in the browser, e.g. https://mail.example.com. Used in invite emails and CORS. |
| SESSION_SECRET | — | Required | Secret for signing session cookies. Generate with openssl rand -hex 32. Must be 32+ characters. |
| DB_PASSWORD | — | Required | PostgreSQL database password. Generate with openssl rand -hex 16. |
| ENCRYPTION_KEY | — | Required | 32-byte key for encrypting stored email credentials. Generate with openssl rand -hex 32. Changing this makes all stored credentials unreadable. |
| APP_PORT | 443 | Optional | Host port for the HTTPS listener (self-signed TLS). |
| APP_HTTP_PORT | 80 | Optional | Host port for the HTTP listener. Use this as the target for an upstream reverse proxy. |
| VAPID_PUBLIC_KEY | — | Optional | Web Push public key. Generate with npx web-push generate-vapid-keys. Required for push notifications. |
| VAPID_PRIVATE_KEY | — | Optional | Web Push private key (keep secret). |
| VAPID_SUBJECT | — | Optional | Contact for the push service, e.g. mailto:admin@example.com or your APP_URL. |
| DOMAIN | — | Optional | Your public domain name. Required only when using --profile https (Let's Encrypt). |
| ACME_EMAIL | — | Optional | Email for Let's Encrypt certificate notifications. Required only with --profile https. |
| MAILFLOW_VERSION | latest | Optional | Pin a specific image version instead of latest, e.g. 1.0.0. |
User Management
The first account registered on a fresh installation automatically becomes the admin. The admin has access to the full Settings panel including user management, system email, and integrations.
Closing registration
After creating your account, close open registration so no one else can self-register: go to Settings → Users → Registration and disable it. Use the invite system to onboard additional users.
Inviting users
From Settings → Users, click Invite User. You can either copy an invite link to send manually, or have MailFlow send an invitation email automatically (requires a system email account to be configured in Settings → Integrations).
Two-factor authentication (TOTP)
Each user can enable TOTP-based 2FA from Settings → Security. Use any authenticator app (Google Authenticator, Authy, 1Password, etc.). Admin accounts should always have 2FA enabled.
SSO / OIDC
MailFlow supports single sign-on via any OpenID Connect provider (Authelia, Keycloak, Authentik, etc.). Configure in Settings → Integrations → SSO / OIDC. Once configured, users can log in with their SSO credentials instead of a local password.
Backups & Restore
MailFlow's state lives entirely in the PostgreSQL database (messages, accounts, users, settings). Back it up regularly — a daily cron job is recommended for production deployments.
Backup
docker exec mailflow-postgres pg_dump -U mailflow mailflow \
> mailflow-$(date +%Y%m%d).sql
Restore
Run docker compose stop backend before restoring to prevent active connections from interfering.
cat mailflow-YYYYMMDD.sql | \
docker exec -i mailflow-postgres psql -U mailflow -d mailflow
Updates
Database migrations run automatically on startup — no manual steps required when updating.
Pre-built images (Option A)
docker compose pull
docker compose up -d
Build from source (Option B)
git pull
docker compose up -d --build
To avoid automatic upgrades, add MAILFLOW_VERSION=1.0.0 to your .env. Remove it to return to latest.
Command Reference
# View all container logs (live)
docker compose logs -f
# View backend logs only
docker compose logs -f backend
# Restart all containers
docker compose restart
# Stop all containers (data preserved)
docker compose down
# Stop and delete all data volumes (destructive — no undo)
docker compose down -v
# Check container health
docker compose ps
Security Hardening
MailFlow ships with sensible defaults. Follow these steps for a production-hardened deployment:
- Close open registration — go to Settings → Users and disable open registration as soon as your admin account is created.
- Enable TOTP 2FA — especially for the admin account (Settings → Security).
-
Use strong secrets — generate all three secrets with
openssl rand, never reuse them across installs. - Use HTTPS — either the built-in Let's Encrypt profile or your own TLS-terminating reverse proxy. Never expose MailFlow over plain HTTP on the internet.
- Protect the server — PostgreSQL and Redis are not exposed outside the Docker network by default. Don't publish their ports.
-
Back up regularly — IMAP credentials are encrypted at rest. Losing the
ENCRYPTION_KEYmeans users must re-enter passwords, so back up both the database and your.env. - Rate limiting is on by default — login and registration are limited to 10 attempts per 15 minutes per IP. No configuration needed.
- Session cookies are HttpOnly, SameSite=Lax, with a 7-day TTL. The Secure flag is set automatically when HTTPS is detected.
Adding Email Accounts
MailFlow supports an unlimited number of email accounts per user. Each account connects via IMAP for reading and SMTP for sending.
-
1
Open the account settings
Click the gear icon (Settings) → Accounts → Add Account.
-
2
Choose a preset or enter custom details
Select Gmail or iCloud for automatic server configuration, or choose Custom and enter your IMAP host, port, SMTP host, and port manually.
-
3
Enter your credentials
Your username (usually your email address) and password (App Password for Gmail/iCloud — see Email Providers). MailFlow tests the connection before saving.
-
4
Wait for the initial sync
MailFlow fetches recent messages in the background. You'll see them appear in the unified inbox within a few seconds.
Interface Overview
Sidebar
Lists all connected accounts. Expand any account to browse its folders. Click an account name to filter the message list to just that account.
Message List
Shows messages from all accounts merged by date (unified inbox), or filtered by the selected account or folder. Unread messages are highlighted.
Message View
Renders HTML and plain-text email safely. Shows full headers on request. Inline images and attachments are displayed directly in the view.
Layouts
Switch between Classic, Compact, Wide Reader, and Vertical Split layouts from Settings → Appearance. Each layout reflows without losing your reading position.
Reading Mail
| Action | How |
|---|---|
| Mark as read / unread | Click the dot indicator on a message, or use the button in the toolbar. Synced back to IMAP immediately. |
| Star a message | Click the star icon. Starred messages sync to IMAP flags (shows up in other clients too). |
| Delete a message | Click the trash icon or press Delete. Moves to Trash on the IMAP server. |
| Mark all as read | Right-click a folder → Mark all read, or use the folder context menu. |
| Select multiple | Hold Shift and click to select a range. Use the bulk action toolbar to delete or move selected messages. |
Composing Email
Click the compose button (pencil icon) or use the keyboard shortcut C to open the compose window.
- From address — automatically selected based on the account you're viewing. Change it with the From dropdown to send from any connected account.
-
Smart address parsing — paste comma-separated addresses including display names like
Smith, John <j@example.com>and they'll parse correctly. - Rich text editor — format text, add links, and insert images. Switch to plain text mode from the toolbar if preferred.
- Reply & Forward — use the Reply, Reply All, and Forward buttons in the message toolbar. Original message is quoted automatically.
- Per-account SMTP routing — each account uses its own SMTP server. Replies are automatically sent from the same account that received the original message.
Search
MailFlow's full-text search scans across all connected accounts simultaneously. Results are sorted by relevance and show the matching account and folder.
Open search
Click the search bar at the top of the message list, or press Ctrl K (or ⌘ K on Mac) from anywhere in the app.
Cross-account results
Results from all accounts appear together, labelled by account. No need to search each account individually.
Appearance
Open Settings → Appearance to customize how MailFlow looks.
| Setting | Options |
|---|---|
| Theme | Dark (default), Light, Catppuccin Mocha, Gruvbox Dark, and more |
| Layout | Classic, Compact, Comfortable, Wide Reader, Wide List, Vertical Split, Focused |
| Language | English, Français, Español, Italiano |
Notifications
MailFlow supports two types of new-mail notifications:
In-app toast notifications
Real-time WebSocket toasts appear at the bottom of the screen whenever new mail arrives — no configuration required. These work as long as you have MailFlow open in a browser tab.
Web Push (OS-level notifications)
Push notifications deliver new-mail alerts even when you don't have MailFlow open. They work on desktop browsers and on mobile when MailFlow is installed as a PWA.
-
1
Generate VAPID keys (server admin)
bashnpx web-push generate-vapid-keysCopy the output into
VAPID_PUBLIC_KEY,VAPID_PRIVATE_KEY, andVAPID_SUBJECTin.env, then restart. -
2
Enable notifications in the browser
In MailFlow, go to Settings → Notifications and click Enable Push Notifications. Your browser will ask for permission — click Allow.
-
3
Optional: Install as PWA
For mobile or desktop, install MailFlow as a Progressive Web App. In Chrome/Edge click the install icon in the address bar; in Safari use Share → Add to Home Screen. The PWA receives push notifications like a native app even when not open.