mcp-inbox
MCP server that gives any MCP-capable agent read/write access to your IMAP inbox, with a local SQLite cache for fast responses and IMAP IDLE for real-time updates.
Works with any IMAP/SMTP provider: Gmail, Outlook, Fastmail, iCloud, Proton Mail (via Bridge), Dovecot, hosted Exchange, self-hosted mail servers. Tools for listing and searching mail, composing and sending, managing drafts, inspecting attachment metadata. Cache stays in sync with the server via CONDSTORE + IDLE so most reads serve from local SQLite without a network round-trip.
On attachments: imap_get_email surfaces attachment metadata (filename, content type, size) by default - enough for the agent to describe what's attached without any download. When the agent genuinely needs to read content inside an attachment (search a PDF, extract a table from a CSV), imap_get_attachment fetches the bytes inline as base64 for one response. Nothing is cached on disk; re-requesting the same attachment refetches from the server.
Requirements
- Node.js 24 LTS or newer (
node --versionshould printv24.xor higher). - An IMAP/SMTP account. Gmail and Outlook users need an app password, not the account password - see Provider notes.
Configure
mcp-inbox reads credentials from environment variables. At minimum you need three:
IMAP_USER=you@example.com
IMAP_PASSWORD=your-app-password
IMAP_HOST=imap.example.com
The full list of variables (ports, TLS flags, cache tuning, IDLE folders) lives in .env.example.
How those env vars reach the server process depends on which MCP client you use. Every client below lets you set env per-server.
Compatible clients
Works with any MCP-capable client over stdio. Confirmed integrations:
| Client | Notes |
|---|---|
| Claude Code (CLI) | claude mcp add |
| Claude Desktop | claude_desktop_config.json |
| Cursor | Settings UI or ~/.cursor/mcp.json |
| VS Code | Native MCP via .vscode/mcp.json |
| Cline | cline_mcp_settings.json |
| Continue.dev | ~/.continue/config.yaml |
| Codex CLI (OpenAI) | ~/.codex/config.toml |
| Zed | context_servers in editor settings |
| Goose (Block) | ~/.config/goose/config.yaml |
| Thunderbird | Via Claude Email Search plugin — Claude CLI only |
For per-client config snippets see docs/clients.md.
Provider notes
Gmail / Google Workspace
Use an app password, not your account password. Requires 2-Step Verification turned on first.
IMAP_HOST=imap.gmail.com
IMAP_PORT=993
SMTP_HOST=smtp.gmail.com
SMTP_PORT=465
SMTP_SECURE=true
Outlook / Microsoft 365
IMAP_HOST=outlook.office365.com
IMAP_PORT=993
SMTP_HOST=smtp.office365.com
SMTP_PORT=587
SMTP_SECURE=false
Outlook rejects the default SMTP_PORT=465 setting - both SMTP_PORT=587 and SMTP_SECURE=false are required. Personal accounts need an app password if 2FA is on.
Fastmail
Use an app password (account-level passwords are rejected).
IMAP_HOST=imap.fastmail.com
SMTP_HOST=smtp.fastmail.com
SMTP_PORT=465
iCloud
Requires an app-specific password from your Apple ID security settings.
IMAP_HOST=imap.mail.me.com
SMTP_HOST=smtp.mail.me.com
SMTP_PORT=587
SMTP_SECURE=false
Proton Mail
Proton Mail requires Proton Mail Bridge running locally; point mcp-inbox at the Bridge's advertised host/port (usually 127.0.0.1:1143 for IMAP).
Troubleshooting
"IMAP authentication failed"
You're almost certainly using your account password instead of an app password. See the provider-specific links above.
The client can't find npx / spawn ENOENT
On Windows, some older clients don't resolve .cmd shims. Wrap the command:
"command": "cmd",
"args": ["/c", "npx", "-y", "@kbzowski/mcp-inbox"]
Or install globally and use the binary directly:
npm install -g @kbzowski/mcp-inbox
# then set command: "mcp-inbox" (no args)
Passwords with shell-special characters
If your password contains $, `, !, or a backslash, put it in an env file / config JSON rather than passing it on a shell command line. The config files above handle this correctly; claude mcp add --env IMAP_PASSWORD=... works if you single-quote the value in your shell.
First-run takes a while
The first npx -y @kbzowski/mcp-inbox invocation downloads the package. Subsequent runs are cached. If it looks hung on first boot, check your network.
"mcp-inbox requires Node.js 24 or later"
The server requires Node 24 because node:sqlite (the built-in SQLite module it uses) gained its full API in Node 24. Run node --version to check which version your MCP client is using, and upgrade to Node 24 LTS if needed: https://nodejs.org/en/download
Verify the connection manually
From any shell, set the env vars and run:
IMAP_USER=you@example.com IMAP_PASSWORD=... IMAP_HOST=imap.gmail.com npx -y @kbzowski/mcp-inbox
On success you'll see JSON log lines on stderr: booting mcp-inbox and mcp-inbox ready. The server then idles on stdin (waiting for MCP JSON-RPC). Hit Ctrl+C to stop.
Tool catalog
All tools are prefixed imap_ so they don't collide with other email MCPs. Every read tool accepts response_format: "markdown" | "json" (default markdown) and max_staleness_seconds (default 60 - serves from cache when fresh, syncs otherwise).
Discovery
imap_list_folders- list every mailbox with its path, delimiter, and RFC 6154 special-use attribute (\Drafts,\Sent,\Trash,\Junk).imap_list_emails(folder?, limit?, offset?, unseen_only?, since_date?, before_date?)- paginated envelope list, newest first. Defaults toINBOX, 20 per page.imap_search_emails(folder?, subject?, from?, to?, body?, unseen?, since_date?, before_date?)- IMAP SEARCH on the server, returns matching envelopes from the cache. At least one criterion required.
Reading
imap_get_email(folder, uid)- full message: headers, plain text, HTML, and attachment metadata (filename, content type, size).imap_list_drafts(folder?, limit?, offset?)- same as list_emails but auto-resolves the Drafts folder via SPECIAL-USE.imap_get_draft(uid, folder?)- full draft content by UID.imap_get_attachment(folder, uid, filename? | part_id?, max_inline_mb?)- download an attachment as base64 bytes, inline in the response. Memory-only, no disk cache. Default cap is 5 MB; raise viamax_inline_mb(up to 50).
Flagging / filing
imap_mark_read(folder, uid)- add\Seen. Idempotent.imap_mark_unread(folder, uid)- remove\Seen. Idempotent.imap_move_to_folder(folder, uid, destination)- IMAP MOVE with COPY+EXPUNGE fallback.imap_delete_email(folder, uid, hard_delete?)- defaults to move-to-Trash.hard_delete: truepermanently expunges.
Composing
imap_create_draft(to, subject, body?, html?, cc?, bcc?, from?)- appends a new draft to the Drafts folder with the\Draftflag. nodemailer handles the RFC 2822 construction, so non-ASCII subjects, long bodies, and multipart text+HTML all just work.imap_update_draft(uid, to, subject, body?, html?, cc?, bcc?, from?)- replaces an existing draft. Append-then-delete: the new draft is written first, only then is the old UID removed. A failure in the middle never loses the draft.
Sending
imap_send_email(to, subject, body?, html?, cc?, bcc?, from?)- SMTP send + best-effort append to the Sent folder so the message shows up in the user's mail client.imap_send_draft(uid, folder?)- fetches raw source of the draft, sends it exactly as written (preserves attachments and formatting), then deletes the draft.imap_reply(folder, uid, body?, html?, reply_all?, cc?, bcc?, from?)- preserves threading viaIn-Reply-To/References. Subject gets aRe:prefix.imap_forward(folder, uid, to, body?, cc?, bcc?, from?)- quotes the original inline,Fwd:subject prefix.
Tool annotations
Every tool carries MCP annotations so clients can gate destructive actions:
| Tool | readOnly | destructive | idempotent |
|---|---|---|---|
| list_folders / list_emails / get_email / search_emails / list_drafts / get_draft / get_attachment | ✓ | ✓ | |
| mark_read / mark_unread | ✓ | ||
| move_to_folder / delete_email | ✓ | ||
| create_draft / update_draft | |||
| send_email / send_draft / reply / forward | ✓ |
License
MIT. Copyright (c) 2026 Krzysztof Bzowski. See LICENSE.