mail-shadow-mcp
Health Warn
- License — License: Apache-2.0
- Description — Repository has a description
- Active repo — Last push 0 days ago
- Low visibility — Only 7 GitHub stars
Code Fail
- Hardcoded secret — Potential hardcoded credential in internal/config/testdata/valid.yaml
Permissions Pass
- Permissions — No dangerous permissions requested
No AI report is available for this listing yet.
MCP server for structured, read-only email access. Exposes a minimal, auditable API surface — AI agents can search and read emails, but cannot send, delete, or modify your mailbox.
Structured email access for AI agents — with built-in safety guarantees.
mail-shadow-mcp is a Model Context Protocol (MCP) server that creates a local shadow copy of your IMAP mailboxes in a SQLite database. AI agents query the local database through well-defined MCP tools instead of connecting directly to your IMAP server.
[Remote IMAP Server] ──IMAP──▶ [Sync Engine] ──▶ [SQLite FTS5] ◀──▶ [MCP Server] ◀──▶ [AI Agent]
Features
- Local shadow database — emails are synced into a local SQLite database; the AI agent never connects to your IMAP server directly
- Read-only — no
STORE,APPEND, orEXPUNGEcommands; your mailbox is never modified - Incremental sync — only fetches messages newer than the last known UID
- Full-text search — SQLite FTS5 index for fast body-text queries
- Multi-account — sync any number of IMAP accounts simultaneously
- IMAP IDLE — optional real-time push notifications; new mail detected within seconds instead of waiting for the next poll interval
- Read/replied status —
is_readandis_repliedflags synced from IMAP and exposed as filters - Thread view —
get_threadwalks full email conversations viaMessage-ID/In-Reply-Toheaders - Paginated results — all list tools return
total_countso agents can page through large result sets - On-demand attachments — attachment files are fetched from IMAP only when explicitly requested
- Safe soft-delete — when an agent calls
delete_mail, the MCP server performs an IMAP MOVE to a configurable trash folder; nothing is ever permanently deleted
Safety: Nothing Is Ever Really Deleted
mail-shadow-mcp gives AI agents a delete_mail tool, but this tool never issues a destructive IMAP command. Here is exactly what happens when an agent calls it:
- The MCP server looks up the email in the local database.
- It opens a short-lived IMAP connection and executes IMAP MOVE — moving the message to the
trash_folderyou specify inconfig.yaml(e.g."llm_delete"). - The local database entry is removed so the agent can no longer see the mail in future queries.
- The email remains intact on the IMAP server, safely tucked away in the trash folder. You can inspect, restore, or permanently delete it yourself at any time.
The AI agent has no direct IMAP access. It cannot expunge messages, empty folders, or issue any write command other than this controlled move. If trash_folder is not configured for an account, delete_mail returns an error and does nothing.
MCP Tools
| Tool | Description |
|---|---|
list_accounts_and_folders |
List all synced accounts and their folders |
get_recent_activity |
N most recent emails with optional filters (is_read, has_attachments, pagination) |
get_email_content |
Full body text, read/replied status, and attachment list for a single email |
search_emails |
FTS5 full-text search with subject/sender/date/folder/is_read/sent_by filters |
get_thread |
All emails in the same thread as a given email, sorted by date ascending |
download_attachments |
Fetch attachment files from IMAP and save them to disk |
get_download_link |
Generate a temporary HTTP download URL for attachments (optional fallback) |
delete_mail |
Soft-delete an email by moving it to a configured trash folder (IMAP MOVE, no permanent deletion) |
Quick Start
1. Build
make build # current platform
make release # cross-compile for all platforms into dist/
Requires Go 1.25+.
2. Configure
Copy the example config and fill in your IMAP credentials:
cp config.example.yaml config.yaml
sync_interval_min: 15
database:
path: "data/mail.db"
attachment_dir: "data/attachments"
# Optional: structured JSON logs for Loki / Elasticsearch pipelines.
# log_format: json # json | text (default: text)
# log_level: info # debug | info | warn | error (default: info)
# log_file: "/var/log/mail-shadow-mcp.log" # omit to use stderr
# Optional: lightweight HTTP server for temporary attachment download links.
# Only enable this if you need the get_download_link MCP tool (e.g. as a
# fallback when the AI agent cannot transfer files via its normal channels).
fileserver_port: 8787 # TCP port to listen on (disabled if omitted)
fileserver_ttl_min: 15 # minutes before a link expires (default: 15)
fileserver_host: "localhost" # hostname/IP shown in generated URLs
accounts:
- id: "[email protected]"
host: "imap.example.com"
port: 993
username: "[email protected]"
password: "$WORK_IMAP_PASS" # or plain text
folders: ["INBOX", "Archive"] # only sync the mentioned folders
# idle_folders: ["INBOX"] # optional: IMAP IDLE for real-time push on these folders
# trash_folder: "llm_delete" # optional: target for delete_mail (soft-delete via IMAP MOVE)
- id: "[email protected]"
host: "imap.example.com"
port: 993
username: "[email protected]"
password: "$PRIVATE_IMAP_PASS" # or plain text
Credentials can be stored as plain text or as $ENV_VAR references that are resolved at runtime.
3. Run
# Start the MCP server (syncs on startup, then every sync_interval_min minutes)
./mail-shadow-mcp serve
# One-shot sync without starting the server
./mail-shadow-mcp sync
# Query from the command line (output is JSON)
./mail-shadow-mcp query --subject "invoice" --body "Q1"
./mail-shadow-mcp query -q "budget" --attachments only # only emails with attachments
./mail-shadow-mcp query --recent --attachments none # recent emails without attachments
./mail-shadow-mcp query --recent --limit 10 --offset 10 # page 2
# Download attachments for a specific email
./mail-shadow-mcp attachments --id "[email protected]:INBOX:42"
Docker
Pre-built multi-architecture images (linux/amd64, linux/arm64) are published to the GitHub Container Registry on every release:
docker pull ghcr.io/dryas/mail-shadow-mcp:latest
Required volumes
| Volume | Purpose |
|---|---|
/config |
Must contain config.yaml. Mount read-only. |
/data |
Persistent storage for the SQLite database and downloaded attachments. Survives container restarts. |
Quick Docker run
Step 1 — prepare a config.yaml with transport: http and paths pointing to /data:
transport: http # StreamableHTTP — required for Docker
http_addr: ":8080"
database:
path: "/data/mail.db"
attachment_dir: "/data/attachments"
accounts:
- id: "[email protected]"
host: "imap.example.com"
port: 993
username: "[email protected]"
password: "$WORK_IMAP_PASS"
Step 2 — run the container:
docker run -d \
--name mail-shadow-mcp \
-v ./config.yaml:/config/config.yaml:ro \
-v mail-shadow-data:/data \
-p 8080:8080 \
ghcr.io/dryas/mail-shadow-mcp:latest
The MCP server is now reachable at http://localhost:8080/mcp.
docker-compose example
services:
mail-shadow-mcp:
image: ghcr.io/dryas/mail-shadow-mcp:latest
restart: unless-stopped
ports:
- "8080:8080"
volumes:
- ./config.yaml:/config/config.yaml:ro # your config — mount read-only
- mail-shadow-data:/data # persistent DB + attachments
environment:
- WORK_IMAP_PASS=your_password_here # referenced as $WORK_IMAP_PASS in config
volumes:
mail-shadow-data:
Connecting an AI agent to the Docker container
Point your MCP client at http://localhost:8080/mcp using the StreamableHTTP transport:
{
"mcpServers": {
"mail_shadow": {
"url": "http://localhost:8080/mcp"
}
}
}
Build the image yourself
docker build --build-arg VERSION=dev -t mail-shadow-mcp .
Integrating with an AI Agent
Configure your MCP client to launch the server via stdio.
Example for Claude Desktop (claude_desktop_config.json):
{
"mcpServers": {
"mail_shadow": {
"command": "/path/to/mail-shadow-mcp",
"args": ["serve", "--config", "/path/to/config.yaml"]
}
}
}
Example for Hermes Agent (config.yaml):
mcp_servers:
mail_shadow:
command: "/path/to/mail-shadow-mcp"
args: ["serve", "--config", "/path/to/config.yaml"]
Example for OpenClaw (~/.openclaw/openclaw.json):
{
"mcpServers": {
"mail_shadow": {
"command": "/path/to/mail-shadow-mcp",
"args": ["serve", "--config", "/path/to/config.yaml"],
"transport": "stdio"
}
}
}
TLS Modes
tls_mode |
Port | Description |
|---|---|---|
tls |
993 | Implicit TLS (default) |
starttls |
143 | STARTTLS upgrade |
none |
143 | No encryption — localhost/testing only |
Set tls_skip_verify: true to accept self-signed certificates.
IMAP IDLE (Real-time Push)
By default, mail-shadow-mcp polls for new messages every sync_interval_min minutes. For folders where you want near-instant notifications, enable IMAP IDLE:
accounts:
- id: "[email protected]"
# ...
idle_folders: ["INBOX"] # IDLE runs on top of regular polling
- One dedicated IMAP connection is opened per entry in
idle_folders - When the server sends an
EXISTSnotification, a sync is triggered immediately - Regular polling continues unchanged for all other folders
- Falls back to polling automatically if the server does not support IDLE
- Exponential backoff (30 s → 5 min) on persistent connection errors
Attachment Download Server
The optional built-in HTTP server lets the AI agent generate temporary, single-use download links for attachment files — useful as a fallback when the agent cannot transfer files through its normal communication channels (e.g. WhatsApp, email).
Enable it in config.yaml:
fileserver_port: 8787 # TCP port to listen on
fileserver_ttl_min: 15 # minutes before a link expires (default: 15)
fileserver_host: "localhost" # hostname/IP shown in generated URLs
When enabled, the get_download_link MCP tool becomes available. It downloads the attachments, saves them to attachment_dir, and returns one temporary URL per file:
[
{
"file": "data/attachments/[email protected]/INBOX/42/invoice.pdf",
"url": "http://localhost:8787/dl/3f8a1c.../invoice.pdf"
}
]
Each link is single-use and expires after fileserver_ttl_min minutes. The tool description instructs the AI agent to prefer direct file transfer and only fall back to this mechanism when necessary.
License
Apache 2.0 — see LICENSE for details.
Copyright (c) 2026 Benjamin Kaiser.
Reviews (0)
Sign in to leave a review.
Leave a reviewNo results found