Self-hosted IMAP/SMTP/CalDAV connector for Claude. Streamable HTTP MCP server with 13 tools for email and calendar. Works with Mailbox.
Overview
Self-hosted with multi-account support. A Streamable HTTP MCP server that lets Claude.ai read and write your email + calendar against any RFC-compliant mailbox. Built because every other Claude email connector targets Gmail. This one is for the rest of us — Mailbox.org, Fastmail, iCloud, Mailcow, iRedMail, Migadu, Nextcloud, your own Postfix box. If your provider speaks IMAP, SMTP and CalDAV, this works. One connector, all your inboxes. Every tool accepts an optional account: " " parameter to pick a mailbox; omit it to use the default account. So "list unread in INBOX of work account" vs "compare today's calendar across work and personal" both work in one connector. Hosted email-AI services need full mailbox access. That's a lot of trust to hand to a vendor. This connector flips the model: . - One Node process per mailbox - Credentials in a single .env file - One Bearer token gates every MCP call - Add the URL to Claude.ai once, done The server boots with — that's fine.
README
claude-mail-mcp
Self-hosted IMAP / SMTP / CalDAV connector for Claude with multi-account support. A Streamable HTTP MCP server that lets Claude.ai read and write your email + calendar against any RFC-compliant mailbox.
Built because every other Claude email connector targets Gmail. This one is for the rest of us — Mailbox.org, Fastmail, iCloud, Mailcow, iRedMail, Migadu, Nextcloud, your own Postfix box. If your provider speaks IMAP, SMTP and CalDAV, this works. One connector, all your inboxes.
What it does
Exposes 14 MCP tools to Claude:
Accounts (1)
| Tool | Purpose |
|---|---|
list_accounts |
List all configured mailboxes — id, label, default flag, From, CalDAV-enabled. Never returns credentials. |
Mail (9)
| Tool | Purpose |
|---|---|
list_folders |
Enumerate IMAP mailboxes (INBOX, Sent, …) |
list_messages |
Newest N messages in a folder |
search_messages |
Server-side IMAP search (from/to/subject/body/date/flags) |
get_message |
Full body + headers + attachment metadata |
send_message |
Send via SMTP, optionally copy to Sent folder |
create_draft |
Build RFC-822 and APPEND to Drafts |
mark_read |
Toggle \Seen flag |
move_message |
Move between folders |
delete_message |
Delete (destructive — prefer move to Trash) |
Calendar (4)
| Tool | Purpose |
|---|---|
list_calendars |
Discover CalDAV calendars |
list_events |
Events in a time window (recurrences expanded) |
create_event |
Add new event (writes to CalDAV) |
find_free_slot |
Compute free intervals across one or more calendars |
Every tool accepts an optional account: "" parameter to pick a mailbox; omit it to use the default account. So “list unread in INBOX of work account” vs “compare today’s calendar across work and personal” both work in one connector.
Why bring-your-own-server?
Hosted email-AI services need full mailbox access. That’s a lot of trust to hand to a vendor. This connector flips the model: you run it, you hold the credentials, no third party between Claude and your inbox.
- One Node process per mailbox
- Credentials in a single
.envfile - One Bearer token gates every MCP call
- Add the URL to Claude.ai once, done
Quick start
git clone https://github.com/maxx3250/claude-mail-mcp.git
cd claude-mail-mcp
npm install
cp .env.example .env
# Generate an AUTH_TOKEN and fill it into .env:
# echo "AUTH_TOKEN=$(openssl rand -hex 32)" >> .env
npm run build
npm start
The server boots with no mailboxes configured — that’s fine. Add them via the OAuth shim’s /settings UI (see Deployment) or hand-craft an accounts.json:
{
"version": 1,
"accounts": [
{
"id": "main",
"label": "Main",
"default": true,
"imap": { "host": "imap.mailbox.org", "port": 993, "user": "[email protected]", "pass": "secret", "tls": true },
"smtp": { "host": "smtp.mailbox.org", "port": 465, "user": "[email protected]", "pass": "secret", "tls": true },
"mail": { "defaultFrom": "[email protected]", "draftsFolder": "Drafts", "sentFolder": "Sent" }
}
]
}
Save as /root/.config/mail-mcp/accounts.json (chmod 600), or set ACCOUNTS_FILE=./accounts.json in .env for local dev. The backend re-reads via fs.watch, no restart needed.
Smoke test:
curl http://localhost:3220/health
# {"status":"ok","server":"claude-mail-mcp","version":"0.2.0","accounts":[{…}],…}
Connecting from Claude.ai
The server speaks the Streamable HTTP MCP transport. To use it from Claude.ai (web), you need an OAuth 2.1 + DCR + PKCE layer in front because Claude.ai does not support raw Bearer auth.
The recommended setup mirrors what claude-meta-mcp uses:
- Terminate TLS with nginx / Caddy on a public hostname (e.g.
mcp-mail.yourdomain.com) - Put a tiny OAuth shim in front that issues short-lived Bearer tokens after a basic-auth login
- Add the public URL as a Custom Connector in Claude.ai (Settings → Connectors → Add custom)
See docs/DEPLOYMENT.md for the full nginx + systemd + shim recipe.
For local testing without a shim, you can call /mcp directly with Authorization: Bearer from any MCP client that supports custom headers (Claude Desktop config, or a stdio bridge).
Provider notes
App passwords (mandatory on 2FA accounts)
| Provider | IMAP host | SMTP host | App password page |
|---|---|---|---|
| Mailbox.org | imap.mailbox.org:993 |
smtp.mailbox.org:465 |
App passwords aren’t needed; main password works |
| Fastmail | imap.fastmail.com:993 |
smtp.fastmail.com:465 |
|
| iCloud | imap.mail.me.com:993 |
smtp.mail.me.com:587 (STARTTLS) |
→ App-Specific Passwords |
| Gmail | imap.gmail.com:993 |
smtp.gmail.com:465 |
|
| Mailcow / iRedMail / Postfix | your server | your server | n/a |
CalDAV endpoints
| Provider | URL |
|---|---|
| Fastmail | https://caldav.fastmail.com/dav/principals/user/[email protected]/ |
| Mailbox.org | https://dav.mailbox.org/caldav/ |
| iCloud | https://caldav.icloud.com/ |
| Nextcloud | https://cloud.example.com/remote.php/dav/principals/users/USER/ |
If your provider doesn’t speak CalDAV, leave CALDAV_URL empty and the calendar tools won’t be registered. Mail still works.
Architecture
Claude.ai (web)
│ HTTPS + OAuth 2.1
▼
nginx (TLS, /health passthrough)
│
├──▶ /mcp/* ─▶ OAuth shim (Port 3212) ──▶ Bearer ──▶ this server (Port 3220)
└──▶ /health ────────────────────────────────────────▶ this server (Port 3220)
this server
├── ImapClient ──▶ imapflow ──▶ IMAP server (993/143)
├── SmtpClient ──▶ nodemailer ─▶ SMTP server (465/587)
└── CalDavClient ──▶ tsdav ──▶ CalDAV server
Everything is one Node process. IMAP holds a single long-lived connection with per-call mailbox locks. SMTP and CalDAV are stateless per call.
Security model
See SECURITY.md for the threat model and docs/HARDENING.md for the full operator checklist.
Defaults in one sentence: TLS via Let’s Encrypt + HSTS + rate-limited htpasswd + OAuth 2.1 with CSRF guard + non-root systemd unit with ProtectSystem=strict + kernel-level loopback-only filter + credentials chmod 600 owned by a dedicated mailmcp user.
Quick summary
- Transport: TLS 1.3 (Let’s Encrypt, auto-renew), HSTS,
X-Frame-Options: DENY,X-Content-Type-Options: nosniff,Referrer-Policy: no-referrer,X-Robots-Tag: noindex. - Auth: OAuth 2.1 + DCR + PKCE + JWT (RS256, 1h access, 30d refresh) gated by htpasswd login. CSRF guard on
/settingsPOST. Brute-force throttled at nginx (10 req/min on auth endpoints). - Process: Both services run as a dedicated non-root
mailmcpsystem user (no shell). Full systemd hardening:NoNewPrivileges,ProtectSystem=strict,ProtectHome,ProtectKernel*,ProtectClock,ProtectHostname,ProtectProc=invisible,RestrictNamespaces,LockPersonality,SystemCallFilter=@system-service ~@privileged @resources,MemoryMax=512M. - Network: Backend + shim bound to
127.0.0.1only. Shim also blocks non-loopback at kernel level (IPAddressDeny=any). UFW default-deny on the host. - Storage: Credentials chmod 600, owned by
mailmcp, in/var/lib/mail-mcp/. htpasswd file chmod 640 (not world-readable)..envchmod 640root:mailmcp. - Output:
list_accountsreturns id/label/From — never credentials. Logs never include passwords or Bearer tokens. - Destructive tools (
delete_message) document irreversibility so Claude.ai surfaces a confirmation step. Prefermove_messageto a Trash folder for reversibility.
Full threat-model walkthrough and operator hardening checklist in docs/HARDENING.md. Reporting issues: see SECURITY.md.
Roadmap
- v0.2 ✅ — Multi-account per deployment, browser setup flow (this release)
- v0.3 — Threading-aware
list_threadstool, attachment download as base64, calendar invitation (iMIP) sending - v0.4 — CardDAV (contacts), JMAP support as an alternative to IMAP for Fastmail/Topicbox
- v1.0 — Audit log, Prometheus metrics, rate limiting, hardened deployment guide
License
MIT — see LICENSE.
Acknowledgements
- imapflow — modern Promise-based IMAP client
- nodemailer — the only Node SMTP client worth using
- tsdav — clean TypeScript WebDAV/CalDAV/CardDAV
- ical.js — battle-tested iCalendar parser
- @modelcontextprotocol/sdk — Anthropic’s official MCP SDK
Built by Markus Stöger — WooCommerce, headless commerce and AI integration.
Install
This server does not publish a one-line install command.
Open the repository installation guide New to MCP? Start with the mcpskill.org guide →