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 with Compose, or a Vercel account plus a Neon Postgres database.
- A domain that can send email over SMTP, for example through Brevo, Postmark, Amazon SES or Mailgun, with SPF and DKIM set up.
- One or more inboxes you own (or that friends lend you) to act as seed inboxes: Gmail and Google Workspace, Outlook and Microsoft 365, Yahoo, iCloud, AOL or any IMAP inbox.
- A free Google Cloud project for Gmail inboxes, and a free Microsoft Entra app registration for Outlook inboxes.
- Optionally, an API key for one AI service (several have free tiers) for drafting emails and reading your website. Autopilot needs one.
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:
- In Google Cloud Console, create a project and enable the Gmail API.
- Open Google Auth Platform (OAuth consent screen). Choose External, fill in the app name and your email, and leave the app in Testing.
- Under Audience → Test users, add every Gmail address you’ll use as a seed inbox.
- Under Data access, add these scopes:
gmail.modify,gmail.sendandgmail.settings.basic. - 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). - Copy the client ID and secret into Settings → Google sign-in, or set
GOOGLE_CLIENT_IDandGOOGLE_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:
| Scope | Used for |
|---|---|
gmail.modify | Reading the labels on one specific message to report placement, and reading filters. Messages are never moved or relabelled. |
gmail.send | Sending replies from that inbox: ones you send, or autopilot’s when it’s on. |
gmail.settings.basic | Creating 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:
- 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.
- Add a Web platform redirect URI: the one shown under Settings → Microsoft sign-in, for example
https://app.example.com/api/microsoft_oauth_callback. - Under Certificates & secrets, create a client secret and copy its value.
- Under API permissions, add delegated Microsoft Graph permissions:
Mail.ReadWrite,Mail.Send,MailboxSettings.ReadWrite,User.Readandoffline_access. - Paste the Application (client) ID and secret into Settings → Microsoft sign-in, or set
MICROSOFT_CLIENT_IDandMICROSOFT_CLIENT_SECRET.
What Outlook supports:
| Action | How |
|---|---|
| Placement | Junk Email folder → Spam; Inbox with Focused or Other. |
| Not spam | Graph markAsNotJunk: moves the email to the Inbox and unblocks the sender. |
| Mark important | Sets the email’s importance to high. |
| Always Focused | A Focused Inbox override for your sender address, plus the Mark important rule below, in one click. |
| Mark important rule | An inbox rule that marks future mail from your sender as high importance. Also available on its own. |
| Replies | Sent 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.
- Yahoo Mail: Account security → Generate app password. Servers
imap.mail.yahoo.comandsmtp.mail.yahoo.com. Spam goes to the Bulk folder. - iCloud Mail: account.apple.com → Sign-In and Security → App-Specific Passwords. Servers
imap.mail.me.comandsmtp.mail.me.com. - AOL Mail: Account security → Generate app password. Servers
imap.aol.comandsmtp.aol.com. - Other providers: enter the IMAP and SMTP servers and ports yourself.
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.
| Service | Cost | Default model | Get a key |
|---|---|---|---|
| Google Gemini | Free tier | gemini-3.8-flash | Google AI Studio |
| Anthropic Claude | Paid API credit | claude-opus-5-5 | Anthropic Console |
| OpenAI | Paid API credit | gpt-5-mini | OpenAI Platform |
| OpenRouter | Free models | openrouter/free | OpenRouter |
| Groq | Free tier | openai/gpt-oss-120b | GroqCloud |
| Cloudflare Workers AI | Free daily allowance | @cf/meta/llama-3.3-70b-instruct-fp8-fast | Cloudflare 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.
- Cloudflare Workers AI needs your account ID as well as an API token. On the Cloudflare dashboard, open AI → Workers AI → Use REST API: the account ID is shown there, and Create a Workers AI API Token makes the key.
- Groq: if it says the key can’t use a model, check the model is allowed in your Groq project’s settings, or switch to
llama-3.3-70b-versatile. - OpenRouter: any model ID ending in
:freecosts nothing.
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:
- 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.
- 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.
- Docker: nothing to set up. The server runs it itself.
- Vercel: add a
CRON_SECRETenvironment variable (any long random string) and redeploy.vercel.jsonalready schedules/api/cron, and Vercel sends the secret with each call. On the Hobby plan the run can start any time within that hour.
Deploy to Vercel + Neon
- Fork the repository and import it as a new Vercel project (framework preset: Other).
- In the project’s Storage tab, add a Neon Postgres database. This sets
DATABASE_URL. - Generate the two keys and add them as environment variables:
No Python?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))"openssl rand -base64 32 | tr '+/' '-_'also makes a valid encryption key. - For autopilot, also add
CRON_SECRET:python -c "import secrets; print(secrets.token_urlsafe(32))". - Deploy, open the deployment URL, and sign in with the setup key. Add a custom domain such as
app.example.comin 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
| Variable | Needed | Purpose |
|---|---|---|
DATABASE_URL | Always (Compose sets it) | Postgres connection string. |
APP_ENCRYPTION_KEY | Vercel | Fernet key for secrets at rest. Generated automatically with Docker. |
ADMIN_SETUP_KEY | Vercel | One-time key for creating the admin login. Generated automatically with Docker. |
GOOGLE_CLIENT_IDGOOGLE_CLIENT_SECRET | Optional | OAuth client. Overrides the values saved in Settings. |
MICROSOFT_CLIENT_IDMICROSOFT_CLIENT_SECRET | Optional | Microsoft app registration. Overrides the values saved in Settings. MICROSOFT_TENANT defaults to common. |
CRON_SECRET | Vercel, for autopilot | Shared secret Vercel Cron sends to /api/cron. Not needed with Docker. |
APP_URL | Optional | Public base URL, if it can’t be worked out from the request. |
GOOGLE_REDIRECT_URI | Optional | Overrides the derived APP_URL/api/google_oauth_callback. |
DOMAIN, TRUST_PROXY | HTTPS profile | Domain for Caddy; trust its forwarded headers. |
POSTGRES_PASSWORD, PORT | Optional | Database 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.