proton-mail-mcp
v0.5.0
Published
MCP server for Proton Mail — send, read, and search email over SMTP and IMAP
Readme
Proton Mail MCP Server
A Model Context Protocol (MCP) server that gives AI assistants full access to your Proton Mail account -- send, read, search, and organize email over SMTP and IMAP.
Features
- Send, reply, and forward email via Proton Mail SMTP with threading headers
- Read email via IMAP through Proton Mail Bridge
- Attachments -- send files (base64) and download attachments from received messages
- Search messages by sender, recipient, subject, body, date, and flags
- Organize -- move, delete, and flag/unflag messages
- List folders with message and unread counts
- Safety first -- delete moves to Trash by default, read-only mode via
READONLY=true, MCP tool annotations for client-side confirmation prompts - Security hardened -- input validation, credential sanitization, rate limiting, attachment size limits
- Works with any MCP-compatible client (Claude Desktop, Claude Code, Cursor, etc.)
Prerequisites
- Node.js v24+
- A Proton Mail account with an SMTP password (how to get one)
- Proton Mail Bridge running locally (required for IMAP/read tools)
Quick Start
Add to your MCP client
Add the following to your client's MCP server configuration (Claude Desktop, Claude Code, Cursor, etc.):
{
"mcpServers": {
"protonmail": {
"command": "npx",
"args": ["-y", "proton-mail-mcp"],
"env": {
"PROTONMAIL_USERNAME": "[email protected]",
"PROTONMAIL_PASSWORD": "your-smtp-password"
}
}
}
}That's it — npx will download and run the server automatically. See Configuration for all available environment variables.
Install from source
If you prefer to run from a local clone:
git clone https://github.com/sethbang/proton-mail-mcp.git
cd proton-mail-mcp
npm install
npm run buildThen use this MCP config instead:
{
"mcpServers": {
"protonmail": {
"command": "node",
"args": ["/absolute/path/to/proton-mail-mcp/build/index.js"],
"env": {
"PROTONMAIL_USERNAME": "[email protected]",
"PROTONMAIL_PASSWORD": "your-smtp-password"
}
}
}
}Tools
Sending
All send tools return the Message-ID of the sent message in their response, which can be used to locate the message via IMAP search. send_email also attempts a best-effort lookup of the sent copy UID in the Sent folder.
send_email
Send an email using Proton Mail SMTP.
| Parameter | Required | Description |
|-----------|----------|-------------|
| to | Yes | Recipient address(es), comma-separated |
| subject | Yes | Subject line |
| body | Yes | Plain text or HTML content |
| isHtml | No | Whether body is HTML (default: false) |
| cc | No | CC recipient(s), comma-separated |
| bcc | No | BCC recipient(s), comma-separated |
| replyTo | No | Reply-To address (Proton SMTP may rewrite unauthenticated values) |
| fromName | No | Display name for the From field |
| attachments | No | Array of {filename, content, contentType} (base64-encoded content) |
reply_email
Reply to a message with proper threading headers (In-Reply-To, References). Includes the quoted original message with attribution by default.
| Parameter | Required | Description |
|-----------|----------|-------------|
| uid | Yes | UID of the message to reply to |
| body | Yes | Reply body content |
| folder | No | Folder containing the original message (default: INBOX) |
| isHtml | No | Whether body is HTML (default: false) |
| cc | No | Additional CC recipients, comma-separated |
| bcc | No | BCC recipients, comma-separated |
| replyAll | No | Reply to all recipients instead of just sender (default: false) |
| includeQuote | No | Include quoted original message below reply (default: true) |
forward_email
Forward a message to new recipients with threading headers. Original attachments are carried forward by default.
| Parameter | Required | Description |
|-----------|----------|-------------|
| uid | Yes | UID of the message to forward |
| to | Yes | Recipient address(es), comma-separated |
| folder | No | Folder containing the original message (default: INBOX) |
| body | No | Optional message to prepend above the forwarded content |
| isHtml | No | Whether body is HTML (default: false) |
| cc | No | CC recipients, comma-separated |
| bcc | No | BCC recipients, comma-separated |
| includeAttachments | No | Include original attachments in the forward (default: true) |
save_draft
Save an email as a draft without sending it. The draft is placed in the Drafts folder for the user to review and send manually. Returns the draft UID when available.
| Parameter | Required | Description |
|-----------|----------|-------------|
| to | Yes | Recipient address(es), comma-separated |
| subject | Yes | Subject line |
| body | Yes | Plain text or HTML content |
| isHtml | No | Whether body is HTML (default: false) |
| cc | No | CC recipient(s), comma-separated |
| bcc | No | BCC recipient(s), comma-separated |
| replyTo | No | Reply-To address (Proton SMTP may rewrite unauthenticated values) |
| fromName | No | Display name for the From field |
| folder | No | Folder to save draft in (default: Drafts) |
Reading
list_folders
List all mailbox folders with message and unread counts. No parameters.
list_messages
List recent messages from a folder. Supports UID-based pagination.
| Parameter | Required | Description |
|-----------|----------|-------------|
| folder | No | Folder path (default: INBOX) |
| limit | No | Max messages to return, 1-100 (default: 20) |
| beforeUid | No | Fetch messages with UIDs before this value (for pagination) |
read_message
Read a specific message by UID. Returns full headers, body, and attachment metadata. Prefers plain text; strips HTML tags from HTML-only messages. Body-part selection skips parts marked Content-Disposition: attachment, so a text/plain attachment sitting next to an HTML body is never returned as the body.
| Parameter | Required | Description |
|-----------|----------|-------------|
| uid | Yes | Message UID (from list_messages or search_messages) |
| folder | No | Folder path (default: INBOX) |
| preferHtml | No | Return raw HTML instead of stripped text (default: false) |
| maxBodyLength | No | Max body length before truncation, 100-500000 (default: 50000) |
| showHeaders | No | Include In-Reply-To, References, Reply-To, List-Unsubscribe, List-ID in an Extra Headers section (default: false) |
| stripUrls | No | Drop anchor URLs from stripped-HTML output, keeping only link text. Useful for summarizing newsletters (default: false) |
list_attachments
List attachment metadata (part numbers, filenames, types, sizes) for a message without downloading the body. Composes with download_attachment for bulk extraction.
| Parameter | Required | Description |
|-----------|----------|-------------|
| uid | Yes | Message UID |
| folder | No | Folder containing the message (default: INBOX) |
download_attachment
Download an attachment by MIME part number. Use list_attachments or read_message first to see available parts. Returns base64-encoded content. Bad part numbers produce an actionable error listing known parts.
| Parameter | Required | Description |
|-----------|----------|-------------|
| uid | Yes | Message UID |
| partNumber | Yes | MIME part number (from read_message / list_attachments) |
| folder | No | Folder containing the message (default: INBOX) |
search_messages
Search messages by various criteria. Date filters use IMAP semantics: since is inclusive, before is exclusive.
| Parameter | Required | Description |
|-----------|----------|-------------|
| folder | No | Folder to search (default: INBOX) |
| from | No | Filter by sender |
| to | No | Filter by recipient |
| subject | No | Filter by subject (substring match) |
| body | No | Filter by body content (substring match) |
| since | No | Messages on or after this date (YYYY-MM-DD, inclusive) |
| before | No | Messages strictly before this date (YYYY-MM-DD, exclusive) |
| seen | No | true = read, false = unread |
| flagged | No | Filter by flagged/starred status |
| limit | No | Max results, 1-100 (default: 20) |
get_thread
Get all messages in a conversation thread by walking In-Reply-To and References headers. Returns messages sorted chronologically (oldest first).
Prefer messageId — Message-IDs are globally unique, so this sidesteps the UID-collision footgun (UIDs are per-folder in IMAP) and walks INBOX + Sent + All Mail by default to catch replies that span folders. Output rows are tagged UID X @FolderName to disambiguate per-folder copies.
| Parameter | Required | Description |
|-----------|----------|-------------|
| messageId | No | RFC 5322 Message-ID (preferred over uid+folder) |
| uid | No | UID of a thread message (used when messageId is omitted; folder-scoped) |
| folder | No | Folder the UID lives in when using uid mode (default: INBOX) |
| folders | No | Override the default folder walk when messageId is set (default: ["INBOX", "Sent", "All Mail"]) |
| limit | No | Max messages to return, 1-50 (default: 25) |
Organizing
move_message
Move a message to a different folder. Returns the new UID in the destination folder when available (requires UIDPLUS server support).
| Parameter | Required | Description |
|-----------|----------|-------------|
| uid | Yes | Message UID |
| destination | Yes | Destination folder path (e.g. Archive, Trash, Spam) |
| folder | No | Source folder (default: INBOX) |
delete_message
Delete a message. By default moves to Trash for safety; set permanent=true to permanently expunge. Returns the new UID in Trash when available.
| Parameter | Required | Description |
|-----------|----------|-------------|
| uid | Yes | Message UID |
| folder | No | Folder containing the message (default: INBOX) |
| permanent | No | If true, permanently expunge instead of moving to Trash (default: false) |
update_message_flags
Add or remove flags on a message. RFC 3501 system flags: \Seen (read), \Flagged (starred), \Answered, \Draft, \Deleted, \Recent. User-defined keywords without a backslash prefix are also accepted (alphanumeric + underscore, e.g. Important, Custom_Tag). Unknown \-prefixed names are rejected.
| Parameter | Required | Description |
|-----------|----------|-------------|
| uid | Yes | Message UID |
| folder | No | Folder containing the message (default: INBOX) |
| flagsToAdd | No | Flags to add (e.g. ["\\Seen", "\\Flagged"]) |
| flagsToRemove | No | Flags to remove (e.g. ["\\Seen"]) |
mark_all_read
Mark all unread messages in a folder as read. Optionally limit to messages older than a given date.
| Parameter | Required | Description |
|-----------|----------|-------------|
| folder | No | Folder to mark as read (default: INBOX) |
| olderThan | No | Only mark messages strictly before this date (YYYY-MM-DD, exclusive) |
Configuration
Environment Variables
SMTP (required):
| Variable | Default | Description |
|----------|---------|-------------|
| PROTONMAIL_USERNAME | -- | Your Proton Mail email address |
| PROTONMAIL_PASSWORD | -- | Your SMTP password (not your login password) |
| PROTONMAIL_HOST | smtp.protonmail.ch | SMTP host |
| PROTONMAIL_PORT | 587 | SMTP port |
| PROTONMAIL_SECURE | false | Use TLS (true for port 465) |
IMAP (for read/search tools):
| Variable | Default | Description |
|----------|---------|-------------|
| IMAP_HOST | 127.0.0.1 | Proton Mail Bridge host |
| IMAP_PORT | 1143 | Bridge IMAP port |
| IMAP_SECURE | false | Use TLS |
| IMAP_USERNAME | falls back to SMTP username | Bridge username |
| IMAP_PASSWORD | falls back to SMTP password | Bridge password |
Other:
| Variable | Default | Description |
|----------|---------|-------------|
| DEBUG | false | Enable verbose logging to stderr |
| READONLY | false | Disable all mutating tools (send, reply, forward, move, delete, flags) |
Development
npm run build # Compile TypeScript
npm run watch # Compile in watch mode
npm run test # Run tests
npm run test:watch # Run tests in watch mode
npm run lint # Lint with ESLint
npm run format # Format with Prettier
npm run inspector # Launch MCP inspectorAcknowledgments
Originally based on protonmail-mcp by amotivv, inc.
License
MIT -- see LICENSE for details.
