Self-host Mail Mantis

Run it on your laptop or a small server with Docker, or deploy it to Vercel with Neon. Either way, it’s your own private instance.

Requirements

Docker quick start

git clone https://github.com/karuridavid/mailmantis
cd mailmantis
docker compose up -d

Compose starts Postgres and the app. On first boot the app generates its encryption key and a one-time admin setup key, and stores them in the app-data volume. Read the setup key from the logs:

docker compose logs app | grep "setup key"

Open http://localhost:8080, enter the setup key, and create your admin login (password of 12+ characters). Then follow the checklist on the Overview page.

Over plain HTTP, sign-in works on localhost only, because browsers drop secure cookies elsewhere. To reach it from another device, use HTTPS (next section) or an SSH tunnel: ssh -L 8080:localhost:8080 your-server.

HTTPS on a domain

Point a DNS A record (for example app.example.com) at your server, open ports 80 and 443, then:

cat > .env <<'EOF'
DOMAIN=app.example.com
TRUST_PROXY=1
EOF
docker compose --profile https up -d

This adds Caddy as a reverse proxy, which gets and renews a Let’s Encrypt certificate automatically. Your dashboard is then at https://app.example.com.

Mail Mantis is a single-admin tool. Keep it on its own subdomain, don’t link to it publicly, and use a strong password. Login attempts are rate-limited, and pages are served with noindex.

Gmail and Google Workspace inboxes

Seed inboxes connect with Google OAuth, so Mail Mantis never sees their passwords. You create the OAuth client in your own Google Cloud project:

  1. In Google Cloud Console, create a project and enable the Gmail API.
  2. Open Google Auth Platform (OAuth consent screen). Choose External, fill in the app name and your email, and leave the app in Testing.
  3. Under Audience → Test users, add every Gmail address you’ll use as a seed inbox.
  4. Under Data access, add these scopes: gmail.modify, gmail.send and gmail.settings.basic.
  5. Under Clients, create an OAuth client of type Web application. Add the Authorized redirect URI shown in Mail Mantis under Settings → Google sign-in. It looks like https://app.example.com/api/google_oauth_callback (http://localhost:8080/… works for local use).
  6. Copy the client ID and secret into Settings → Google sign-in, or set GOOGLE_CLIENT_ID and GOOGLE_CLIENT_SECRET.

In Testing mode Google expires the connection after 7 days, so you’ll reconnect seed inboxes weekly with one click. Publishing the app removes this, but Gmail’s restricted scopes then require Google’s verification. For personal use, Testing mode is simplest.

What each scope is used for:

ScopeUsed for
gmail.modifyReading the labels on one specific message to report placement, and reading filters. Messages are never moved or relabelled.
gmail.sendSending replies you wrote or approved, from that inbox.
gmail.settings.basicCreating “Never send to Spam” and “Mark important” filters, only when you click them.

Outlook and Microsoft 365 inboxes

Outlook.com, Hotmail and Microsoft 365 inboxes connect through Microsoft Graph with your own app registration:

  1. In the Microsoft Entra admin center, open App registrations → New registration. For supported account types choose Accounts in any organizational directory and personal Microsoft accounts.
  2. Add a Web platform redirect URI: the one shown under Settings → Microsoft sign-in, for example https://app.example.com/api/microsoft_oauth_callback.
  3. Under Certificates & secrets, create a client secret and copy its value.
  4. Under API permissions, add delegated Microsoft Graph permissions: Mail.ReadWrite, Mail.Send, MailboxSettings.ReadWrite, User.Read and offline_access.
  5. Paste the Application (client) ID and secret into Settings → Microsoft sign-in, or set MICROSOFT_CLIENT_ID and MICROSOFT_CLIENT_SECRET.

What Outlook supports:

ActionHow
PlacementJunk Email folder → Spam; Inbox with Focused or Other.
Not spamGraph markAsNotJunk: moves the email to the Inbox and unblocks the sender.
Mark importantSets the email’s importance to high.
Always FocusedA Focused Inbox override for your sender address.
Mark important ruleAn inbox rule that marks future mail from your sender as high importance.
RepliesSent from the inbox in the same conversation.

Microsoft Graph has no API for the Safe Senders list, so a “never send to Junk” filter isn’t possible for Outlook. Use Not spam on individual emails instead.

Yahoo, iCloud, AOL and other IMAP inboxes

These connect with an app password over IMAP, and send replies over SMTP. Your normal password won’t work, and two-step verification usually has to be on.

Mail Mantis finds the spam folder by its \Junk flag or a name like Spam, Junk or Bulk. Not spam moves the email to INBOX, and Flag as important sets the \Flagged flag (shown as starred or flagged). IMAP has no filter API, so server-side filters aren’t available for these inboxes.

Domain sender

Under Domain sender, enter your From name, sender address and SMTP details, then choose Test connection. For Brevo that’s smtp-relay.brevo.com, port 587, your SMTP login and SMTP key. Saving tests the connection again.

Before sending anything, make sure your domain has SPF, DKIM and a DMARC record. Your SMTP provider’s domain setup page lists the exact DNS records to add.

Gemini

Create an API key at Google AI Studio and add it under Settings → Gemini. The default model is gemini-3.8-flash; you can change it there. Gemini is optional. You can write the business brief and every email yourself.

Deploy to Vercel + Neon

  1. Fork the repository and import it as a new Vercel project (framework preset: Other).
  2. In the project’s Storage tab, add a Neon Postgres database. This sets DATABASE_URL.
  3. Generate the two keys and add them as environment variables:
    python -c "from cryptography.fernet import Fernet; print('APP_ENCRYPTION_KEY=' + Fernet.generate_key().decode())"
    python -c "import secrets; print('ADMIN_SETUP_KEY=' + secrets.token_urlsafe(18))"
    No Python? openssl rand -base64 32 | tr '+/' '-_' also makes a valid encryption key.
  4. Deploy, open the deployment URL, and sign in with the setup key. Add a custom domain such as app.example.com in Vercel if you like, and use that URL for the Google redirect URI.

Keep APP_ENCRYPTION_KEY safe and never change it. Saved SMTP keys, Google tokens and API keys can only be decrypted with it.

Configuration reference

VariableNeededPurpose
DATABASE_URLAlways (Compose sets it)Postgres connection string.
APP_ENCRYPTION_KEYVercelFernet key for secrets at rest. Generated automatically with Docker.
ADMIN_SETUP_KEYVercelOne-time key for creating the admin login. Generated automatically with Docker.
GOOGLE_CLIENT_ID
GOOGLE_CLIENT_SECRET
OptionalOAuth client. Overrides the values saved in Settings.
MICROSOFT_CLIENT_ID
MICROSOFT_CLIENT_SECRET
OptionalMicrosoft app registration. Overrides the values saved in Settings. MICROSOFT_TENANT defaults to common.
APP_URLOptionalPublic base URL, if it can’t be worked out from the request.
GOOGLE_REDIRECT_URIOptionalOverrides the derived APP_URL/api/google_oauth_callback.
DOMAIN, TRUST_PROXYHTTPS profileDomain for Caddy; trust its forwarded headers.
POSTGRES_PASSWORD, PORTOptionalDatabase password inside Compose; host port (default 8080).

Updates & backups

# update to the latest version
git pull && docker compose up -d --build

# back up the database
docker compose exec db pg_dump -U mailmantis mailmantis > mailmantis-backup.sql

The database schema upgrades itself on start. Also back up the app-data volume, or your APP_ENCRYPTION_KEY, alongside the database dump.

Development

The dashboard is plain HTML, CSS and JavaScript, and the API is standard-library Python with psycopg and cryptography. There is no build step. To run it with Gmail, SMTP and Gemini replaced by fakes:

docker run -d --name ms-test-db -e POSTGRES_PASSWORD=dev -p 55432:5432 postgres:16-alpine
pip install -r requirements.txt
DEMO=1 python tests/fake_server.py   # http://localhost:18080, demo@example.com / demo-password-123

With an empty database (no DEMO=1), python tests/test_api.py runs the end-to-end API tests.