Self-hosted MCP server that gives Claude.ai read/write access to IMAP/SMTP email accounts over streamable HTTP.
Claude.ai ──HTTPS──> Nginx (Proxmox host) ──HTTP:8765──> mailbridge-mcp (LXC)
├── IMAP/TLS ──> mail servers
└── SMTP/TLS ──> mail servers
Python 3.13, FastMCP 3.x, GitHub OAuth (for Claude.ai web access), structlog JSON logging. Runs as a systemd service inside a dedicated Proxmox LXC container (Debian 13 Trixie), proxied through Nginx with TLS.
11 MCP tools spanning read and write operations across multiple email accounts:
| Tool | Description |
|---|---|
imap_list_accounts |
List configured accounts with IDs and labels |
imap_list_folders |
List all IMAP folders with message/unread counts |
imap_list_messages |
Paginated message summaries (JSON or markdown) |
imap_get_message |
Full message content with HTML-to-plaintext, 50K body truncation |
imap_search_messages |
Search by text, sender, date range, flags |
imap_get_thread |
Thread reconstruction via Message-ID/References headers |
imap_send_email |
Compose and send with address validation and rate limiting |
imap_reply |
Reply with correct In-Reply-To/References threading |
imap_move_message |
Move between folders (COPY + delete pattern) |
imap_delete_message |
Move to Trash (auto-detected); never expunges |
imap_set_flags |
Mark read/unread, flagged/unflagged |
All tools return structured JSON errors on failure. Attachment content is never returned to the model (metadata only).
git clone https://github.com/L3DigitalNet/mailbridge-mcp.git
cd mailbridge-mcp
uv venv --python 3.13 .venv
source .venv/bin/activate
uv pip install -e ".[dev]"Copy and edit the config files:
cp .env.example .env
cp config/accounts.yaml.example config/accounts.yaml
# Edit both files with your credentialsThe server requires GitHub OAuth credentials to start (it refuses to run without authentication):
# Set these in .env:
# GITHUB_OAUTH_CLIENT_ID=<your GitHub OAuth App client ID>
# GITHUB_OAUTH_CLIENT_SECRET=<your GitHub OAuth App client secret>
# MCP_PUBLIC_HOST=<your public hostname>Run the server:
python -m mailbridge_mcp.server
# Listening on http://0.0.0.0:8765
# Health check: curl http://localhost:8765/healthClaude.ai's web interface requires OAuth 2.1 for remote MCP servers (it does not support raw Bearer tokens). This server uses FastMCP's built-in GitHubProvider to handle the OAuth flow.
Setup:
- Create a GitHub OAuth App with callback URL set to
https://<your-host>/auth/callback - Set
GITHUB_OAUTH_CLIENT_IDandGITHUB_OAUTH_CLIENT_SECRETin.env - When connecting from Claude.ai, enter just the base URL (e.g.,
https://mcp.example.com) - no/mcpsuffix - Claude.ai redirects you through GitHub to authorize, then you're connected
For Claude Code: Use claude mcp add with --transport http and --header for direct Bearer token access.
| Variable | Default | Purpose |
|---|---|---|
GITHUB_OAUTH_CLIENT_ID |
(required) | GitHub OAuth App client ID |
GITHUB_OAUTH_CLIENT_SECRET |
(required) | GitHub OAuth App client secret |
MCP_PUBLIC_HOST |
(required) | Public hostname for OAuth callbacks |
MCP_HOST |
0.0.0.0 |
Listen address |
MCP_PORT |
8765 |
Listen port |
IMAP_TIMEOUT |
30 |
Seconds per IMAP operation before timeout |
SMTP_TIMEOUT |
30 |
Seconds per SMTP send before timeout |
LOG_LEVEL |
INFO |
DEBUG, INFO, WARNING, or ERROR |
SMTP_RATE_LIMIT |
10 |
Max sends per minute across all tools (0 = unlimited) |
ACCOUNTS_CONFIG_PATH |
/etc/mailbridge-mcp/accounts.yaml |
Path to account definitions |
Each account specifies IMAP and SMTP connection details. Passwords use ${VAR} placeholders resolved from environment variables at startup; they never appear in the YAML.
accounts:
- id: personal
label: "Personal (me@example.com)"
imap:
host: mail.example.com
port: 993
tls: true
username: me@example.com
password: "${PERSONAL_IMAP_PASSWORD}"
smtp:
host: mail.example.com
port: 587
starttls: true
username: me@example.com
password: "${PERSONAL_SMTP_PASSWORD}"
default_from: "My Name <me@example.com>"The design doc (docs/mailbridge-mcp-design.md) has full deployment instructions. The short version:
-
LXC container: Create an unprivileged Debian 13 container on Proxmox with a static IP and Python 3.13.
-
Install: Clone the repo to
/opt/mailbridge-mcp, create a venv,pip install -e ., deploy.envandaccounts.yamlwithchmod 600. -
Systemd service: The unit runs as a dedicated
mailbridgeuser:ExecStart=/opt/mailbridge-mcp/.venv/bin/python -m mailbridge_mcp.server -
Nginx reverse proxy on the Proxmox host forwards HTTPS to the container. Disable global rate limiting for this vhost (OAuth flow is bursty):
location / { limit_req zone=api burst=200 nodelay; proxy_pass http://<lxc-ip>:8765; proxy_buffering off; proxy_read_timeout 300s; }
-
Connect Claude.ai: Settings > Integrations > Add custom integration. Enter your base URL (e.g.,
https://mcp.example.com). Claude.ai redirects to GitHub OAuth to authorize.
GitHub Actions runs ruff check, mypy, and pytest on every push. A deploy workflow SSH-deploys to the LXC on merge to main.
ruff check . # lint
ruff format . # format
mypy mailbridge_mcp/ # type check
pytest # run tests
pytest tests/test_config.py # single file
pytest -k "test_send" # single test by name
pytest --cov=mailbridge_mcp # coverage report108 tests covering config, IMAP client, SMTP client, formatters, auth middleware, tool behavior, MCP contract verification, and input validation.
- GitHub OAuth 2.1 via FastMCP's
GitHubProvider(for Claude.ai web access) - Server refuses to start without authentication configured
- Credentials in environment variables only, never in code or YAML
- Error messages sanitized: email addresses, hostnames, and IPs stripped before returning to the model
- IMAP folder names validated against safe character regex; search queries length-limited
- Email header injection prevented: subject and reply_to reject CR/LF
- SMTP rate limiting prevents runaway sends (10/min default)
- Delete operations move to Trash;
EXPUNGEis never called - Email bodies truncated at 50,000 characters to protect context windows
- Attachment binary content is never returned (metadata only)
- UID fields validated (positive integers only; lists capped at 1,000)
See SECURITY.md for vulnerability reporting.
MIT