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. Choose Check connection to confirm the keys and redirect URI.

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 from that inbox: ones you send, or autopilot’s when it’s on.
gmail.settings.basicCreating the “Never send to Spam” filter for your sender, which also marks its mail important. Only when you (or the inbox’s owner) click it.

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, plus the Mark important rule below, in one click.
Mark important ruleAn inbox rule that marks future mail from your sender as high importance. Also available on its own.
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.

Inviting people to lend inboxes

Under People, create an invite link and send it to someone. They set a password and get a member account that can only connect, manage and disconnect their own Gmail or Outlook inboxes, and add the Never send to Spam filter to them. They can’t see emails, domains, results or settings. Links work once and expire after 7 days. Removing a person also disconnects the inboxes they added.

While your Google app is in Testing mode, add each person’s Gmail address as a test user, or Google will block their sign-in.

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.

AI services

Add a key for any of these under Settings → AI writing, then choose Test. Pick one as the default; every draft, reply and brief also has a picker, so you can try several. Keys are encrypted and only sent from your server to that service.

ServiceCostDefault modelGet a key
Google GeminiFree tiergemini-3.8-flashGoogle AI Studio
Anthropic ClaudePaid API creditclaude-opus-5-5Anthropic Console
OpenAIPaid API creditgpt-5-miniOpenAI Platform
OpenRouterFree modelsopenrouter/freeOpenRouter
GroqFree tieropenai/gpt-oss-120bGroqCloud
Cloudflare Workers AIFree daily allowance@cf/meta/llama-3.3-70b-instruct-fp8-fastCloudflare dashboard

Gemini and Claude read your website themselves when writing the business brief. The others work from the page text Mail Mantis fetches from your site.

No API key? Choose Chat app when you write. Mail Mantis gives you the prompt to paste into Claude, ChatGPT or any chat app you use, and you paste the answer back. A Claude Pro or ChatGPT Plus plan works for this, but doesn’t include API use.

Autopilot

Autopilot keeps mail flowing without you opening the dashboard every day. Turn it on from the Autopilot card on the Overview page. It runs once a day, around 09:00 UTC, until you press Pause. Each run:

  1. Replies once in each of its open conversations, in turn: the seed inbox replies to your email, then your domain answers, then the seed replies once more. If the email landed in spam, it can move it to the inbox first (an option on the card). Emails that haven’t arrived are left to wait.
  2. Writes and sends new emails (1 to 10 a day, your choice) to the seed inboxes that have gone longest without one.

It writes with your default AI service. Its emails are tagged Auto in the email list, the card shows the last run and any problems, and Run now does today’s run straight away. It only continues conversations it started, so your hand-written threads are left alone. Gmail seed inboxes need Allow sending replies before it can reply from them.

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. For autopilot, also add CRON_SECRET: python -c "import secrets; print(secrets.token_urlsafe(32))".
  5. 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.
CRON_SECRETVercel, for autopilotShared secret Vercel Cron sends to /api/cron. Not needed with Docker.
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, Outlook, SMTP and the AI services 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.