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. Type your email address and MailFlow picks the provider, or choose Outlook.com, Microsoft 365, Gmail, Yahoo, iCloud or Other (IMAP). 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. Microsoft and Google sign-in are configured by an admin under Settings → Email providers. Provider-specific instructions are below.
Choose Gmail under Add account. If your admin has set up Google sign-in, select Sign in with Google and you are done. Otherwise, use 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 |
Admins: Sign in with Google needs a Google Cloud OAuth client, set up under Settings → Email providers, which shows the steps. Use it when Google Workspace has app passwords turned off.
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 |
Personal accounts (Outlook.com, Hotmail, Live, MSN): No setup needed. MailFlow signs in through its shared Microsoft app with a short code.
- In Outlook.com, turn on IMAP: Settings → Mail → Forwarding and IMAP → Let devices and apps use IMAP. MailFlow tells you if it is off
- In MailFlow, go to Settings → Accounts → Add Account and choose Outlook.com, Hotmail, Live, MSN, or type your address
- MailFlow shows a short code. Visit microsoft.com/devicelogin, enter it and sign in
Microsoft may label the app “unverified”; that is expected. You sign in on Microsoft’s own page, Microsoft sends access straight to your server, and MailFlow’s maintainers never see your mail or tokens. Admins can turn the shared app off under Settings → Email providers, or with MS_BUILTIN_APP=false in .env.
Work / school accounts (Microsoft 365): Need your instance’s own Microsoft Entra app, set up once by an admin. Admins who prefer not to use the shared app can use their own app for personal accounts too.
- At portal.azure.com, go to Microsoft Entra ID → App registrations → New registration. Under Supported account types, choose Accounts in any organizational directory and personal Microsoft accounts, then copy the Application (client) ID
- Under API permissions, add
IMAP.AccessAsUser.AllandSMTP.Send(Office 365 Exchange Online, Delegated) plusoffline_access,openid,email,profile(Microsoft Graph, Delegated), then click Grant admin consent - Under Authentication → Add a platform → Web, add the redirect URI shown in MailFlow (
https://your-domain/oauth/microsoft/callback). Under Certificates & secrets, create a client secret and copy its Value - In MailFlow: Settings → Email providers → Set up your own Microsoft Entra app. Enter the client ID, tenant ID, client secret and redirect URI, then Save. People then choose Microsoft 365 (work or school) under Add Account and sign in on Microsoft’s page
Don’t skip the API permissions. Without the mail permissions the account connects but no mail loads and sending fails with a credentials error.
Personal accounts on your own app: with tenant common or consumers, personal accounts use your app instead of the shared one. With a client secret and redirect URI they sign in on Microsoft’s page; without them, set Authentication → Allow public client flows to Yes and they sign in with a code. Accounts already connected through the shared app keep working.
If sending is turned off: Microsoft turns off sending from mail apps (SMTP) for some Outlook.com mailboxes, often new ones, and work or school admins can turn it off too. Mail still arrives, and MailFlow tells you when it cannot send. Signing in again does not fix it: send from Outlook on the web, or for a work or school account, ask your admin to turn SMTP sending back on for the mailbox.
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.
For on-premises Exchange servers that allow only ActiveSync. The third-party EASBridge runs next to MailFlow and offers IMAP, SMTP and read-only CardDAV contacts on its Docker network.
- Follow its install guide for MailFlow, which adds the bridge to your compose setup
- As an admin, turn on Allow private / local hosts under Settings → Security
- Add the mailbox as a Custom account: IMAP
easbridgeport 993, SMTPeasbridgeport 465
Needs Basic authentication for ActiveSync, so not Microsoft 365, and has no calendar. EASBridge is a separate project that MailFlow’s maintainers don’t maintain or review, and it handles your Exchange password, so decide for yourself whether to trust it.
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. 4.2.0. |
| IMAP_MAX_PERSISTENT_PER_HOST | unlimited | Optional | Caps how many accounts on one mail server hold a live push connection. The rest poll on the sync interval instead. Empty or 0 means unlimited. Only set this if you run many accounts on a single connection-limited server. |
| MS_BUILTIN_APP | true | Optional | Set false to stop offering MailFlow’s shared Microsoft app for personal Outlook.com and Hotmail accounts. Accounts already connected through it keep working. The switch under Settings → Email providers, once used, takes precedence. |
| MS_CLIENT_ID MS_CLIENT_SECRET MS_TENANT_ID MS_REDIRECT_URI | - | Optional | Your own Microsoft Entra app, instead of entering it under Settings → Email providers. MS_TENANT_ID defaults to common. |
| GOOGLE_CLIENT_ID GOOGLE_CLIENT_SECRET GOOGLE_REDIRECT_URI | - | Optional | Your own Google Cloud OAuth client for Sign in with Google, instead of entering it under Settings → Email providers. |
Values saved under Settings → Email providers take precedence over the MS_* and GOOGLE_* settings, field by field, and an empty value counts as not set. On Docker these settings reach MailFlow only with the compose file from MailFlow 4.1 or later. If your docker-compose.yml has no MS_CLIENT_ID line, download it again (see Updates).
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, email providers, 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 authorize 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.
Moving MailFlow to a new server? Stop the old one first. If both run, both can send the same scheduled messages.
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=4.2.0 to your .env. Remove it to return to latest.
Updating the compose file
docker compose pull updates the images but not your compose file. For MailFlow 4.1, download the compose file again: it now passes the GOOGLE_* and MS_* settings in your .env to the backend, and gives the backend 30 seconds to stop, so a send in progress can finish during an update. If you changed your compose file, copy the new backend lines into it instead. When you build from source, git pull updates it for you.
curl -o docker-compose.yml https://raw.githubusercontent.com/maathimself/mailflow/main/docker-compose.ghcr.yml
docker compose up -d
MailFlow 4.1 adds one database migration (0071) with the tables for scheduled messages. It runs automatically when the backend starts, and your existing data is unchanged. MailFlow 4.2 adds one more (0073) that renumbers the account order in the sidebar, keeping the order you see, so moving an account up or down sticks.
Going back to 4.0.x
4.0.x does not send scheduled messages, and its built-in backups refuse to run while the scheduled send tables exist. Send or delete any pending scheduled messages, switch back, then remove the tables. This deletes every scheduled message.
docker compose exec postgres psql -U mailflow -d mailflow -c "DROP TABLE scheduled_message_attachments, scheduled_messages; DELETE FROM schema_migrations WHERE version = '0071_scheduled_messages';"
Use your DB_USER and DB_NAME if you changed them. Updating to 4.1 again recreates the tables.
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
Diagnostics
MailFlow can generate a sanitized report you can share when asking for help, from Settings → Diagnostics.
- Includes app versions, your environment, per-account and folder counts, sync error categories, and server health. Account IDs and custom folder names are replaced by one-time hashes.
- Excludes email addresses, names, subjects, message content, passwords, and API keys or tokens.
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, registration, and two-factor verification are limited to 10 attempts per 15 minutes per IP, adjustable under Settings → Security.
- 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 your provider
Type your email address and MailFlow picks the provider, or choose one: Outlook.com, Hotmail, Live, MSN, Microsoft 365 (work or school), Gmail, Yahoo Mail, iCloud Mail, or Other (IMAP) to enter your IMAP and SMTP servers yourself.
-
3
Sign in or enter your password
Microsoft accounts, and Gmail when your admin has set up Google sign-in, sign in on the provider’s own page. For the others, enter your username (usually your email address) and password (an App Password for Gmail, Yahoo and iCloud, see Email Providers). MailFlow checks that it can reach your mailbox before it reports the account as connected.
-
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.
-
5
If an account can't connect
The account says why, with the fix next to it: for example, IMAP is turned off in Outlook.com (with the steps to turn it on), the sign-in has expired, or the server rejected the password. Select Try again once it is fixed, or Sign in again for Microsoft and Google accounts.
Microsoft and Google accounts show who they are signed in as, with a Sign in again button instead of password and server fields. For a shared or delegated Microsoft mailbox, enter its address under Mailbox to open.
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 Focused, Compact, Comfortable, Wide, Vertical Split, and List first layouts from Settings → Appearance → Layout. List first gives the message list the whole window and opens a message in its place; Back, Escape, or the browser’s Back button return you to the list where you left it. 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. |
| Open in a separate window | Right-click a message → Open in new window to detach it into its own floating window, so you can read one message while working through the rest of the list. |
| Copy a link to a message | Right-click a message → Copy link for a durable permalink that reopens that exact message later. |
| Sort a folder | Click Sort in the list header to order a folder by date, sender, or subject, either way. Subject sort ignores Re: and Fwd:. Each folder keeps its own order, and it follows you to your other devices. Back to newest first resets it. Search, Snoozed, and Scheduled keep their own order. |
| Starred on top | A switch in the same menu, per folder, that keeps starred messages above the rest. It uses the IMAP star, so mail starred in other clients counts too. |
| 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 <j@example.com>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.
- Send later: schedule the message for a time you choose instead of sending it now. See Send Later.
Send Later
Write a message now and have MailFlow send it at the time you choose. The message is stored encrypted on your MailFlow server and sent from there, so once it is scheduled you can close the browser or turn off your computer.
Scheduling a message
On a computer, open the menu on the arrow next to Send, or press Ctrl+Shift+Enter (⌘+Shift+Enter on a Mac). On a phone, tap the clock button next to Send. Then choose when it goes:
| Option | When it is sent |
|---|---|
| Later today | 6:00 PM today. Offered until 4:00 PM. |
| Tomorrow morning | 8:00 AM tomorrow. |
| Tomorrow afternoon | 1:00 PM tomorrow. |
| Monday morning | 8:00 AM next Monday. Not offered on Sundays, when it would be the same as Tomorrow morning. |
| Pick date and time | Any date and time from a minute from now to a year ahead. |
Times are in your device’s time zone, which the picker shows. Once the message is scheduled, the composer closes and a notice offers Undo for 10 seconds, which brings it back to the composer. A draft leaves Drafts once it is scheduled. Attachments and forwarded messages are saved with the message when you schedule it, so archiving or deleting the original does not affect the send.
The Scheduled view
While you have scheduled messages, Scheduled appears in the sidebar below Snoozed. It lists them from every account in four groups: Needs attention, Scheduled (soonest first), recently Sent, and Deleted messages you can still restore. Click a message to edit it, and hover it, right-click it or use the keyboard for the rest. On a phone, tap a message to edit it, and long-press it or use its More button for the other actions.
| Action | What it does |
|---|---|
| Edit (Enter) | Opens it in the composer. Save your changes with Update, or choose Unschedule (save as draft) to move it to Drafts. A message open for editing is never sent underneath you. If it is open in another window or on another device, MailFlow asks before moving it to the one you are using. If its time passes while it is open, closing it asks whether to send it now, pick a new time or keep it paused, and if the tab is simply closed, it is paused. |
| Reschedule (r) | Choose a new time from the same options. |
| Send now (s) | Sends it in 10 seconds, with Undo until then. |
| Delete (Delete) | Moves it to Deleted, where Restore brings it back for 10 minutes. If its time has already passed, a restored message comes back paused. Delete for good removes it at once. |
| Resume / Try again | Sends a paused or failed message now. |
| View in Sent | Opens the copy in your Sent folder. Sent messages stay in the list for a week, or until you remove them. |
Sent at most once
MailFlow never sends a scheduled message twice on its own. If it cannot tell whether the mail server took the message, for example because the connection dropped in the middle of sending, it does not try again. The message shows Check delivery under Needs attention. Look in your Sent folder, then choose Send anyway (if it did go out, the recipients get it twice) or Mark as sent (MailFlow deletes its content and never sends it).
When the mail server asks MailFlow to try later, or cannot be reached, the message shows Retrying and MailFlow tries again after 1, 5 and 15 minutes, 1 hour and 3 hours, about four hours in all. If every attempt fails, it stops and tells you.
If MailFlow was offline
If your MailFlow server was down when a message was due, it sends the message once it is back, as long as it is at most 6 hours late, and marks it Sent late with the time it was due. Later than that, MailFlow pauses it and asks you, so a “see you in an hour” note never goes out the next day. Open it to send it now or pick a new time.
When a message needs you
A message that cannot go out on its own moves to Needs attention with the reason in words, and the sidebar shows how many are waiting for you. MailFlow also shows a notice in the app, and sends a push notification if you have turned them on.
| Reason shown | What to do |
|---|---|
| Sign in to the account again to send it. | The account’s sign-in has expired or its password was rejected. Sign in again or update the password under Settings → Accounts, then select Resume. |
| Its account is turned off. | Turn the account back on, then select Resume. |
| It was more than 6 hours late, so MailFlow held it. | Send it now or pick a new time. |
| It was open for editing when it was due. | Resume it, or pick a new time. |
| It was paused when a backup was restored. | Restoring a backup pauses every waiting message, because the server the backup came from may still be running and send it too. Send it from here once you know it will not go out twice. |
| Microsoft has turned off sending from mail apps (SMTP) for this account. | Microsoft turns this off for some Outlook.com mailboxes, often new ones, and work or school admins can turn it off too. Signing in again does not help. Send it from Outlook on the web, or ask your admin to turn sending back on for a work or school account. |
| The mail server refused it, or it is too large for the mail server. | If the server gave a reason, it is shown with the message. Edit it, for example to fix an address or remove an attachment, then try again. |
| The account’s sending settings need attention, or the secure connection to the mail server failed. | Check the account’s outgoing (SMTP) server settings, then try again. |
| The address it was to be sent from no longer exists. | Edit it and choose another From address. |
| The mail server kept failing, so MailFlow stopped trying. | Try again later, or check the mail server’s status. |
Each person can have up to 100 messages waiting to be sent, and 250 MB of scheduled mail in all. The text and inline images of one message can take up to 10 MB.
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, labeled 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 customizable in Settings → Shortcuts. Use Ctrl+K (or ⌘+K on Mac) to open the command palette.
| Key | Action |
|---|---|
| c | Open new compose window |
| Ctrl+Shift+Enter | Send later (in the composer) |
| / | 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 |
| Ctrl+/ | Toggle the right sidebar |
| 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 |
| t / w / d | Label as Todo / Watch / Delegated (GTD, when enabled) |
| Ctrl+Z | Undo the last GTD label change |
GTD Workflow
An optional Getting Things Done workflow, off by default and enabled per account under Settings → Categories → GTD. When on, a rail beside the message list groups threads into five states, each backed by a real IMAP folder, so the labels are ordinary server-side folders that sync to every mail client and survive MailFlow itself.
Todo / Someday
Things you need to act on. The Todo label clears itself automatically once you reply to the thread.
Watch / Delegated
Things you are waiting on. The label clears itself once the other party replies.
Reference
Kept until you remove it by hand, for threads you want to keep at your fingertips.
Keyboard labels
Label the selected thread with t Todo, w Watch, d Delegated (all remappable) or from the context menu, which also covers Someday and Reference. Folder names are configurable per account.
AI Assistant
Connect any OpenAI-compatible provider (a local model or a cloud API) under Settings → AI. MailFlow only sends mail to the provider you configure.
Summarize threads
Condense a long conversation into a few lines so you can catch up at a glance.
Draft & rewrite replies
Generate a reply from a short prompt, or rewrite your draft to adjust tone and length. Reply with AI writes the answer from the conversation so far and a few words from you; it also needs summaries turned on. Output renders as Markdown and can be copied as rich text or source.
Ask about a message
Ask questions about the selected message and get answers grounded in its content.
Your provider, your keys
Point MailFlow at any endpoint that speaks the OpenAI API (a local runner such as Ollama or LM Studio, OpenAI, or a compatible gateway) and pick the model.
Snooze
Snooze a message to get it out of your inbox until you are ready. It disappears now and returns to the top of the inbox at the time you choose.
Snooze a message
From the message toolbar or context menu, pick a time: later today, tomorrow, this weekend, or a custom date and time.
It comes back on top
At the chosen time the whole conversation returns to the inbox, surfaced at the top so it stands out.
Spam Reporting
Mark messages as spam or not spam from the context menu, toolbar, or bulk actions. Reported mail moves to the spam folder; this feedback will feed automated filtering in a future release.
Report spam
Flag one or many messages as spam, they move to the spam folder and out of your inbox.
Not spam
Rescue a legitimate message from the spam folder and move it back to the inbox.
Inbox Categorization
Incoming mail is sorted into tabs at the top of the inbox (Primary, Newsletters, Promotions, Automated, and Social) using header detection and sender heuristics. Categorization is optional and can be turned off in settings.
Category tabs
Primary keeps person-to-person mail front and center; newsletters, social, and automated notifications get their own tabs so they do not bury the messages that matter. Each tab shows how many messages it holds, with unread mail in a badge of its own, so a tab whose mail is all read no longer looks empty.
AI reclassify
When a message lands in the wrong tab, the AI reclassify button moves it to the right one.
One-click unsubscribe
For detected newsletters an unsubscribe button appears in the message pane. MailFlow sends the unsubscribe request or opens the unsubscribe link for you.
Rules & Block List
Automate what happens to incoming mail. Rules run on delivery, and the block list runs first, before any rules.
Inbox rules
Move, archive, delete, mark read, or star messages automatically based on sender, subject, recipient, headers, body, or attachments.
Block list
Add a sender to the block list and their mail is moved straight to trash before any rules run.
Contacts & CardDAV
MailFlow builds a contact list from your mail and can expose it as a CardDAV address book, so your contacts sync to your phone and desktop address books.
CardDAV address book
Point any CardDAV client (iOS, macOS, Thunderbird, DAVx5) at MailFlow to sync your contacts as a standard address book.
Sync to your devices
Contacts and their photos sync so the people you email are available everywhere.
Sender avatars
Contact photos (and optional Gravatar avatars) appear beside senders in the message list. Gravatar is off by default and proxied through the server.
Folder Sync
MailFlow keeps each account’s folder list in step with the server, so folders you create or rename in another mail client show up here on their own.
Automatic folder sync
The folder structure refreshes on a schedule you choose in Settings → Appearance: 15 minutes, 30 minutes, 1 hour, or never.
Sync folders now
Trigger an immediate folder refresh from the sidebar account menu or the account settings page.
Command Palette
A fast, keyboard-first way to jump to any action or place in MailFlow.
Open the palette
Press Cmd K (or Ctrl K) from anywhere to open it.
Actions & navigation
Type to filter accounts, folders, settings, and actions, then press Enter. No mouse required.
API Tokens
Let scripts and tools, such as an MCP server, use your mail without your password. Create a token under Settings → Security → API tokens.
Scopes
Read lists, reads and searches your mail. Organize marks read, stars, archives, moves, snoozes and sets categories. Draft saves drafts for you to review and send. A token can never send, delete or change settings, and it can’t move mail into Trash or Junk.
Creating and revoking
Confirm with your password, your authenticator code or single sign-on. The token is shown once, so copy it then, and send it as Authorization: Bearer mfat_…. Each token shows when it was last used, and Revoke stops it at once.
Turning tokens on
Tokens are off by default. An administrator turns them on with the Allow API tokens on this server switch on the same card. Turning it off stops every user’s tokens at once without deleting them.
Mailbox Cleanup
Find bulk mail cluttering your inbox and clear it out, from Settings → Mailbox Cleanup. Choose whether to archive it (kept and searchable) or move it to Trash (recoverable until you empty it).
Top bulk senders
The highest-volume newsletter and marketing senders in your inbox. Acting on one processes all of that sender's inbox mail with your chosen action.
Promotional terms
Common promotional keywords you can search to review and delete the rest yourself. They span many senders, so skim the results before deleting.
Nothing is lost
Deletes always move to Trash first, so you can review before emptying it. Archiving keeps everything searchable.
Plugins
Optional features you can turn on or off for your own account under Settings → Plugins. Enabling a plugin adds its own settings and interface; disabling it removes them again. Activation is per user, so one person enabling a plugin does not change it for anyone else on the server. The GTD workflow described above ships as a plugin.
Appearance
Open Settings → Appearance to customize how MailFlow looks.
| Setting | Options |
|---|---|
| Theme | Dark (default), Light, Catppuccin Mocha, Gruvbox, and more |
| Layout | Focused, Compact, Comfortable, Wide, Vertical Split, List first |
| Font size | Slider from 80% to 130%, scales the entire UI uniformly. |
| Language | English, Čeština, Deutsch, Español, Français, Italiano, Polski, Русский, 中文 |
| 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.