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 Gmail or Google Workspace inboxes you own to act as seed inboxes.
- A Google Cloud project for the OAuth client that connects those inboxes (free).
- Optionally, a Gemini API key for drafting emails and reading your website.
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.
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 you wrote or approved, from that inbox. |
gmail.settings.basic | Creating “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:
- 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. |
| Mark important rule | An inbox rule that marks future mail from your sender as high importance. |
| 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.
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
- 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. - 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. |
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, 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.