# Proton Mail Mcp

> Unofficial MCP server for Proton Mail (not affiliated with Proton AG) — send, read, search & organize email over SMTP/IMAP

- **Type:** MCP server
- **Install:** `agentstack add mcp-sethbang-proton-mail-mcp`
- **Verified:** Yes — security-reviewed for prompt injection and unsafe behavior
- **Seller:** [sethbang](https://agentstack.voostack.com/s/sethbang)
- **Installs:** 0
- **Category:** [Communication](https://agentstack.voostack.com/c/communication)
- **Latest version:** 0.1.0
- **License:** MIT
- **Upstream author:** [sethbang](https://github.com/sethbang)
- **Source:** https://github.com/sethbang/proton-mail-mcp
- **Website:** https://www.npmjs.com/package/proton-mail-mcp

## Install

```sh
agentstack add mcp-sethbang-proton-mail-mcp
```

Requires the [AgentStack CLI](https://agentstack.voostack.com/docs/cli). Works with Claude Code, Cursor, and any MCP-compatible agent.

## About

# Proton Mail MCP Server

[](https://www.npmjs.com/package/proton-mail-mcp)
[](https://opensource.org/licenses/MIT)
[](https://glama.ai/mcp/servers/sethbang/proton-mail-mcp)

A [Model Context Protocol](https://modelcontextprotocol.io) (MCP) server that gives AI assistants full access to your Proton Mail account -- send, read, search, and organize email over SMTP and IMAP.

> ⚠️ **Unofficial — not affiliated with Proton.** This is an independent, community-built project. It is **not** developed, endorsed, sponsored, or supported by Proton AG. "Proton", "Proton Mail", and "Proton Mail Bridge" are trademarks of Proton AG, used here only to describe interoperability. It talks to Proton Mail over the standard SMTP submission endpoint and the locally-run Proton Mail Bridge; no Proton private API is used. Use at your own risk.

## Demo

## Features

- **Send, reply, and forward** email via Proton Mail SMTP with threading headers; dedicated `reply_all_email` tool
- **Markdown bodies** -- pass `markdownBody` to `send_email` / `reply_email` / `reply_all_email` / `forward_email`; rendered to HTML with a plain-text fallback
- **Read email** via IMAP through [Proton Mail Bridge](https://proton.me/mail/bridge)
- **Attachments** -- send files (base64), download to memory or to disk (`saveTo` + `ALLOW_FILE_DOWNLOAD_DIR`), forward a subset by part number
- **Search** messages by sender, recipient, subject, body, date, flags, size, List-ID, attachment presence, attachment name (substring), and attachment MIME type
- **Organize** -- move, delete, and flag/unflag messages individually or in bulk; bulk-update labels too
- **Bulk operations** -- `bulk_move`, `bulk_delete`, `bulk_update_flags`, `bulk_update_labels` with `dryRun` preview and XOR uid/match input
- **Folder & label management** -- `create_folder`, `create_label`, `rename_folder`, `delete_folder` (non-destructive on Proton); `empty_folder` (opt-in via `ALLOW_EMPTY_FOLDER=true`; not recommended)
- **Labeling** -- `update_message_labels` adds and removes Proton labels on a message (additive — message stays in its source folder)
- **Aggregations** -- `count_messages`, `folder_stats`, `top_senders` (with `excludeSelf` + per-row direction) for inbox analytics
- **Thread mutations** -- `move_thread`, `delete_thread`, `flag_thread` with optional cross-folder walk; `get_thread` dedupes by Message-ID across mailbox copies
- **Snippets** -- optional `includeSnippet` on `list_messages` and `search_messages` for at-a-glance previews
- **List folders** with message and unread counts
- **Honest accounting** -- post-STORE FETCH verify on flags/labels surfaces silently-dropped operations as `notApplied`; sent-copy lookup retries SEARCH to defeat Proton's index lag; Reply-To rewrites are surfaced in the send response
- **Safety first** -- delete moves to Trash by default (via special-use resolver), read-only mode via `READONLY=true`, `dryRun` on all bulk ops, MCP tool annotations for client-side confirmation prompts, path-traversal defense on filesystem-touching tools
- **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](https://nodejs.org/) v24+
- A [Proton Mail](https://proton.me/mail) account with an SMTP password ([how to get one](https://proton.me/support/smtp-submission))
- [Proton Mail Bridge](https://proton.me/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.):

```json
{
  "mcpServers": {
    "protonmail": {
      "command": "npx",
      "args": ["-y", "proton-mail-mcp"],
      "env": {
        "PROTONMAIL_USERNAME": "your-email@protonmail.com",
        "PROTONMAIL_PASSWORD": "your-smtp-password"
      }
    }
  }
}
```

That's it — `npx` will download and run the server automatically. See [Configuration](#configuration) for all available environment variables.

### Install from source

If you prefer to run from a local clone:

```bash
git clone https://github.com/sethbang/proton-mail-mcp.git
cd proton-mail-mcp
npm install
npm run build
```

Then use this MCP config instead:

```json
{
  "mcpServers": {
    "protonmail": {
      "command": "node",
      "args": ["/absolute/path/to/proton-mail-mcp/build/index.js"],
      "env": {
        "PROTONMAIL_USERNAME": "your-email@protonmail.com",
        "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. All four send tools (`send_email`, `reply_email`, `reply_all_email`, `forward_email`) perform a best-effort lookup of the sent copy UID in the resolved `\Sent` folder (retried across ~30s to defeat Proton's indexing lag) and lead the response with a machine-parseable token prefix — `[sent-copy:verified]` or `[sent-copy:unverified]`, plus `[reply-to:preserved|rewritten|stripped|unverified]` when `replyTo` was requested. Agents can grep these tokens instead of text-matching prose. When the Sent-copy lookup converges, the response also includes a `Sent copy UID: N in Sent` clause; when it doesn't, the leading verb switches from "X sent successfully (Sent-copy verified)" to "X send accepted by SMTP (Sent-copy unverified within the lookup window)" — the message did go out, but per-delivery verification couldn't be completed within the budget.

> 🛡️ **HTML sanitization defaults to ON** (since v1.0.0). Send / reply / forward / save_draft tools strip ``, event handlers, inline `style` attributes, and remote `` beacons via a conservative allowlist before delivery. Pass `sanitizeHtml: false` to preserve full-fidelity HTML for trusted-content workflows. The success response notes when sanitization ran.
>
> ⚠️ **Display-name spoofing partially defended.** As of v1.0.0, `fromName` rejects values containing `@` by default — without this guard, a `fromName` like `"Anthropic Security "` reaches the wire as `Anthropic Security security@anthropic.com` after angle-bracket sanitization, which mail clients render as a forged sender. Pass `allowAddressLikeFromName: true` to override for legitimate product names. SMTP itself still prevents *address-level* spoofing (the envelope `From:` is bound to your authenticated identity), and arbitrary display names without `@` are still allowed (e.g. `"CEO of Acme"`) — treat `fromName` like any other user-controllable string.

All send/reply/forward/save_draft tools accept an alternative `markdownBody` parameter (rendered to HTML via [marked](https://github.com/markedjs/marked)) — mutually exclusive with `body`+`isHtml`. `sanitizeHtml` applies after Markdown rendering.

> 🛡️ **Outbound preview + self-only lock.** `send_email`, `reply_email`, `reply_all_email`, and `forward_email` accept `dryRun: true` — it runs all validation and resolves the complete recipient set (To/CC/BCC, including the reply-all fan-out) and returns it **without sending**, leading with an `[outbound:*]` token and an explicit list of any external (non-self) recipients. Use it to confirm exactly who would receive the mail before a live call. For throwaway/QA accounts, set `RESTRICT_OUTBOUND_TO_SELF=true` (see [Environment variables](#environment-variables)) to *refuse* any live send to a non-self recipient — preventing an agent from fanning real mail out to external addresses embedded in seed data. (Preview is caller-opt-in; the env lock is enforced server-side with no per-call override.)

#### `send_email`

Send an email using Proton Mail SMTP.

| Parameter | Required | Description |
|-----------|----------|-------------|
| `to` | Yes | Recipient address(es), comma-separated |
| `subject` | Yes | Subject line |
| `body` | Conditional | Plain text or HTML content. Required unless `markdownBody` is provided |
| `isHtml` | No | Whether `body` is HTML (default: `false`) |
| `markdownBody` | No | Markdown source — rendered to HTML before sending. Mutually exclusive with `body`/`isHtml` |
| `sanitizeHtml` | No | Strip scripts, event handlers, inline styles, and remote `` beacons via a conservative allowlist when the body is HTML. **Default `true` as of v1.0.0**. Pass `false` to preserve full-fidelity HTML. No-op on plain-text |
| `cc` | No | CC recipient(s), comma-separated |
| `bcc` | No | BCC recipient(s), comma-separated (count in response dedupes against To/CC) |
| `replyTo` | No | Reply-To address (Proton SMTP may rewrite values that don't match an authenticated identity; rewrites are surfaced in the response) |
| `fromName` | No | Display name for the From field. Rejects values containing `@` by default (display-name-as-address spoofing defense — see warning above). Pass `allowAddressLikeFromName: true` to override for legitimate cases (product names with `@`) |
| `allowAddressLikeFromName` | No | Opt-in escape valve for `fromName` containing `@`. Default `false` |
| `attachments` | No | Array of `{filename, content, contentType}`. `content` is validated as base64 at the Zod boundary (catches malformed payloads before nodemailer silently emits garbage bytes); `contentType` is validated as a MIME `type/subtype` (catches malformed values before SMTP rejects the message) |
| `dryRun` | No | Validate and resolve the full recipient set (To/CC/BCC) + subject + body **without sending**. Returns a preview leading with an `[outbound:*]` token and an explicit list of any external (non-self) recipients (default: `false`) |

#### `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` | Conditional | Reply body content. Required unless `markdownBody` is provided |
| `folder` | No | Folder containing the original message (default: `INBOX`) |
| `isHtml` | No | Whether `body` is HTML (default: `false`) |
| `markdownBody` | No | Markdown source. Mutually exclusive with `body`/`isHtml` |
| `sanitizeHtml` | No | Strip scripts, event handlers, inline styles, and remote `` beacons via a conservative allowlist when the body is HTML. **Default `true` as of v1.0.0**. Pass `false` to preserve full-fidelity HTML. No-op on plain-text |
| `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`). Prefer the dedicated `reply_all_email` tool for discoverability |
| `includeQuote` | No | Include quoted original message below reply (default: `true`) |
| `dryRun` | No | Resolve the reply recipients (incl. reply-all fan-out) + subject **without sending**; returns a preview of who would receive it (default: `false`) |

#### `reply_all_email`

Reply to all original recipients (sender + TO + CC), excluding the authenticated user. Same threading + quoting semantics as `reply_email`. Use this instead of `reply_email` + `replyAll: true` for clarity. Self-exclusion covers the whole recipient set including the primary `To`: replying-all to a message **you** sent reaches the original recipients rather than looping back to you.

| Parameter | Required | Description |
|-----------|----------|-------------|
| `uid` | Yes | UID of the message to reply to |
| `body` | Conditional | Reply body content. Required unless `markdownBody` is provided |
| `folder` | No | Folder containing the original message (default: `INBOX`) |
| `isHtml` | No | Whether `body` is HTML (default: `false`) |
| `markdownBody` | No | Markdown source. Mutually exclusive with `body`/`isHtml` |
| `sanitizeHtml` | No | Strip scripts, event handlers, inline styles, and remote `` beacons via a conservative allowlist when the body is HTML. **Default `true` as of v1.0.0**. Pass `false` to preserve full-fidelity HTML. No-op on plain-text |
| `cc` | No | Additional CC recipients beyond original to+cc, comma-separated |
| `bcc` | No | BCC recipients, comma-separated |
| `includeQuote` | No | Include quoted original message below reply (default: `true`) |
| `dryRun` | No | Resolve the full reply-all fan-out (sender + original To + CC, minus self) **without sending**; returns a preview of every recipient. Recommended before a live reply-all on unfamiliar mail (default: `false`) |

#### `forward_email`

Forward a message to new recipients. Original attachments are carried forward by default. Unlike reply, a forward does **not** set `In-Reply-To`/`References` — it starts its own conversation and leaves the original message's `\Answered` flag untouched.

| 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`) |
| `markdownBody` | No | Markdown source for the prepended message. Mutually exclusive with `body`/`isHtml` |
| `sanitizeHtml` | No | Strip scripts, event handlers, inline styles, and remote `` beacons in the prepended HTML body. Does NOT sanitize the forwarded original content. **Default `true` as of v1.0.0**; pass `false` to preserve full-fidelity HTML |
| `cc` | No | CC recipients, comma-separated |
| `bcc` | No | BCC recipients, comma-separated |
| `includeAttachments` | No | Include original attachments in the forward (default: `true`). Pass `false` to strip all attachments |
| `attachmentParts` | No | Forward only the listed MIME part numbers (e.g. `["2", "3.1"]`). Discover parts via `list_attachments`. Mutually exclusive with `includeAttachments: false` |
| `dryRun` | No | Resolve recipients (To/CC/BCC) + subject + attachment count **without sending or downloading attachment bytes**; returns a recipient preview (default: `false`) |

#### `save_draft`

Save an email as a draft without sending it. The draft is placed in the user's `\Drafts` special-use folder (resolved at runtime; falls back to literal `Drafts` if no annotation). Returns the draft UID when available.

> ⓘ **Destination changed in v1.0.0** — the `folder` parameter is gone. Previously callers could land a `\Draft`-flagged message in any folder, including INBOX, which was confusing for anyone scanning the mailbox.

| Parameter | Required | Description |
|-----------|----------|-------------|
| `to` | Yes | Recipient address(es), comma-separated |
| `subject` | Yes | Subject line |
| `body` | Conditional | Plain text or HTML content. Required unless `markdownBody` is provided |
| `isHtml` | No | Whether `body` is HTML (default: `false`) |
| `markdownBody` | No | Markdown source — rendered to HTML before saving. Mutually exclusive with `body`/`isHtml` |
| `sanitizeHtml` | No | Strip scripts, event handlers, inline styles, and remote `` beacons via a conservative allowlist when the body is HTML. **Default `true`**. No-op on plain-text |
| `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. Rejects values containing `@` by default (display-name-as-address spoofing defense — see warning above). Pass `allowAddressLikeFromName: true` to override for legitimate cases (product names with `@`) |
| `allowAddressLikeFromName` | No | Opt-in escape valve for `fromName` containing `@`. Default `false` |
| `replaceDraftUid` | No | UID of a prior draft to atomically replace. APPENDs the new draft first; deletes the old one only on success so a failed append never destroys the original. Errors

…

## Source & license

This open-source MCP server is cataloged on AgentStack and links to its original source — we do not rehost the code.

- **Author:** [sethbang](https://github.com/sethbang)
- **Source:** [sethbang/proton-mail-mcp](https://github.com/sethbang/proton-mail-mcp)
- **License:** MIT
- **Homepage:** https://www.npmjs.com/package/proton-mail-mcp

Install and usage instructions live in the source repository linked above.

## Pricing

- **Free** — Free

## Security capabilities

Automated source analysis of v0.1.0 — what this tool can access:

- **Network access:** no
- **Filesystem access:** no
- **Shell / process execution:** no
- **Environment & secrets:** no
- **Dynamic code execution:** no

*"Yes" means the capability is present in the source — more access means more to trust, not that it is unsafe.*


## Versions

- **0.1.0** — security scan: passed — Imported from the upstream source.

## Links

- Listing page: https://agentstack.voostack.com/l/mcp-sethbang-proton-mail-mcp
- Seller: https://agentstack.voostack.com/s/sethbang
- Browse the marketplace: https://agentstack.voostack.com/browse

---
Listed on AgentStack — the marketplace for AI agent skills and MCP servers. Every listing is security-reviewed. Creators keep 70%.
