Everything you need to
self-host MailFlow

Installation guides, server administration, and a complete user reference — all in one place.

Section 1
Installation

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. 1

    Download the compose file and default config

    These two commands fetch the latest pre-built compose file and a template .env.

    bash
    curl -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. 2

    Generate secrets and configure .env

    Open .env in 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. 3

    Start the containers

    bash
    docker compose up -d
  4. 4

    Create your admin account

    Open https://your-domain.com in a browser. The first account you register becomes the admin. After registering, go to Settings → Users to close registration or invite others.

  5. 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:

.env
APP_PORT=443       # HTTPS port (self-signed cert)
APP_HTTP_PORT=80   # HTTP port (no TLS)
Self-signed certificate

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
X-Forwarded-Proto is required

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

nginx
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";
    }
}
Caddyfile
mail.example.com {
    reverse_proxy localhost:80
    # Caddy automatically sets X-Forwarded-Proto and handles TLS
}
Caddy sets X-Forwarded-Proto automatically

Caddy adds the X-Forwarded-For and X-Forwarded-Proto headers by default — no extra configuration needed.

docker-compose labels
# 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. 1

    Download the HTTPS overlay and set your domain in .env

    You need one extra file alongside the docker-compose.yml downloaded in step 1 above:

    bash
    curl -o docker-compose.https.yml https://raw.githubusercontent.com/maathimself/mailflow/main/docker-compose.https.yml

    Then add these lines to your .env:

    .env
    DOMAIN=mail.example.com
    [email protected]
    APP_URL=https://mail.example.com
  2. 2

    Start with the HTTPS overlay

    bash
    docker compose -f docker-compose.yml -f docker-compose.https.yml --profile https up -d
Automatic certificate renewal

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.

bash
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:

bash
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.

📧
Gmail

Requires an App Password — your regular Google password will not work.

  1. Enable 2-Step Verification on your Google account
  2. Go to myaccount.google.com/apppasswords
  3. Create a new App Password, name it "MailFlow"
  4. Copy the 16-character password into MailFlow
IMAP Hostimap.gmail.com
IMAP Port993
SMTP Hostsmtp.gmail.com
SMTP Port587
Usernameyour Gmail address
🍎
iCloud / Apple Mail

Requires an App-Specific Password from Apple ID settings.

  1. Go to appleid.apple.com
  2. Sign-In and Security → App-Specific Passwords
  3. Generate a password, name it "MailFlow"
IMAP Hostimap.mail.me.com
IMAP Port993
SMTP Hostsmtp.mail.me.com
SMTP Port587
Username[email protected]
🏢
Microsoft 365 / Outlook

Work / school accounts (Microsoft 365): Require OAuth2 via an Azure App Registration.

  1. In MailFlow: Settings → Integrations
  2. 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
  3. 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.

  1. Settings → Accounts → Add Account → Outlook.com / Hotmail
  2. MailFlow shows a short code — visit microsoft.com/devicelogin and enter it to authorise
⚙️
Custom IMAP / SMTP

Any standard IMAP/SMTP server works. Use these port conventions:

IMAP TLSPort 993
SMTP STARTTLSPort 587
SMTP TLSPort 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_URLRequiredFull URL users see in the browser, e.g. https://mail.example.com. Used in invite emails and CORS.
SESSION_SECRETRequiredSecret for signing session cookies. Generate with openssl rand -hex 32. Must be 32+ characters.
DB_PASSWORDRequiredPostgreSQL database password. Generate with openssl rand -hex 16.
ENCRYPTION_KEYRequired32-byte key for encrypting stored email credentials. Generate with openssl rand -hex 32. Changing this makes all stored credentials unreadable.
APP_PORT443OptionalHost port for the HTTPS listener (self-signed TLS).
APP_HTTP_PORT80OptionalHost port for the HTTP listener. Use this as the target for an upstream reverse proxy.
VAPID_PUBLIC_KEYOptionalWeb Push public key. Generate with npx web-push generate-vapid-keys. Required for push notifications.
VAPID_PRIVATE_KEYOptionalWeb Push private key (keep secret).
VAPID_SUBJECTOptionalContact for the push service, e.g. mailto:[email protected] or your APP_URL.
DOMAINOptionalYour public domain name. Required only when using --profile https (Let's Encrypt).
ACME_EMAILOptionalEmail for Let's Encrypt certificate notifications. Required only with --profile https.
MAILFLOW_VERSIONlatestOptionalPin a specific image version instead of latest, e.g. 1.7.3.
Section 2
Administration

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:

MethodHow it works
TOTPTime-based one-time passwords via any authenticator app (Google Authenticator, Authy, 1Password, etc.). Recommended for all users.
Email OTPA one-time code sent to a recovery email address. Useful as a fallback if the authenticator app is unavailable.
Device trustAfter 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. 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. 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.

Per-user integration

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

bash
docker exec mailflow-postgres pg_dump -U mailflow mailflow \
  > mailflow-$(date +%Y%m%d).sql

Restore

Stop MailFlow before restoring

Run docker compose stop backend before restoring to prevent active connections from interfering.

bash
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)

bash
docker compose pull
docker compose up -d

Build from source (Option B)

bash
git pull
docker compose up -d --build
Pinning a version

To avoid automatic upgrades, add MAILFLOW_VERSION=1.9.0 to your .env. Remove it to return to latest.


Command Reference

bash
# 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_KEY means 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.
Section 3
User Guide

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. 1

    Open the account settings

    Click the gear icon (Settings) → AccountsAdd Account.

  2. 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. 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. 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

ActionHow
Mark as read / unreadClick the dot indicator on a message, or use the button in the toolbar. Synced back to IMAP immediately.
Star a messageClick the star icon. Starred messages sync to IMAP flags (shows up in other clients too).
Delete a messageClick the trash icon or press Delete. Moves to Trash on the IMAP server.
Mark all as readRight-click a folder → Mark all read, or use the folder context menu.
Select multipleHold 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 actionsHover 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 behaviorConfigure 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.

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.

KeyAction
cOpen new compose window
/Focus the search box
?Show keyboard shortcut reference
Ctrl+KOpen command palette
j / kNext / previous message
oOpen first message (if none selected)
g iGo to unified inbox
rReply to current message
aReply all
fForward current message
eArchive message / selection
#Delete message / selection
sToggle star on current message
mMark current message read or unread
xSelect / deselect current message
Ctrl+PPrint current message

Appearance

Open Settings → Appearance to customize how MailFlow looks.

SettingOptions
ThemeDark (default), Light, Catppuccin Mocha, Gruvbox Dark, and more
LayoutClassic, Compact, Comfortable, Wide Reader, Wide List, Vertical Split, Focused
Font sizeSlider from 80% to 130% — scales the entire UI uniformly.
LanguageEnglish, Deutsch, Español, Français, Italiano, Русский, 中文
Conversation threadingGroups replies into conversations. Toggle in Settings → Appearance → Layout.
Custom CSSPaste 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. 1

    Generate VAPID keys (server admin)

    bash
    npx web-push generate-vapid-keys

    Copy the output into VAPID_PUBLIC_KEY, VAPID_PRIVATE_KEY, and VAPID_SUBJECT in .env, then restart.

  2. 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. 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.