mailpouch
mailpouch is a permission-gated MCP server that gives AI agents typed access to a Proton Mail / IMAP mailbox through Proton Bridge — reading, searching, sending, organizing, scheduling, and analyzing email without handing credentials to a third party.
Read & browse mail — page through folders (
get_emails), fetch full message content with attachment metadata (get_email_by_id,get_thread), list folders and Proton labels with counts (get_folders,list_labels), check unread counts, optionally scoped to a chosenaccount_idin multi-account setups.Search — live server-side IMAP search by sender, recipient, subject, body, date range, size, read/answered/starred/draft status, and attachments (
search_emails); plus a local BM25-ranked FTS5 index supporting phrases, boolean, prefix, and column filters (fts_search,fts_rebuild,fts_status) so terms never leave the machine.Send & respond — compose new mail with CC/BCC, HTML, priority, reply-to, and base64 attachments (
send_email), reply or reply-all (reply_to_email), forward with preserved headers (forward_email), and verify SMTP withsend_test_email.Drafts & scheduling — save incomplete drafts to Drafts (
save_draft), queue emails for future delivery up to 30 days out (schedule_email,list_scheduled_emails,cancel_scheduled_email), view natively Proton-scheduled mail, and set no-reply follow-up reminders (remind_if_no_reply,check_reminders,list_pending_reminders,cancel_reminder).Organize mail — move messages between folders (
move_email,archive_email,move_to_folder,bulk_move_emails), mark read/unread/answered/forwarded/starred singly or in bulk, and add or remove Proton labels (move_to_label,remove_label, plus bulk variants).Delete with guardrails — trash messages (
delete_email,bulk_delete_emails,move_to_trash,move_to_spam) or permanently purge Trash (empty_trash); these require{ confirmed: true }or MCP elicitation, and deletion always moves to Trash exceptempty_trash.Folders & labels management — create, rename, and delete custom folders/labels (
create_folder,rename_folder,delete_folder) and refresh the folder cache (sync_folders).Analytics & contacts — inbox/sent dashboards, top senders and recipients, peak hours, attachment stats, measured response times (
get_email_stats,get_email_analytics,get_volume_trends), ranked contact lists with sent/received counts and inferred organizations (get_contacts), and per-address relationship profiles (get_correspondence_profile).Content extraction — parse action items from an email body (
extract_action_items), parse ICS/VEVENT meeting invites into structured details (extract_meeting), and download attachment binaries as base64 (download_attachment).System & diagnostics — check live IMAP/SMTP health and TLS mode (
get_connection_status), diagnose install state end-to-end with the always-availablesetup_status, read redacted server logs, report version, clear caches, sync mail into the cache, and launch Proton Bridge (start_bridge).Server lifecycle — restart or shut down the MCP server (
restart_server,shutdown_server), both destructive and confirmation-gated.Permission escalation flow — request temporary elevated access via
request_permission_escalation, then pollcheck_escalation_status; approval requires a human in the settings UI, so the agent can never widen its own privileges.
Allows AI agents to read, compose, and manage encrypted Proton Mail inboxes through Proton Bridge, with permission-gated and audit-logged access.
Provides tools for managing SimpleLogin aliases, enabling alias creation, deletion, and listing.
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@mailpouch show my unread emails from today"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
mailpouch
mailpouch is an MCP server that gives AI agents a typed, permission-gated, audit-logged tool surface over private-mail providers — Proton Mail (via Proton Bridge) and plain IMAP.
The pitch in one line: if you picked Proton Mail because you didn't want a third party reading your inbox, you don't suddenly want to hand a chatbot OAuth access to that same inbox so it can triage on your behalf. The usual "connect your email" integrations route everything through someone else's servers and ask for blanket scopes. Hand-rolled IMAP inside the agent is worse — no permission boundary, no audit trail, and the model holds your credentials in its context window. Neither option respects why you chose the provider in the first place.
mailpouch runs locally and speaks to Proton Bridge over a TLS socket on your own machine; nothing leaves the box unless you asked it to. Up to 86 tools (83 canonical plus 3 meta-tools) cover reading, sending, drafts, folders, search, analytics, optional aliases and Proton Pass, plus system control. Tool tiers and capability-aware listing keep unconfigured companion tools out of an agent's context. Every connecting client gets its own grant with folder allowlists, IP pins, per-tool rate caps, expiry, and account binding — all hashed-args in the audit log, never the values. Delete, trash, spam, alias removal, and sensitive Pass retrieval round-trip through MCP elicitation for human confirmation before they execute.
It is real because the primitives are real: OAuth 2.1 with PKCE S256, RFC 7591 dynamic client registration, RFC 8707 resource indicators, RFC 9728 protected-resource metadata, and an OAuth client_credentials grant so headless agents authenticate too — every agent gets its own gated, revocable identity. Credentials live in the OS keychain. A local FTS5 index with BM25 ranking handles phrase, boolean, prefix, and column-filter queries so your search terms never leave your laptop. Desktop notifications use native osascript / notify-send / powershell.exe with no added dependency; webhook dispatch auto-detects CloudEvents 1.0, Slack, or Discord, signs with HMAC, and retries with eight-attempt exponential backoff. So how do you point it at your Bridge install and wire up a client?
Read, compose, and manage your encrypted Proton Mail inbox from any AI assistant — over stdio or remote HTTP — with human-controlled permissions.
⚠ Proton Terms of Service Notice
This is an unofficial third-party tool that connects to Proton Mail through Proton Bridge's local IMAP/SMTP surface. It is not affiliated with, endorsed by, or authored by Proton AG.
Proton's Terms of Service (proton.me/legal/terms) §2.10 prohibits "accessing the Services through automated means (including but not limited to bots, scripts, or similar technologies)". The textual reading covers agentic / scripted workloads against Bridge even though Bridge itself is a sanctioned surface.
This server is designed to keep access user-initiated, not autonomous:
Default permission preset is
read_only. Sending, deletion, and folder mutation require explicit user opt-in via the settings UI.Destructive tools (delete / empty_trash / move-to-trash / move-to-spam / alias deletion / sensitive Pass retrieval) require explicit confirmation. With MCP elicitation-capable clients, the server prompts the user out-of-band before executing; non-elicitation clients must pass
{ confirmed: true }.Elevated permissions require out-of-band human approval (settings UI button or terminal), not an agent-only grant.
The settings UI shows a first-run ToS acknowledgement the user must click through before credentials are accepted.
You remain the operator of your Proton account. Running this server against your own account is your decision to make under Proton's ToS; the authors disclaim responsibility for ToS compliance on your behalf.
Related MCP server: mail-mcp
What It Does
Proton Mail encrypts your email end-to-end, which means no third-party API can read it. Proton Bridge solves this by decrypting email locally. This MCP server connects to Bridge and gives Claude (or any MCP host) structured, permission-gated access to your inbox.
Your emails are decrypted on your own machine by Proton Bridge. Message content is normally cached in memory and cleared on restart, but scheduled outbound messages persist recipients, subjects, and bodies in ~/.mailpouch-scheduled.json. The optional local FTS5 index uses ~/.mailpouch-fts.db (or MAILPOUCH_FTS_DB) as its base/legacy path, then stores live account indexes under the derived private <base-name>.accounts/<account-hash>.db locations. Protect or clear those stores as appropriate. You control exactly what the AI can do through a preset permission system with human-gated escalation for anything sensitive.
Quick Start (zero to running)
Prereq: Proton Bridge installed, running, and signed in.
Add the MCP server to your client. This one form works whether or not mailpouch is globally installed:
{ "mcpServers": { "mailpouch": { "command": "npx", "args": ["-y", "mailpouch"] } } }Configure Bridge credentials — either path writes
~/.mailpouch.json(+ OS keychain):Interactive wizard:
npx -y mailpouch-settingsNon-interactive (scriptable / agent-driven):
npx -y mailpouch setup --username you@proton.me --password-stdin(paste the Proton Bridge password — Bridge → Settings → IMAP/SMTP → Password — not your Proton login password)
Verify:
npx -y mailpouch doctor— prints the exact next step until it reportsready. (Agents can call the always-availablesetup_statustool for the same diagnosis.)Approve the agent. On first connect, every client is gated behind a one-time human Approve/Deny — open the settings UI (
http://localhost:8766/#/agents) and click Approve. This is expected, not an error.
That's it. The sections below cover everything in depth.
Key Features
Up to 86 tools — 83 canonical tools across 11 categories plus 3 always-available meta-tools (
setup_status,request_permission_escalation,check_escalation_status). SimpleLogin and Proton Pass groups are listed only when configured. Seesrc/config/schema.tsfor the canonical inventory.Two transports — stdio (default, Claude Desktop) and HTTP (remote / self-host). HTTP is OAuth-only:
authorization_code+ PKCE-S256 for interactive agents andclient_credentialsfor headless service accounts, with RFC 7591 Dynamic Client Registration, RFC 8414 authorization-server metadata, and RFC 9728 protected-resource metadata. Per-caller token-bucket rate limiting on every endpoint.Progressive tool tiering —
core/extended/completecontrols how many tools land in the client'sListToolsresponse, so context isn't burned on tools you don't use. Configurable viatoolTierorMAILPOUCH_TIER.Destructive-tool confirmation — uses MCP elicitation when the client supports it (Claude Desktop, Cline) so the user sees a prompt before delete, trash, spam, alias deletion, server lifecycle, or sensitive Pass retrieval. Falls back to a required
{ confirmed: true }argument for clients without elicitation.5 permission presets — read-only by default; write access requires explicit opt-in. Per-tool overrides and rate limits via the Custom preset.
Human-gated escalation — agents request elevated permissions, you approve via browser UI or terminal; the agent cannot approve its own requests.
Browser-based settings UI at
localhost:8766— auto-starts with the daemon; setup wizard, live connection test, per-tool toggles, escalation approval panel, per-agent Approve/Deny.Native system tray icon — always visible, clickable menu opens the settings UI or quits. Rendered via a bundled Rust (napi-rs) binding around the
tauri-apps/tray-iconcrate — the same one Tauri ships in production — so the tray behaves correctly on modern GNOME (where the legacy Go-binary library shows a generic placeholder), NSStatusBar on macOS, and Shell_NotifyIcon on Windows. Prebuilts for linux-x64/arm64, darwin-arm64, win32-x64/arm64 ship inside the main package; darwin-x64 (Intel Mac) falls back to the legacy Go backend cleanly.6 MCP prompts — triage inbox, compose reply, daily briefing, find subscriptions, thread summary, draft in my voice.
MCP Resources — individual emails and folders addressable via
email://andfolder://URIs.Scheduled email delivery — queue emails for future sending; survives server restarts. Plus
remind_if_no_replyfor outbound follow-ups gated on inbox replies.Optional companion services — SimpleLogin alias management (16 tools, requires API key) and Proton Pass via pass-cli (4 tools, requires PAT) are omitted from
ListToolsuntil configured; local FTS5 full-text index remains available whenbetter-sqlite3is installed.TLS-strict by default — refuses to connect to localhost Bridge without a pinned cert, recommends Bridge ≥
3.22.0(older detected versions warn but do not block connection), and uses exponential backoff on SMTP abuse-signal responses.Multi-account — configure more than one Proton / IMAP account; a running daemon hot-swaps the active account, while standalone settings and failed live rebinds explicitly request a restart. Tools accept an optional
account_idargument to route a single call to a specific account. Seesrc/accounts/.Per-agent grants — each MCP client (identified by its OAuth
client_id) is gated by its own approvable grant, with optional folder allowlists, IP pins, per-tool rate caps, expiry, and account binding. Separate from the global preset and the escalation flow. Seesrc/agents/.Live notifications — desktop toasts (no extra deps) and outbound webhooks (CloudEvents / Slack / Discord, HMAC-signed, retried) fire on grant-state changes. See
src/notifications/.Strict TypeScript and a comprehensive Vitest suite; unused locals and parameters are compiler errors.
Documentation: HELP.md (task-oriented how-tos) · README_FIRST_AI.md (agent API reference) · docs/index.md (full index)
Quick Start
Ask Claude things like:
"Summarize everything from my boss this week"
"Find emails about my Acme invoice and draft a reply"
"Move all order confirmations to my Shopping folder"
"What's my average email response time this month?"
"Schedule a follow-up email to alice@example.com for next Monday at 9am"
"Remind me if there's no reply within 3 business days"With read-only permissions (the default), Claude can read, search, and analyse your inbox but cannot send, move, delete, or change anything.
Prerequisites
Requirement | Version | Notes |
Node.js | >= 22.0.0 | Check with |
npm | >= 10.0.0 | Bundled with Node.js |
Proton Bridge | 3.22.0 recommended minimum | Must be running and signed in; older detected versions warn but do not block connection · proton.me/mail/bridge |
Proton Mail account | Paid plan | Bridge requires a paid Proton plan (Mail Plus, Unlimited, etc.) |
MCP client | Latest | Claude Desktop, Cline, or any MCP-compatible host · claude.ai/download |
Supported on macOS, Windows, and Linux.
Linux runtime libraries
The native tray binding dynamically links against two GTK system libraries that are preinstalled on every modern desktop Linux distribution (Ubuntu ≥ 18.04, Fedora ≥ 34, Mint, Pop!_OS, Arch, etc.). No manual install is needed on a normal desktop system — just a note for server / container / minimal-WM deployments:
Runtime library | Package name |
|
|
|
|
If both are missing (headless server, container, SSH-only host), mailpouch's tray startup logs a skip reason and continues without the icon — the MCP server itself runs unaffected. macOS and Windows ship their native equivalents as part of the OS.
Proton Bridge ports
Bridge listens locally on:
Protocol | Host | Port |
SMTP (sending) |
|
|
IMAP (reading) |
|
|
Use
127.0.0.1, notlocalhost. On some systemslocalhostresolves to::1(IPv6), which Bridge does not listen on.
Installation
Option A — npm (recommended)
npm install -g mailpouchOr skip the install entirely — npx -y mailpouch runs the latest published version on demand, which is exactly what the canonical MCP client config below uses.
Option B — From source
git clone https://github.com/chandshy/mailpouch.git
cd mailpouch
npm install
npm run buildOptional companions
Install only if you plan to use the corresponding tool group:
Optional dep | Enables | Install |
|
|
|
|
| See |
SimpleLogin API key |
| Generate at app.simplelogin.io; paste in settings UI |
Tools in unconfigured groups return a clean configuration error rather than failing silently.
Setup Wizard
Run the settings server to complete first-time setup:
npx -y mailpouch-settings
# Then open http://localhost:8766The 6-step wizard walks you through everything automatically:
Welcome — overview, ToS acknowledgement, and prerequisites checklist
Bridge health check — live TCP test to ports 1025 and 1143; blocks progress until Bridge is reachable
Account — your Proton Mail address and Bridge password (Bridge app → Settings → IMAP/SMTP → Password — this is not your Proton login password)
Permission preset — choose what the AI is allowed to do (see table below)
Review — confirm your settings before saving
Done — displays the exact JSON snippet to paste into your MCP client config; optionally writes it for you automatically
Settings are saved to ~/.mailpouch.json with mode 0600 (owner read/write only). The Bridge password and SMTP token prefer the OS keychain when available.
Non-interactive setup (agents / CI)
When a browser/TUI wizard isn't an option, configure credentials from the command line — it writes the same ~/.mailpouch.json (+ keychain) the wizard does:
# password from stdin (keeps the secret out of the process list)
printf '%s' "$BRIDGE_PASSWORD" | npx -y mailpouch setup --username you@proton.me --password-stdin
# or from a file, or inline; optional non-default ports / cert
npx -y mailpouch setup --username you@proton.me --password-file ./bridge.pw \
[--imap-host 127.0.0.1 --imap-port 1143 --smtp-host 127.0.0.1 --smtp-port 1025] \
[--bridge-cert /path/cert.pem | --insecure]Then check the install end-to-end:
npx -y mailpouch doctor # human-readable diagnosis + next step; exit 0 when ready
npx -y mailpouch doctor --json # structured output for scripts
npx -y mailpouch status # is it running? ports, connection, approved agents (read-only)
npx -y mailpouch --help # all commands + flags + the config/log pathsdoctor reports the same state machine the always-available setup_status MCP tool returns: unconfigured → bridge-unreachable → pending-approval → ready, each with the exact action to advance. These commands print and exit — they never start a server, so they're safe to run alongside a live daemon. (Running mailpouch with no command starts the MCP stdio server, which is what your MCP client spawns.)
Claude Desktop Configuration (stdio)
The config file locations are:
macOS:
~/Library/Application Support/Claude/claude_desktop_config.jsonWindows:
%APPDATA%\Claude\claude_desktop_config.jsonLinux:
~/.config/Claude/claude_desktop_config.json
Canonical entry — works whether or not mailpouch is globally installed:
{
"mcpServers": {
"mailpouch": {
"command": "npx",
"args": ["-y", "mailpouch"]
}
}
}This is the same JSON shown in llms.txt and README_FIRST_AI.md — use it everywhere. If you installed globally (npm install -g mailpouch) you may instead use "command": "mailpouch" with no args. Restart the client after saving.
If you cloned the repo instead of installing from npm, point the client at the built entry file directly (your path will differ):
{
"mcpServers": {
"mailpouch": {
"command": "node",
"args": ["/path/to/mailpouch/dist/index.js"]
}
}
}The settings wizard can also write the entry to your client config automatically — click Write to Claude Desktop on the Done step.
Multi-Account
More than one mail account can be configured in the same server — handy for juggling a personal Proton address, a work Proton address, and a generic IMAP account from a single MCP client.
The config file grows two fields alongside the existing connection block:
{
"accounts": [
{ "id": "primary", "name": "Personal", "providerType": "proton-bridge",
"smtpHost": "127.0.0.1", "smtpPort": 1025,
"imapHost": "127.0.0.1", "imapPort": 1143,
"username": "me@proton.me", "password": "<bridge-pw>" },
{ "id": "acct-7b1c", "name": "Work", "providerType": "imap", "...": "..." }
],
"activeAccountId": "primary"
}The Accounts tab in the settings UI handles add / edit / activate / delete. The server refuses to delete the last remaining account.
AccountManagerkeeps one{ imap, smtp, spec }triple per configured account. In a running daemon, saving a new active selection emits anactive-changedevent that rebinds active-account services and clears prior-account caches. Standalone settings (or a failed live rebind) reports that a restart is required.Tools accept an optional
account_idargument. When omitted, the call runs againstactiveAccountId; when present, the dispatcher routes to the named account. Agent grants can pin a client to a singleaccountIdviaconditions.Legacy single-account configs are migrated lazily: the first load with
accounts: []lifts the top-levelconnectionfields into aprimaryaccount. No manual migration step.
Canonical code: src/accounts/registry.ts, src/accounts/manager.ts, src/accounts/types.ts.
Remote / HTTP Transport
For headless boxes, phones, or sharing one Bridge across multiple devices, switch to HTTP transport. The same binary listens on a port instead of stdio.
Enable it via the Setup tab → Remote (HTTP) mode, or by setting these in ~/.mailpouch.json:
{
"connection": {
"remoteMode": true,
"remoteHost": "127.0.0.1",
"remotePort": 8788,
"remotePath": "/mcp",
"remoteTlsCertPath": "/path/to/cert.pem",
"remoteTlsKeyPath": "/path/to/key.pem",
"remoteOauthEnabled": true,
"remoteOauthIssuer": "https://mcp.example.com",
"remoteRateLimitPerSecond": 20,
"remoteRateLimitBurst": 40
}
}Auth is OAuth-only — every agent authenticates as its own client. There is no shared bearer token (it was removed because it bypassed per-agent gating and audit). A verified token always maps to a specific, gated, revocable agent identity. remoteMode refuses to start without remoteOauthEnabled. Two grant types share the one listener:
authorization_code+ PKCE-S256 (interactive agents) — MCP hosts self-register viaPOST /oauth/register(RFC 7591), discover endpoints viaGET /.well-known/oauth-authorization-server(RFC 8414) andGET /.well-known/oauth-protected-resource(RFC 9728), then complete a PKCE flow with automatic consent (no admin password —GET /oauth/authorizeissues a code immediately). The human gate is the per-agent Approve/Deny in the Agents tab: the issued token is inert until you approve the agent, and a pending request expires after 5 minutes.client_credentials(headless / service accounts) — cron, CI, and scheduled agents that can't do interactive consent. Issue a service account out-of-band, then log in with itsclient_id+client_secret:mailpouch agent issue --name nightly-triage --preset read_only # → prints client_id + client_secret ONCE; the matching grant is pre-approved (active). # also: mailpouch agent list / mailpouch agent revoke <client_id> # (or use the "+ Service account" button in the Agents tab) curl -s -u "$CLIENT_ID:$CLIENT_SECRET" \ -d grant_type=client_credentials https://mcp.example.com/oauth/token # → { "access_token": "…", "token_type": "Bearer", "expires_in": 86400 }
Rate limiting — token-bucket per caller (per IP for unauthed paths, per token key for /mcp). A compromised token can't DoS Bridge.
TLS — provide remoteTlsCertPath + remoteTlsKeyPath for HTTPS. Required for any non-loopback exposure.
A remote client config (after obtaining an access token via either grant) looks like:
{
"mcpServers": {
"mailpouch-remote": {
"url": "https://mcp.example.com/mcp",
"headers": { "Authorization": "Bearer <access_token>" }
}
}
}Connecting a client (stdio or HTTP). The Settings UI (Setup tab → Connect a client) and the first-run wizard both let you choose how an MCP client connects and write the entry for you:
Write to Claude Code merges a
mailpouchentry into~/.claude.jsonundermcpServers(and Write to Claude Desktop does the same forclaude_desktop_config.json).stdio — the client spawns its own mailpouch. The written entry sets
MAILPOUCH_FORCE_STDIO=1so it speaks stdio even when your config hasremoteMode: true. Don't run a stdio client alongside the shared HTTP daemon against the same Proton account — they'd contend for the one IMAP connection.HTTP — the client connects to the shared daemon at
/mcp(needsremoteMode+remoteOauthEnabledand the daemon running). The client performs an OAuth login and you Approve it once in the Agents tab.
Running mailpouch as a shared daemon (multiple apps at once). mailpouch holds one IMAP connection per account (a singleton lock), so two stdio instances for the same account can't run at the same time — the second exits. To use several clients together (e.g. Claude Code and Claude cowork, or headless agents), run one HTTP daemon and point every client at it:
mailpouch daemon # starts the shared HTTP daemon (forces HTTP; requires remoteOauthEnabled)
# optional: mailpouch daemon --host 0.0.0.0 --port 8788mailpouch daemon is user-started (run it in a tmux / login session or your own supervisor) — it is not an autostart. Then:
Interactive apps (Claude Code, Claude Desktop) connect over HTTP and you Approve each once in the Agents tab.
Headless / cowork hosts authenticate with a per-host service account (
mailpouch agent issue --name <host> --preset <preset>) over theclient_credentialsgrant.
All clients share the daemon's single IMAP connection — no singleton conflict, and each is independently gated, audited, and revocable.
While the daemon is running, the Settings "Connect an app" chooser only offers HTTP — the per-computer (stdio) option is disabled, because a stdio entry would spawn a second instance that collides with the daemon and fail to start. Issuing or re-issuing a service account (mailpouch agent issue) takes effect immediately — the running daemon picks it up on the next login, no restart. Revoking a service account (in the Agents tab) deletes its credential, so it stays revoked across restarts; to re-enable, re-issue it.
Environment variables
Configuration is stored in ~/.mailpouch.json and managed via the settings UI — not environment variables. The following env vars are available for advanced/optional overrides:
Variable | Default | Description |
|
| Override config file path |
|
| Scheduled email persistence file |
|
| Override log file path |
|
| Override pending escalations file path |
|
| Override escalation audit log path |
|
| Override reminder persistence file path |
|
| Override Proton Pass access audit log path |
|
| Override full-text-search index database path |
|
| Override per-agent grant store path |
|
| Override service-account (client_credentials) store path |
|
| Override per-agent tool-call audit log path |
| derived from host | Override the machine-binding secret used to encrypt at-rest credentials |
| unset | Force stdio for this spawn even if the config has |
| unset | Per-launch opt-in to localhost Bridge without a pinned cert |
|
| Tool-tier override: |
|
| Override settings UI HTTP server port |
Available Tools
83 canonical tools across 11 categories, plus 3 always-available meta-tools. Optional companion groups are listed only when configured; see src/config/schema.ts for the machine-checkable inventory.
Category | Tools | Default tier | Risk | Permission required |
Reading | 14 |
| safe | always available |
Sending | 4 |
| moderate |
|
Analytics | 4 |
| safe | always available |
System | 5 |
| safe | always available |
Drafts & Scheduling | 9 |
| moderate |
|
Folder Management | 5 |
| moderate |
|
Email Actions | 16 |
| moderate |
|
SimpleLogin Aliases (optional) | 16 |
| moderate | listed when API key is present |
Proton Pass (optional) | 4 |
| moderate | listed when a PAT is present; calls require |
Deletion | 3 |
| destructive |
|
Bridge & Server Control | 3 |
| destructive | mixed; |
Plus 3 always-available meta-tools: setup_status, request_permission_escalation, and check_escalation_status.
Notable tools worth calling out
Reading includes
get_thread,get_correspondence_profile, and thefts_*family (local SQLite FTS5 index — much faster than IMAP search for repeat queries).Drafts & Scheduling includes
remind_if_no_reply(queue an outbound, fire a follow-up reminder if no reply lands within N days),list_pending_reminders,cancel_reminder,check_reminders. JSONL persistence so reminders survive restarts.Bridge & Server Control — if
autoStartBridgeis enabled, the server launches Bridge automatically on startup and runs a 30 s watchdog that attempts up to 3 restarts on outage.shutdown_serverandrestart_serverare capped at 5/hr insupervised.
MCP Prompts
Pre-built prompt templates for common tasks:
Prompt | Description | Arguments |
| Review unread emails, assess urgency, suggest actions |
|
| Draft a contextual reply to an email thread |
|
| Summarize today's inbox: unread, key senders, action items | — |
| Identify mailing lists / newsletters; offer to archive or unsubscribe |
|
| Fetch all messages in a thread; produce a concise summary with open action items |
|
| Draft a new email to a specific recipient in the user's own voice, using a handful of recent sent emails as tone samples. Infers style (formality, greeting/sign-off habits, typical length) from the samples. |
|
Permission Presets
Preset | What's allowed | Best for |
Read-Only (default) | Read, search, analytics, connection status, logs, Bridge start | Starting out; untrusted or new agents |
Supervised | All tools enabled; reading unlimited; sending 200/hr, schedule 100/hr, bulk actions 100/hr; deletion 20/hr; folder delete 20/hr; server lifecycle 5/hr | Day-to-day agentic use |
Send-Only | Reading unlimited; send/forward/schedule 50/hr, remind_if_no_reply 100/hr; actions, deletion, folder writes, and bulk ops disabled | Agents that only need to compose and send |
Full Access | All tools, no rate limits | Trusted workflows where you review actions |
Custom | User-defined per-tool toggles and rate limits (set via the Permissions tab) | Fine-grained control beyond the 4 presets |
Change the preset at any time from the Permissions tab in the settings UI; changes take effect within 15 s without restart.
Human-Gated Escalation
The escalation system lets an agent request broader permissions without permanently changing your settings.
How it works:
The agent calls
request_permission_escalationwith a reason and the target preset it needs.A challenge appears as a banner in the Settings UI (above the tabs) and is also printed to the terminal.
You review the request, type
APPROVEin the confirmation field, and click Approve (or Deny).The agent polls with
check_escalation_statusand proceeds once approved.After 5 minutes, permissions revert automatically.
Security properties:
The agent requests via MCP; approval can only happen via browser or terminal — channels the agent cannot write to
You must type
APPROVEbefore the button activates — no accidental clicksCSRF-protected: the approval API requires a session token embedded only in the rendered HTML page
Rate-limited: max 5 escalation requests per hour, max 1 pending at a time
Audit trail: every request, approval, and denial is appended to
~/.mailpouch.audit.jsonlApprove from another device:
npx -y mailpouch-settings --lan
Settings UI
The settings UI starts automatically on http://localhost:8766 whenever your MCP client runs the server. A system tray icon (purple envelope) appears in your taskbar — right-click it to open the UI, disable it temporarily, or quit.
To run the settings UI standalone (useful for initial setup, headless / SSH systems, or a dedicated remote-mode host):
npx -y mailpouch-settings # auto-detects display; opens browser if available
npx -y mailpouch-settings --port 9000 # custom port (default: 8766)
npx -y mailpouch-settings --lan # bind to 0.0.0.0 (approve from phone/other device)
npx -y mailpouch-settings --browser # force browser UI even if no display detected
npx -y mailpouch-settings --tui # force interactive terminal UI
npx -y mailpouch-settings --plain # plain readline menus (no ANSI colors/escapes)
npx -y mailpouch-settings --no-open # start server but don't auto-open browserThe same standalone behaviour is available from the main binary via mailpouch --settings-only: it starts only the settings UI + tray (no MCP transport, no Bridge connect) and stays running until you quit it from the tray or with Ctrl-C. Unlike a bare mailpouch, it is safe to launch detached (autostart / nohup / a wrapper) — it does not tie its lifetime to stdin.
Tabs:
Setup — credentials, SMTP/IMAP hosts and ports, Bridge TLS certificate, Optional Integrations (SimpleLogin API key, Proton Pass PAT + CLI path), debug mode, auto-start Bridge, insecure-connection toggle, destructive-confirm toggle, desktop notifications toggle, auto-open approval window toggle, settings port
Accounts — per-account Bridge credentials; live active-account switching with an explicit restart fallback
Permissions — preset selector, per-tool enable/rate-limit toggles, tool-tier (
core/extended/complete), destructive-confirm toggleAgents — per-client (OAuth
client_id) approvable grants with folder allowlists, IP pins, per-tool rate caps, expiry, and account bindingStatus — server info, MCP config snippet, live connectivity check, escalation audit log, config reset
Pending escalation requests appear as a full-page banner above the tabs. A Logs tab appears automatically when debug mode is enabled. Most settings changes propagate to the running MCP server within 15 s; active-account switching explicitly reports when a restart is required.
Security
This server gives AI agents controlled access to sensitive email data. The security model has these layers:
Layer | Mechanism |
Permission gate | Every tool call checked against |
Tool tiering |
|
Rate limiting | Per-tool sliding-window limits in-process; per-caller token-bucket on the HTTP transport |
Destructive confirmation | MCP elicitation prompt (or required |
Escalation gate | Privilege increases require explicit human approval via a separate channel |
Audit log | Append-only log of all escalation events at |
OAuth 2.1 + PKCE-S256 | Spec-compliant DCR + automatic consent; per-agent Approve/Deny is the human gate (HTTP transport) |
CSRF protection | All mutating settings API calls require a session token (timing-safe comparison) |
Origin validation | Settings server validates |
Input validation | Email addresses, folder names, attachment sizes, hostnames, label names |
Injection prevention | CRLF stripped from all SMTP headers, subjects, filenames, custom headers |
TLS-strict Bridge | Refuses to connect to localhost Bridge without a pinned cert by default |
Bridge version floor | Warns when Bridge < |
SMTP backoff | Exponential backoff on abuse-signal SMTP responses (4xx 421/450/454 throttle codes) |
Config file isolation | Mode |
Memory safety | Email cache capped at 500 entries / 50 MB; rate-limiter buckets evicted when idle (fully refilled) |
Keychain storage | OS keychain preferred for Bridge password and SMTP token |
What agents cannot do:
Approve their own escalation requests
Bypass the permission gate (it runs in the server process, not the agent)
Read or modify
~/.mailpouch.jsondirectly (not an exposed tool)Erase the audit log
Inject headers into outgoing email via crafted subjects, filenames, or custom headers
Execute destructive tools without surfacing the intent to the user
Credentials: Stored in ~/.mailpouch.json with 0600 permissions (or in the OS keychain). Never commit this file. The settings UI never displays or transmits high-value secrets after they are first saved.
Agent Grants
The global permission preset gates what actions are allowed; the selected tool tier controls what is advertised; and an agent grant gates which MCP client may use that allowed surface. Every agent — local and remote — registers and must be approved. A remote MCP host completes OAuth Dynamic Client Registration and mailpouch creates a pending grant keyed by its client_id; a local stdio client (e.g. Claude Desktop) is registered at the MCP handshake, keyed by its self-reported client name (stdio:<hash>) and shown with a 🖥 local marker. Nothing that client calls will succeed until you approve it; approve once and a local client is remembered across relaunches. To restore the legacy "local client is auto-trusted" behavior, set gateLocalAgents: false (or MAILPOUCH_TRUST_LOCAL=1).
Grant lifecycle: pending → active → revoked | expired. Each grant carries:
Field | Purpose |
| Effective preset for this agent; intersected with the global preset (grants can never widen the ceiling) |
| Per-tool allow/deny that trumps the preset, still bounded by the global config |
| ISO-8601 auto-expiry; checked at call time |
| Restrict which IMAP folders the agent may touch |
| Allowed remote IPs (OAuth/bearer path only) |
| Per-tool hourly rate cap |
| Bind the agent to a single multi-account id |
Approve, deny, revoke, and "approve-with-conditions" all live in the Agents tab of the settings UI. The tab streams live updates over SSE from GET /api/notifications — new pending grants surface without a reload. When a new agent registers, mailpouch also auto-opens this tab in your browser (and fires a desktop notification + tray badge) so you can approve or deny the connection right away; the pending card shows the agent's name, registering IP, and time. The agent's tool calls stay blocked until you approve. When a new agent connects, mailpouch pops a native Approve/Deny dialog right on the screen where it runs (zenity/osascript/PowerShell) so you can decide without opening the tab — Approve grants your global preset, Deny revokes it (nativeApprovalDialog, default on). If no dialog tool is available (headless), it falls back to auto-opening this Agents tab in your browser (autoOpenApprovalWindow, default on) plus a desktop notification + tray badge. Once connected, the card also shows the agent's MCP handshake connection info — its self-reported client name + version, the transport, and a last-connected timestamp — captured at initialize (display-only; the agent's identity is its server-issued OAuth client_id, not the self-reported name).
Every agent authenticates as its own client — there is no shared bearer. The static bearer was removed because it had no per-agent identity (one secret, no gating, no audit). Remote mode requires OAuth (
connection.remoteMode: true+remoteOauthEnabled: true). Interactive agents register and you Approve/Deny them here (automatic consent — no human password; a pending request expires after 5 minutes). Headless agents (cron, CI) use a pre-approved service account (mailpouch agent issue …or the "+ Service account" button) and theclient_credentialsgrant. DeprecatedremoteBearerTokenandremoteOauthAdminPasswordvalues are ignored and scrubbed from config/keychain storage during startup migration.
Every gated tool call writes one row to an append-only JSONL audit log at ~/.mailpouch-agent-audit.jsonl (mode 0600). Rows carry a truncated sha256 argHash — never argument values, never response bodies — so "same call repeated" patterns are observable without creating a parallel on-disk copy of your email. The log rotates at 10 MB and keeps 3 gzipped generations.
Caller identity propagates through the dispatcher via AsyncLocalStorage (see src/agents/caller-context.ts); the caller's clientId is always a real per-agent identity (pmc_… for OAuth clients and service accounts, stdio:… for local stdio agents). Local stdio agents are gated too (gateLocalAgents, default on) — they register and must be approved like any other agent.
Canonical code: src/agents/grant-store.ts, grant-manager.ts, audit.ts, caller-context.ts, notifications.ts, registry.ts.
Notification Channels
Grant-state transitions (grant-created / -approved / -denied / -revoked / -expired) fan out through an in-process NotificationBroker to two optional channels:
Desktop toasts — platform-native, no extra dependency. macOS shells to
osascript, Linux tonotify-send(libnotify), Windows topowershell.exedriving the WinRT toast API. Fire-and-forget; missing tooling degrades to a debug log.Outbound webhooks —
WebhookDispatcherPOSTs a JSON body to each configured endpoint. Format defaults to CloudEvents 1.0; URLs onhooks.slack.comauto-select the Slack shape anddiscord.com/discordapp.comauto-select the Discord shape (explicitformat: "raw"is also available). When an endpoint has a secret configured, every body is HMAC-signed asX-Mailpouch-Signature-256: sha256=<hex>(GitHub-webhook convention). Delivery retries up to 8 times with exponential backoff (1 / 2 / 4 / 8 / 16 / 32 / 64 / 128 s) plus ±20 % jitter; 4xx responses other than 408 and 429 stop retries immediately.
Canonical code: src/notifications/desktop.ts, src/notifications/webhooks.ts.
Troubleshooting
"Connection refused" on Bridge ports
Confirm Proton Bridge is running and signed in.
Use
127.0.0.1instead oflocalhostin all host fields.Verify ports are listening:
lsof -i :1025 -i :1143(macOS/Linux) ornetstat -ano | findstr "1025\|1143"(Windows).Some VPNs block localhost port binding — try disabling the VPN.
"Authentication failed" or IMAP login error
Use the Bridge password, not your Proton Mail login password.
Find it in the Bridge app: Settings → IMAP/SMTP → Password (a long random string).
If you recently reinstalled Bridge, it generates a new password — update it in the settings UI.
"Tool blocked by permission policy"
Open the settings UI → Permissions tab and switch to Supervised or Full Access.
Per-tool toggles let you enable individual tools without changing the overall preset.
The agent can call
request_permission_escalationfor temporary access.
"Certificate error" or TLS handshake failure
Export the Bridge TLS certificate: Bridge app → Settings → Export TLS certificates.
Set the path in the settings UI under Setup → Bridge TLS Certificate.
The server refuses to connect to a localhost Bridge without a pinned TLS certificate — this matches Proton Bridge's own v3.21.2+ hardening. If you cannot provide a cert, set Allow insecure Bridge connection under Setup (or launch with
MAILPOUCH_INSECURE_BRIDGE=1) to opt back into the legacy behavior. Configs that predate this change are grandfathered into the legacy mode with a startup warning until the opt-in is set explicitly.
Bridge version warning on startup
The server issues an IMAP
IDrequest after connect and warns when Bridge is older than 3.22.0 (the recommended minimum); the version probe is warn-only and does not block connection. Upgrade from the Bridge app → Check for updates.
Tool list looks short / missing tools
Check your tool tier under Permissions.
coreexposes 30 tools;extendedexposes 80;completeexposes up to 86, with unconfigured SimpleLogin and Pass tools omitted.SimpleLogin and Proton Pass tools (
alias_*,pass_*) appear once their API key or PAT is configured. Thefts_*tools remain listed and requirebetter-sqlite3when called.
Remote / HTTP client returns 401
The
Authorization: Bearer <token>header must carry an OAuth access token, not a pre-shared secret — the static bearer was removed. For interactive clients, check that/oauth/registersucceeded, the agent is approved in the Agents tab, and the token has not been revoked or expired.For headless clients, confirm the service account exists (
mailpouch agent list) and thatPOST /oauth/tokenwithgrant_type=client_credentialsreturned anaccess_token. A 401invalid_clientmeans theclient_id/client_secretpair is wrong.The
WWW-Authenticateheader on the 401 response carries the failure reason per RFC 6750.
Claude Desktop doesn't show mailpouch tools
Confirm the
mcpServersblock is valid JSON (no trailing commas).Fully quit and reopen Claude Desktop.
Check MCP logs: Help → Show Logs.
Verify the server starts manually:
npx -y mailpouch— it should stay running silently.
Analytics show zero or empty data
Run
sync_emailsfirst to populate the cache.Response time stats only appear when sent emails have
In-Reply-Toheaders matching inbox messages.
Development
git clone https://github.com/chandshy/mailpouch.git
cd mailpouch
npm install
npm run build # compile TypeScript to dist/
npm run dev # watch mode (recompiles on save)
npm run test # run the Vitest suite
npm run test:coverage # coverage report
npm run lint # TypeScript type check (tsc --noEmit)
npm run settings # start standalone settings UI (after build)Project structure
src/
index.ts # Unified daemon: MCP server (up to 86 tools, resources, prompts) + settings + tray
settings-main.ts # Standalone settings UI CLI (for headless/SSH environments)
# (Tray moved to native/tray/ — napi-rs binding around tauri-apps/tray-icon)
config/
schema.ts # Tool registry, categories, tiers, destructive set, response limits
loader.ts # Config load/save, preset builder, keychain migration
permissions/
manager.ts # Per-tool permission checks and rate limiting
escalation.ts # Human-gated escalation challenge system
security/
keychain.ts # OS keychain integration (@napi-rs/keyring)
memory.ts # Credential wipe helpers
services/
smtp-service.ts # Email sending via Nodemailer (with abuse-signal backoff)
simple-imap-service.ts # Email reading via ImapFlow
analytics-service.ts # Email analytics computation
scheduler.ts # Scheduled email delivery (JSONL persistence)
reminder-service.ts # remind_if_no_reply queue
fts-service.ts # Local SQLite FTS5 index (optional better-sqlite3)
simplelogin-service.ts # SimpleLogin alias API client
pass-service.ts # Proton Pass via pass-cli subprocess
settings/
server.ts # Browser-based settings UI server
security.ts # CSRF, origin validation, TLS
tui.ts # Terminal UI for settings
transports/
http.ts # HTTP transport with required OAuth 2.1 authentication
oauth-handlers.ts # /.well-known + /oauth/* (RFC 7591/8414/9728)
oauth-store.ts # Client / authorization-code / token store
rate-limit.ts # Token-bucket per-caller limiter
utils/
helpers.ts # ID generation, email validation, log sanitisation
logger.ts # Structured log store
tracer.ts # Lightweight request tracing
backoff.ts # Exponential backoff helper
types/
index.ts # Shared TypeScript typesWorks Best With…
mailpouch is deliberately scoped to email. Chain it with these MCP servers to cover the rest of an agentic workflow:
MCP server | Use with mailpouch |
| Save attachments to disk; read local files to attach to outgoing mail |
| Follow links the agent reads in an email without leaving the chat |
Turn an email into a task; complete triage in one pass | |
File an issue from a bug-report email with full context | |
Obsidian-vault MCPs (example) | Archive important threads to notes with linked metadata |
The project is intentionally Proton-focused; the positioning is "best email surface for your agent." Pair it with whichever non-email MCPs fit your workflow rather than waiting for this server to grow a second home.
Acknowledgements
This project is built on the foundation originally created by Hawk94, whose initial IMAP/SMTP integration, tool architecture, and test setup made this project possible. The original work was published as barhatch/protonmail-mcp-server.
License
MIT — see LICENSE
Unofficial third-party server. Not affiliated with or endorsed by Proton AG.
Available Tools
66 toolsarchive_emailArchive EmailA
Move an email to the Archive folder. Convenience wrapper for move_email targeting Archive. Note: labels are lost when an email is moved — label copies in Labels/ folders are not preserved.
Inputs: emailId (required), account_id, sourceFolder. Returns: reason, success, messageId
bulk_delete_emailsBulk Delete EmailsADestructive
Delete multiple emails by MOVING them to Trash — mail is never permanently deleted and stays recoverable from Trash. Emits progress notifications if a progressToken is provided in _meta. Returns success/failed counts. Requires { confirmed: true }. Pass sourceFolder whenever the UIDs came from a folder other than INBOX.
Inputs: emailIds (required), confirmed, account_id, sourceFolder. Returns: errors, failed, success
bulk_mark_readBulk Mark Emails Read/UnreadAIdempotent
Mark multiple emails as read or unread. Emits progress notifications. Returns success/failed counts. Pass sourceFolder whenever the UIDs came from a folder other than INBOX.
Inputs: emailIds (required), isRead, account_id, sourceFolder. Returns: errors, failed, success
bulk_move_emailsBulk Move EmailsA
Move multiple emails to a folder in one call. Moving to Trash or Spam requires { confirmed: true }. Emits progress notifications if a progressToken is provided in _meta. Returns success/failed counts. Pass sourceFolder whenever the UIDs came from a folder other than INBOX.
Inputs: emailIds (required), targetFolder (required), confirmed, account_id, +1 more. Returns: errors, failed, success
bulk_move_to_labelAdd Label to EmailsA
Add a label to multiple emails. Each email remains in its original folder and also appears in Labels/{label}; use bulk_move_emails only when messages should leave their source folder. Progress notifications are sent for large batches.
Inputs: label (required), emailIds (required), account_id, sourceFolder. Returns: errors, failed, success
bulk_remove_labelBulk Remove Label from EmailsA
Remove a label from multiple emails. Emails are removed from Labels/{label} but remain in their original folders. The UIDs passed must be the UIDs inside Labels/{label} — Labels/ folders have their own UID space, so INBOX UIDs will silently miss.
Inputs: label (required), emailIds (required), account_id. Returns: errors, failed, success
bulk_starBulk Star/Unstar EmailsAIdempotent
Star or unstar multiple emails. Emits progress notifications. Returns success/failed counts. Pass sourceFolder whenever the UIDs came from a folder other than INBOX.
Inputs: emailIds (required), isStarred, account_id, sourceFolder. Returns: errors, failed, success
cancel_reminderCancel ReminderAIdempotent
Cancel a pending no-reply reminder by ID. Silently returns false if the ID is unknown or the reminder already fired.
Inputs: reminder_id (required), account_id. Returns: reason, success, messageId
cancel_scheduled_emailCancel Scheduled EmailAIdempotent
Cancel a pending scheduled email before it is sent. Returns false if the ID is not found or the email has already been sent.
Inputs: id (required), account_id. Returns: reason, success, messageId
check_escalation_statusCheck Escalation StatusARead-onlyIdempotent
Check whether a pending permission escalation has been approved, denied, or has expired. Poll this after calling request_permission_escalation.
Inputs: challenge_id (required), account_id. Returns: status, newTools, expiresAt, resolvedAt, +3 more
check_remindersCheck RemindersA
Return every pending reminder whose deadline has passed. Each returned reminder is transitioned to 'fired' status so it won't appear in subsequent calls. The agent can then search INBOX for replies to messageId and decide whether to surface the reminder to the user.
Inputs: account_id. Returns: fired
clear_cacheClear CacheAIdempotent
Clear all in-memory caches (email message cache, folder cache, analytics cache). Forces fresh IMAP fetches on next access. Use if you suspect stale data. Does NOT rebuild the on-disk FTS index — run fts_rebuild to refresh fts_search results.
Inputs: account_id. Returns: reason, success, messageId
create_folderCreate FolderA
Create one new IMAP mailbox. Use Folders/Name for a custom folder or Labels/Name for a label; the path must be unused across both namespaces. Use rename_folder to change an existing mailbox. Returns success after Proton Bridge accepts the create; create labels before adding them with move_to_label.
Inputs: folderName (required), account_id. Returns: reason, success, messageId
delete_emailDelete EmailADestructive
Delete an email by MOVING it to Trash — mail is never permanently deleted and stays recoverable from Trash. An email already in Trash is left in place. Requires { confirmed: true }. Pass sourceFolder whenever the UID came from a folder other than INBOX.
Inputs: emailId (required), confirmed, account_id, sourceFolder. Returns: reason, success, messageId
delete_folderDelete FolderADestructive
Delete an empty folder or label. Protected system folders (INBOX, Sent, Drafts, Trash, Spam, Archive, All Mail, Starred) cannot be deleted. Requires { confirmed: true }.
Inputs: folderName (required), confirmed, account_id. Returns: reason, success, messageId
download_attachmentDownload AttachmentARead-only
Download the binary content of an email attachment as a base64-encoded string. Use get_email_by_id first to see available attachments and their indices (0-based). Provide the message folder to avoid a cross-folder UID collision; it is required for folder-restricted agents.
Inputs: email_id (required), attachment_index (required), folder, account_id. Returns: size, content, encoding, filename, +1 more
empty_trashEmpty TrashADestructive
PERMANENTLY delete every message in the Trash mailbox. This is the only operation that bypasses the move-to-Trash safety net — purged mail is UNRECOVERABLE. It only ever touches the Trash mailbox, never live mail. Requires { confirmed: true }.
Inputs: confirmed, account_id. Returns: deleted, success
extract_action_itemsExtract Action ItemsARead-only
Scan a single email's body for action-item-looking lines (bullets with action verbs, TODO:/ACTION: markers, @mentions) and return a structured list with best-effort assignee and due-date fields. Heuristic — not a replacement for a real task extractor, but useful for quick triage.
Inputs: email_id (required), folder, account_id. Returns: action_items
extract_meetingExtract Meeting from ICSARead-only
Parse an iCalendar (ICS) attachment or inline VCALENDAR block out of an email and return structured meeting details. Returns { meeting: null } when no ICS block is found. Supports RFC 5545 line folding and the common VEVENT properties (SUMMARY, DTSTART, DTEND, LOCATION, ORGANIZER, ATTENDEE, DESCRIPTION, RRULE).
Inputs: email_id (required), folder, account_id. Returns: meeting
forward_emailForward EmailA
Forward an email to a new recipient. Original message is included as quoted content. Standard email headers (From, Date, Subject) are preserved in the forward body. Optionally prepend a message before the forwarded content.
Inputs: to (required), emailId (required), folder, message, +1 more. Returns: reason, success, messageId
fts_rebuildRebuild Local FTS IndexAIdempotent
Clear the local FTS5 index and rebuild it from the messages currently cached by the analytics layer (INBOX + Sent). Intended for use after major mailbox changes or when fts_search returns stale results. Returns the number of messages indexed.
Inputs: account_id. Returns: dbPath, indexed, messageCount
fts_searchFull-Text Search (Local Index)ARead-only
BM25-ranked keyword search over the locally indexed mail corpus. Supports FTS5 syntax: phrases ("exact phrase"), boolean (foo AND bar, foo OR bar, NOT baz), prefix (proto*), and column filters (subject:invoice from:alice). Use search_emails for live, authoritative IMAP results; use this for faster ranked local search after fts_rebuild has populated the index.
Inputs: query (required), limit, folder, account_id, +1 more. Returns: hits
fts_statusFTS Index StatusARead-only
Report the path, row count, and on-disk size of the local FTS5 index. Returns { available: false } when better-sqlite3 is not installed.
Inputs: account_id. Returns: dbPath, reason, available, messageCount, +1 more
get_connection_statusGet Connection StatusARead-only
Check live SMTP and IMAP health after setup. Returns connection status, TLS security mode (secure/insecure), and host/port details. Use setup_status first for credentials, Bridge reachability, or agent-approval diagnosis.
Inputs: account_id. Returns: imap, smtp, settingsConfigPath, settingsConfigured
get_contactsGet ContactsARead-only
Extract a ranked contact list from inbox and sent history, including send/receive counts, last interaction, inferred organization, and recency. Use this to choose recipients; use get_correspondence_profile for one relationship in depth.
Inputs: limit, sortBy, account_id. Returns: contacts
get_correspondence_profileGet Correspondence ProfileARead-only
Return relationship statistics for a single email address — volume sent/received, first and last interaction, average response time (if computable). Useful before drafting so the agent can match tone and recall context.
Inputs: email (required), account_id. Returns: name, email, emailsSent, exhaustive, +5 more
get_email_analyticsGet Email AnalyticsARead-only
Return a detailed inbox-and-sent dashboard: top senders/recipients, peak hours, attachment stats, and measured response times (null when insufficient data). Use get_email_stats for a compact overview or get_volume_trends for a custom date window. Results are cached for 5 minutes.
Inputs: account_id. Returns: topSenders, volumeTrends, topRecipients, attachmentStats, +2 more
get_email_by_idGet Email by IDARead-only
Fetch a single email's full content including body, attachment metadata (no binary content), isAnswered, and isForwarded flags. Use the id returned by get_emails or search_emails.
Inputs: emailId (required), folder, account_id. Returns: cc, id, to, body, +11 more
get_emailsGet EmailsARead-only
Fetch a page of emails from a folder. Returns summary fields (id, messageId, from, subject, date, isRead, bodyPreview). id is a per-folder IMAP UID; messageId is stable across folders. Use id with get_email_by_id for full content. Set summaryOnly=true to omit bodyPreview for lean listing/triage. Pass nextCursor from a previous response to get the next page.
Inputs: limit, cursor, folder, account_id, +1 more. Returns: count, emails, folder, nextCursor
get_emails_by_labelGet Emails by LabelARead-only
Legacy convenience wrapper for get_emails with folder set to Labels/. Prefer get_emails when you need the canonical pagination and summaryOnly options.
Inputs: label (required), limit, cursor, account_id. Returns: count, emails, folder, nextCursor
get_email_statsGet Email StatisticsARead-only
Return a compact inbox-and-sent dashboard: totals, unread count, most active contact, and storage estimate. Use get_email_analytics for deeper breakdowns or get_volume_trends for a custom date window. Results are cached for 5 minutes.
Inputs: account_id. Returns: totalEmails, totalFolders, unreadEmails, starredEmails, +5 more
get_foldersGet FoldersARead-only
List all email folders with message counts. Labels appear as folders with the Labels/ prefix (e.g. Labels/Work). Each folder reports folderType (system | user-folder | label) and, for system mailboxes, the IMAP specialUse attribute (\Inbox \Sent \Drafts \Trash \Junk \Archive \All \Flagged) — use these to identify the real Trash/Sent/Archive on a localised account rather than matching English names, and to avoid moving mail into the \All (All Mail) union view.
Inputs: account_id. Returns: folders
get_logsGet Server LogsARead-only
Retrieve recent server log entries filtered by level. Sensitive fields are redacted.
Inputs: level, limit, account_id. Returns: logs
get_server_versionGet Server VersionARead-only
Return the running mailpouch server version. Use to confirm which version is active before reporting bugs or checking for new features.
Inputs: account_id. Returns: version
get_threadGet Email ThreadARead-only
Return all messages that look like they belong to the same thread as the given email. Uses the normalized Subject (Re:/Fwd: stripped) to collect related messages from INBOX + Sent. Folder-restricted agents only receive messages from their allowed folders. Useful for summarising long conversations in one call.
Inputs: email_id (required), folder, account_id, max_messages. Returns: subject, messages
get_unread_countGet Unread CountARead-only
Get unread email count for each folder. Cheap call — use this before get_emails to decide whether to fetch. Returns object mapping folder path to unread count.
Inputs: account_id. Returns: totalUnread, unreadByFolder
get_volume_trendsGet Volume TrendsARead-only
Return daily sent/received volume for a caller-selected time window. Use get_email_analytics for the broader default dashboard, or get_unread_count for current unread counts.
Inputs: days, account_id. Returns: trends
list_labelsList LabelsARead-only
List only Proton Mail labels with message counts (Labels/ prefix), not regular folders. Use get_folders when you also need regular folders, folder type, or IMAP special-use metadata.
Inputs: account_id. Returns: count, labels
list_pending_remindersList Pending RemindersARead-only
List every pending no-reply reminder, sorted by earliest fireAt.
Inputs: account_id. Returns: reminders
list_proton_scheduledList Proton Scheduled EmailsARead-onlyIdempotent
List emails natively scheduled via Proton Mail web/mobile app (not MCP-scheduled emails). Reads the 'All Scheduled' IMAP folder exposed by Proton Bridge.
Inputs: account_id. Returns: note, count, emails, folder
list_scheduled_emailsList Scheduled EmailsARead-only
List all scheduled emails, including in-flight and outcome-unknown deliveries. Sorted by scheduledAt ascending.
Inputs: account_id. Returns: count, scheduled
mark_answeredMark Email AnsweredAIdempotent
Set or clear the \Answered flag on an email (the IMAP 'replied to' marker). answered defaults to true. Pass sourceFolder whenever the UID came from a folder other than INBOX.
Inputs: emailId (required), answered, account_id, sourceFolder. Returns: reason, success, messageId
mark_email_readMark Email Read/UnreadAIdempotent
Set the read/unread status of an email. isRead defaults to true. Pass sourceFolder whenever the UID came from a folder other than INBOX — IMAP UIDs are folder-scoped and silent no-ops can otherwise occur.
Inputs: emailId (required), isRead, account_id, sourceFolder. Returns: reason, success, messageId
mark_forwardedMark Email ForwardedAIdempotent
Set or clear the $Forwarded keyword on an email (the 'has been forwarded' marker the forward tool writes). forwarded defaults to true. Pass sourceFolder whenever the UID came from a folder other than INBOX.
Inputs: emailId (required), forwarded, account_id, sourceFolder. Returns: reason, success, messageId
move_emailMove EmailA
Move an email to a different folder. Common targets: Trash, Archive, Spam, INBOX, Folders/MyFolder. Moving to Trash or Spam requires { confirmed: true }. Pass sourceFolder whenever the UID came from a folder other than INBOX — IMAP UIDs are folder-scoped.
Inputs: emailId (required), targetFolder (required), confirmed, account_id, +1 more. Returns: reason, success, messageId
move_to_folderMove Email to Custom FolderA
Move an email to a custom folder (Folders/). Similar to move_to_label but for Folders/ paths.
Inputs: folder (required), emailId (required), account_id, sourceFolder. Returns: reason, success, messageId
move_to_labelAdd Label to EmailA
Add a label to an email. The email remains in its original folder and also appears in Labels/{label}; labels are additive, so an email can have multiple labels simultaneously. Use move_email only when the email should leave its source folder.
Inputs: label (required), emailId (required), account_id, sourceFolder. Returns: reason, success, messageId
move_to_spamMove Email to SpamADestructive
Move an email to the Spam folder. Convenience wrapper for move_email targeting Spam. Destructive: requires { confirmed: true }.
Inputs: emailId (required), confirmed, account_id, sourceFolder. Returns: reason, success, messageId
move_to_trashMove Email to TrashADestructive
Move an email to the Trash folder. Convenience wrapper for move_email targeting Trash. Note: labels are lost when an email is moved — label copies in Labels/ folders are not preserved. Destructive: requires { confirmed: true }.
Inputs: emailId (required), confirmed, account_id, sourceFolder. Returns: reason, success, messageId
remind_if_no_replyRemind If No ReplyA
Schedule a follow-up reminder for a message you've sent. Given the IMAP UID of a message in Sent, captures its Message-ID + recipient and fires a reminder after N days. Use check_reminders to retrieve due reminders; list_pending_reminders to audit; cancel_reminder to drop one.
Inputs: email_id (required), after_days (required), note, folder, +1 more. Returns: id, fireAt, subject, recipient
remove_labelRemove Label from EmailA
Remove a label from an email. The email is removed from Labels/{label} but remains in its original folder (Inbox, etc.). The UID passed must be the UID in Labels/{label} (not the INBOX/source UID) — Labels/ folders have their own UID space.
Inputs: label (required), emailId (required), account_id. Returns: reason, success, messageId
rename_folderRename FolderA
Rename a custom folder or label. Protected system folders cannot be renamed.
Inputs: newName (required), oldName (required), account_id. Returns: reason, success, messageId
reply_to_emailReply to EmailA
Send a reply to an existing email. Fetches the original to pre-fill To, Re:-prefixed subject, and thread references. Use replyAll to include original CC recipients.
Inputs: body (required), emailId (required), folder, isHtml, +2 more. Returns: reason, success, messageId
request_permission_escalationRequest Permission EscalationA
Request an increase in the server's active permission preset. YOU CANNOT APPROVE THIS YOURSELF — approval requires a human to open the settings UI (http://localhost:8766) and click Approve. Use check_escalation_status to poll for the result. Downgrading (reducing access) never requires a challenge.
Inputs: reason (required), target_preset (required), account_id.
restart_serverRestart MCP ServerADestructive
Restart the MCP server. Terminates Proton Bridge, shuts down gracefully, then spawns a fresh server process. If autoStartBridge is enabled the new process will re-launch Bridge automatically. Requires { confirmed: true }.
Inputs: confirmed, account_id. Returns: reason, success, messageId
save_draftSave DraftA
Save an email as a draft in the Drafts folder without sending it. All fields are optional — drafts can be incomplete. Returns the server-assigned UID.
Inputs: cc, to, bcc, body, +6 more. Returns: uid, error, success
schedule_emailSchedule EmailA
Schedule an email for future delivery (minimum 60 seconds from now, maximum 30 days). Definite failures are retried up to 3 times; ambiguous deliveries are never retried automatically. Use list_scheduled_emails to view pending sends and cancel_scheduled_email to cancel before delivery.
Inputs: to (required), body (required), send_at (required), subject (required), +7 more. Returns: id, success, scheduledAt
search_emailsSearch EmailsARead-only
Search the live mailbox by sender, recipient (To/CC/BCC), subject, body content, date range (received or sent), size, read/replied/starred/draft status, or attachment presence. Uses server-side IMAP SEARCH except hasAttachment, which filters locally. Use fts_search only for faster local ranked search after its index is built. Use folder for one folder or folders for many (pass ["*"] for all); returns summaries, so use get_email_by_id for full content.
Inputs: to, bcc, body, from, +17 more. Returns: count, emails, folder
send_emailSend EmailA
Send an email via Proton Mail SMTP (through Proton Bridge). Supports To/CC/BCC (comma-separated), plain text or HTML body, priority (high/normal/low), reply-to, and base64-encoded attachments. Returns messageId on success.
Inputs: to (required), body (required), subject (required), cc, +6 more. Returns: reason, success, messageId
send_test_emailSend Test EmailA
Send a test email to verify SMTP is working. Returns messageId on success. Use before relying on send_email in automated workflows.
Inputs: to (required), account_id, customMessage. Returns: reason, success, messageId
setup_statusSetup & Connection StatusARead-only
CALL THIS FIRST. Diagnoses the mailpouch install end-to-end and returns the single next action to get connected: whether credentials are configured, whether Proton Bridge is reachable, and whether this agent's access has been approved. Always available — works even before the agent is approved or credentials are set. Use get_connection_status afterwards for live IMAP/SMTP auth health.
Inputs: account_id. Returns: imap, smtp, state, nextStep, +8 more
shutdown_serverShutdown MCP ServerADestructive
Gracefully shut down the MCP server. Terminates Proton Bridge (regardless of whether this server launched it), disconnects IMAP/SMTP, scrubs credentials from memory, then exits. Requires { confirmed: true }.
Inputs: confirmed, account_id. Returns: reason, success, messageId
star_emailStar / Unstar EmailAIdempotent
Toggle the starred (flagged) status of an email. isStarred defaults to true. Pass sourceFolder whenever the UID came from a folder other than INBOX.
Inputs: emailId (required), isStarred, account_id, sourceFolder. Returns: reason, success, messageId
start_bridgeStart Proton BridgeAIdempotent
Launch Proton Mail Bridge if it is not already running. Waits up to 15 s for SMTP/IMAP ports to become reachable before returning.
Inputs: account_id. Returns: reason, success, messageId
sync_emailsSync EmailsAIdempotent
Fetch the latest emails from IMAP into the local cache. Use this to refresh the cache after Bridge syncs new messages. Returns emails fetched; use get_emails for paginated access.
Inputs: limit, folder, account_id. Returns: count, folder, success
sync_foldersSync FoldersAIdempotent
Refresh the folder list from IMAP (invalidates folder cache). Call this after creating/renaming/deleting folders in another client or if folder counts seem stale.
Inputs: account_id. Returns: success, folderCount
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
Loading tool schema changelog…
TDQS
Scored across 66 tools
Each tool serves a clearly distinct purpose, with well-defined differences between single and bulk operations, folder vs label moves, and explicit convenience wrappers. Overlaps like move_email and its convenience variants are documented as such, so agents can confidently select the right tool.
All tool names use a consistent verb_noun pattern in snake_case, with modifiers like 'bulk_' or 'check_' for clarity. Even convenience wrappers follow the pattern (e.g., move_to_trash, archive_email). No mixing of conventions or ambiguous verbs.
With 66 tools, the count is well above the recommended range for a coherent set. While many tools are justifiable for the domain, the proliferation of convenience wrappers (e.g., archive_email, move_to_trash) and separate bulk variants bloats the surface unnecessarily, making it harder for agents to navigate.
The tool set covers the full email lifecycle: CRUD, labels, folders, search, scheduling, reminders, contacts, analytics, and server management. Minor gaps exist, such as the absence of a single-email permanent deletion (only bulk empty_trash) and no tool for managing filters or rules, but these are reasonable omissions for a personal email server.
Maintenance
Related MCP Connectors
Email infrastructure for AI agents — send, receive, search, and reply to email over MCP.
Programmable email inbox for AI agents — JMAP, PoW auth, stdio MCP server.
Give AI agents their own email inboxes: list, read and send mail via scoped, revocable OAuth grants.
Email inboxes for AI agents: send, receive, reply, search, and manage threaded email over MCP.
Related MCP Servers
AlicenseNot gradedqualityDmaintenanceMCP server that enables Claude Code agents to send and read emails via SMTP/IMAP with per-agent credential isolation and audit logging.MIT- AlicenseNot gradedqualityCmaintenanceA self-hosted MCP server that gives AI agents full email superpowers.1MIT
- FlicenseNot gradedqualityBmaintenanceA lightweight MCP server providing AI agents a scoped, read-and-draft-only view into Proton Mail via Proton Bridge, with code-enforced restrictions preventing external access or sending.-
- AlicenseNot gradedqualityAmaintenanceA local MCP server that unifies email and calendar management across multiple providers, enabling AI agents to search, read, and write with explicit approval and local-first security.2Apache 2.0