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
    ACME_EMAIL=you@example.com
    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
Usernameyou@icloud.com
🏢
Microsoft 365 / Outlook

Work and school accounts typically require OAuth2 (modern auth). Personal Outlook accounts can use IMAP with an app password.

  1. In MailFlow: Settings → Integrations → Microsoft 365
  2. Follow the Azure App Registration instructions shown there
  3. Save the client ID and secret, then click Connect Microsoft account
⚙️
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:admin@example.com 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.0.0.
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 → 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

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

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

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

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.

SettingOptions
ThemeDark (default), Light, Catppuccin Mocha, Gruvbox Dark, and more
LayoutClassic, Compact, Comfortable, Wide Reader, Wide List, Vertical Split, Focused
LanguageEnglish, 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. 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.