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 [email protected] 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 | [email protected] |
Work / school accounts (Microsoft 365): Require OAuth2 via an Azure App Registration.
- In MailFlow: Settings → Integrations
- Follow the Azure App Registration instructions shown there — create a registration at portal.azure.com and copy your Client ID, Tenant ID, Client Secret, and Redirect URI
- Enter the values and click Save, then click Connect Microsoft account
Personal accounts (Outlook.com / Hotmail): Use the built-in device code OAuth flow — no Azure registration needed.
- Settings → Accounts → Add Account → Outlook.com / Hotmail
- MailFlow shows a short code — visit microsoft.com/devicelogin and enter it to authorise
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:[email protected] 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.7.3. |
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 and disable Open registration. 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 → Users → System Email).
Two-factor authentication
MailFlow supports three 2FA methods, configurable per user from Settings → Security:
| Method | How it works |
|---|---|
| TOTP | Time-based one-time passwords via any authenticator app (Google Authenticator, Authy, 1Password, etc.). Recommended for all users. |
| Email OTP | A one-time code sent to a recovery email address. Useful as a fallback if the authenticator app is unavailable. |
| Device trust | After completing 2FA on a trusted device, the second factor is skipped for a configurable period (7 days, 30 days, permanent, or never). Managed per-user under Settings → Security → Trusted Devices. |
Admin enforcement policy — Admins can require 2FA for all users under Settings → Security → MFA Policy. Options are off (users opt in) or required (enforced on next login). Admin accounts should always have 2FA enabled regardless of policy.
SSO / OIDC
MailFlow supports single sign-on via any OpenID Connect provider (Authelia, Keycloak, Authentik, etc.). Configure in Settings → SSO. Once configured, users can log in with their SSO credentials instead of a local password. You can also optionally disable password login entirely so that SSO is the only sign-in method.
Group claim → admin role mapping — If your identity provider includes group membership in the OIDC token, MailFlow can automatically grant the admin role to members of a specified group. In Settings → SSO, set the Group claim name (e.g. groups) and the Admin group value to the group identifier your IdP uses. Any user whose token contains that value in the specified claim will be promoted to admin on sign-in.
Integrations
Todoist
Connect a Todoist account to create tasks directly from emails. Once linked, a Todoist button appears in the message toolbar.
-
1
Link your Todoist account
Go to Settings → Apps → Todoist and click Connect Todoist. You'll be redirected to Todoist to authorise access, then returned to MailFlow.
-
2
Create a task from an email
Open any email and click the Todoist icon in the message toolbar. Choose a project, set a due date and priority, and click Add Task. The task is created in Todoist with a link back to the email subject.
Each MailFlow user connects their own Todoist account independently. The admin does not need to configure anything at the server level.
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.9.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. Right-click an account to access quick actions including Move Up / Move Down to reorder accounts. Pin frequently used folders to a Favorites section at the top of the sidebar — favorites can be renamed and reordered by dragging.
Message List
Shows messages from all accounts merged by date (unified inbox), or filtered by the selected account or folder. Unread messages are highlighted. Sender avatars show contact photos from your CardDAV-synced address book when available.
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, Comfortable, Wide Reader, Wide List, Vertical Split, and Focused layouts from Settings → Appearance → Layout. 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, or press x to toggle individual messages. Use the bulk action toolbar to mark read/unread, archive, move, or delete the selection. |
| Hover quick actions | Hover over any message row to reveal inline action buttons (archive, delete, reply). Toggle in Settings → Appearance → Layout. |
| Swipe actions (mobile) | Swipe left or right on a message to trigger configurable actions (e.g. archive, delete). Configure in Settings → Appearance → Layout. |
| Mark-as-read behavior | Configure when messages are automatically marked as read in Settings → Reading: Immediately on open, After a delay (a configurable number of seconds), or Manually — only when you click the mark-as-read button. |
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 <[email protected]>and they'll parse correctly. - Rich text editor — format text, add links, and insert images. Choose your font family group and font size directly from the composer toolbar. Switch to plain text mode if preferred.
- Reply & Forward — use the Reply, Reply All, and Forward buttons in the message toolbar. Original message is quoted automatically.
- Email priority — set message priority (low, normal, high, or urgent) from the composer toolbar. Recipients see the priority flag in their mail client.
- 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 / from anywhere in the app.
Cross-account results
Results from all accounts appear together, labelled by account. No need to search each account individually.
Keyboard Shortcuts
MailFlow supports a full set of keyboard shortcuts. Press ? at any time to view the shortcut reference inside the app. All shortcuts are customisable in Settings → Shortcuts. Use Ctrl+K (or ⌘+K on Mac) to open the command palette.
| Key | Action |
|---|---|
| c | Open new compose window |
| / | Focus the search box |
| ? | Show keyboard shortcut reference |
| Ctrl+K | Open command palette |
| j / k | Next / previous message |
| o | Open first message (if none selected) |
| g i | Go to unified inbox |
| r | Reply to current message |
| a | Reply all |
| f | Forward current message |
| e | Archive message / selection |
| # | Delete message / selection |
| s | Toggle star on current message |
| m | Mark current message read or unread |
| x | Select / deselect current message |
| Ctrl+P | Print current message |
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 |
| Font size | Slider from 80% to 130% — scales the entire UI uniformly. |
| Language | English, Deutsch, Español, Français, Italiano, Русский, 中文 |
| Conversation threading | Groups replies into conversations. Toggle in Settings → Appearance → Layout. |
| Custom CSS | Paste CSS into Settings → Appearance → Custom CSS to override any part of the MailFlow interface. Applies only to your own account. |
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.
Unread badges
MailFlow can display unread counts as badges in two places, each independently togglable in Settings → Notifications:
- App icon badge — shows the total unread count on the PWA or browser app icon (uses the Web Badging API; supported on Chrome/Edge and most mobile browsers).
- Favicon badge — overlays the unread count on the browser tab favicon so you can see it at a glance without switching tabs.
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.