{
  "markdown": "# your-mail-mcp\n\n[![MCP registry](https://img.shields.io/badge/MCP_registry-io.github.wildsurfer%2Fyour--mail--mcp-blue)](https://registry.modelcontextprotocol.io/v0.1/servers?search=io.github.wildsurfer)\n[![Glama score](https://glama.ai/mcp/servers/wildsurfer/your-mail-mcp/badges/score.svg)](https://glama.ai/mcp/servers/wildsurfer/your-mail-mcp)\n\nYour mail already holds the answers: booking references, gate codes,\ninvoices, warranty periods, promises people made in writing. This server lets\nyour AI assistant find them, and it can only read.\n\n**Ask it things like:**\n\n- \"Find the booking reference for the June ferry.\"\n- \"What was the wifi password the hotel sent last summer?\"\n- \"What did the accountant answer about VAT, and when?\"\n- \"Collect everything between me and the builder about the roof, in order,\n  and summarize who promised what.\"\n- \"What arrived this morning, across all my accounts, that actually needs me?\"\n\n**Use it for:**\n\n- **Search that understands questions.** Full-text search over your whole\n  history, every account in one index, phrased the way you think.\n- **Triage from your phone.** A morning summary of what came in overnight,\n  with junk already filtered out, from wherever you are.\n- **Mail as context for other work.** Pull the client's requirements out of\n  the thread and into your coding or writing session.\n- **Agents you can leave running.** The process has no path that sends,\n  deletes or moves a message. A malicious email that reaches your assistant\n  gets read, and that is all that can happen to it. Scheduled digests and\n  always-on agents are a calm thing to run.\n\nSetup is two files and `docker compose up -d`. See [Quick start](#quick-start).\n\n## How it works\n\nA self-hosted MCP server. It mirrors one or more IMAP accounts into a local\nmaildir with [mbsync](https://isync.sourceforge.io/), indexes them with\n[notmuch](https://notmuchmail.org/), and answers tool calls from that index.\nThe Claude and ChatGPT apps attach over HTTPS with OAuth; Claude Code, Codex,\nCursor and Claude Desktop attach over stdio on the same machine. Any MCP\nclient works, so if you want full privacy you can attach one backed by a\nlocal LLM and your mail never leaves your machine.\n\n![How your-mail-mcp works: mail is pulled from IMAP providers into a local mirror, indexed by notmuch, and served to an MCP client through an OAuth gate, with no write path back to the providers](docs/diagrams/how-it-works.png)\n\nMail only ever moves left to right in that picture. The mirror is pull-only\nby configuration. The one connection the Go code makes toward a provider is\nan IMAP `LIST` per account, to learn what that server calls its junk and\ntrash folders; it never selects a mailbox and never fetches a message.\n[Security](#security) has the full list of what the process cannot do. The\ndiagram source is\n[`docs/diagrams/how-it-works.html`](docs/diagrams/how-it-works.html).\n\n## Quick start\n\nOn your machine, for your machine. Docker is the only requirement; the image\nat `ghcr.io/wildsurfer/your-mail-mcp` is built by CI for amd64 and arm64.\n\n```bash\nmkdir your-mail && cd your-mail\ncurl -fsSLO https://raw.githubusercontent.com/wildsurfer/your-mail-mcp/main/compose.yaml\ncurl -fsSL https://raw.githubusercontent.com/wildsurfer/your-mail-mcp/main/accounts.example.json -o accounts.json\n```\n\nPut your accounts in `accounts.json`. `${WORK_PASS}` is replaced from the\nenvironment, so the file itself holds no password:\n\n```json\n{\n  \"accounts\": [\n    { \"name\": \"work\", \"host\": \"imap.gmail.com\", \"user\": \"you@example.com\", \"password\": \"${WORK_PASS}\" },\n    { \"name\": \"personal\", \"host\": \"imap.mail.me.com\", \"user\": \"you\", \"password\": \"${PERSONAL_PASS}\" }\n  ]\n}\n```\n\nPut the passwords in `.env` next to `compose.yaml`:\n\n```bash\n# .env\nWORK_PASS=your-gmail-app-password\nPERSONAL_PASS=your-icloud-app-specific-password\n```\n\n`compose.yaml` passes `WORK_PASS` and `PERSONAL_PASS` into the container. An\naccount with another variable name needs that name added under\n`environment:` there as well.\n\nTwo provider details cost people the most time. Gmail accepts only an app\npassword over IMAP, and app passwords need 2-step verification turned on\nfirst. iCloud wants the short name before `@icloud.com` as `user`; the full\naddress fails to log in. Every key of the file is in\n[the reference](docs/reference.md#the-accounts-file).\n\nThese two files hold your mail passwords. Keep the directory out of version\ncontrol and out of backups that leave the machine.\n\n```bash\ndocker compose up -d\ndocker compose logs -f     # watch the first sync\n```\n\nToday's INBOX mail is searchable within minutes. The full history follows at\nwhatever pace the provider allows, and the `status` tool reports how far it\nhas got. A large Gmail account takes days, because Google caps IMAP downloads\nat about 2.5GB per day; set `SYNC_TIMEOUT=8h` in `.env` for that first\nmirror. [Provider notes](docs/reference.md#provider-notes) has the details.\n\nThen connect a client. Claude Code:\n\n```bash\nclaude mcp add your-mail -- docker exec -i your-mail-mcp your-mail-mcp stdio\n```\n\nCursor and VS Code add it in one click, once the stack is up:\n\n[![Add to Cursor](https://cursor.com/deeplink/mcp-install-dark.svg)](https://cursor.com/install-mcp?name=your-mail&config=eyJjb21tYW5kIjoiZG9ja2VyIiwiYXJncyI6WyJleGVjIiwiLWkiLCJ5b3VyLW1haWwtbWNwIiwieW91ci1tYWlsLW1jcCIsInN0ZGlvIl19)\n[![Install in VS Code](https://img.shields.io/badge/VS_Code-Install_Server-0098FF?style=flat-square&logo=visualstudiocode&logoColor=white)](https://insiders.vscode.dev/redirect/mcp/install?name=your-mail&config=%7B%22command%22%3A%22docker%22%2C%22args%22%3A%5B%22exec%22%2C%22-i%22%2C%22your-mail-mcp%22%2C%22your-mail-mcp%22%2C%22stdio%22%5D%7D)\n\n<details>\n<summary>Claude Desktop, Cursor, Codex and other stdio clients</summary>\n\nAny client that starts an MCP server as a command:\n\n```json\n{ \"command\": \"docker\", \"args\": [\"exec\", \"-i\", \"your-mail-mcp\", \"your-mail-mcp\", \"stdio\"] }\n```\n\nEach session is a bridge into the running container, so every client sees\nthe same index and the same sync. Close the client and the session goes with\nit.\n\nClaude Code can also take it as a plugin, which adds the server and an\n`email` skill that knows the query syntax:\n\n```\n/plugin marketplace add wildsurfer/your-mail-mcp\n/plugin install your-mail@your-mail-mcp\n```\n\nWithout a running stack,\n`docker run -i --rm --env-file .env -v index:/index -v mail:/mail -v ./accounts.json:/config/accounts.json:ro ghcr.io/wildsurfer/your-mail-mcp`\nstarts a daemon for the life of one session. Fine for a look; use compose for\nanything you want kept fresh.\n\n</details>\n\n## Use it from your phone\n\nThe Claude and ChatGPT smartphone apps reach a connector through the vendor's\nservers, so the server needs a public HTTPS address. A tunnel gives it one:\nthe tunnel dials out, nothing listens on your home network, and the mail\nstays on your machine.\n\nWith [Tailscale](https://tailscale.com/) installed, one command, the same on\nmacOS and Linux:\n\n```bash\ntailscale funnel --bg 8080\n```\n\nIt prints a hostname like `https://your-machine.your-tailnet.ts.net`, and\n`--bg` keeps it running across reboots. Put that hostname in `.env` together\nwith a passphrase, then restart:\n\n```bash\n# .env\nPUBLIC_URL=https://your-machine.your-tailnet.ts.net\nOAUTH_PASSPHRASE=pick-a-long-one-you-can-type-on-a-smartphone\n```\n\n```bash\ndocker compose up -d\n```\n\n`OAUTH_PASSPHRASE` is the only credential between the internet and your\nmail. A wrong guess costs one second and guesses are serialised, and neither\nof those saves a short passphrase. Use a long one you can still type on a\nphone.\n\nFunnel needs HTTPS certificates and the Funnel node attribute enabled for\nyour tailnet; the CLI offers to add the policy line the first time.\n`tailscale funnel status` shows what is exposed, and\n`tailscale funnel --https=443 off` takes it down.\n\nNow add the connector. Neither the Claude nor the ChatGPT smartphone app can\nadd one, so you do it once on the web, and it then appears on the phone.\n\n1. On [claude.ai](https://claude.ai) or in Claude Desktop, open\n   **Settings → Connectors** and add a custom connector.\n2. Give it a name and the URL `<PUBLIC_URL>/mcp`. Leave the advanced OAuth\n   fields empty; the server registers clients itself.\n3. Claude opens the consent page. Enter your `OAUTH_PASSPHRASE`.\n4. Open the Claude app on your phone. The connector is already there; turn it\n   on for a conversation from the tools menu in the composer.\n\n`PUBLIC_URL` has to match what you type into the client exactly. The server\npublishes `PUBLIC_URL + /mcp` as the `resource` in its OAuth metadata, and a\nmismatch there is the most common reason a connector refuses to add.\n\n<details>\n<summary>Cloudflare Tunnel instead of Tailscale, on your own domain</summary>\n\nUse this for a hostname on a domain you own. `mail.example.com` below has to\nbe **your** domain, already added to your Cloudflare account; Cloudflare does\nnot hand out hostnames for named tunnels.\n\n```bash\ncloudflared tunnel login\ncloudflared tunnel create your-mail\n```\n\n`create` prints the tunnel's UUID and the path of the credentials file it\nwrote; `cloudflared tunnel list` prints the UUID again if you lose it. Route\nthe hostname, then write `~/.cloudflared/config.yml`:\n\n```bash\ncloudflared tunnel route dns your-mail mail.example.com\n```\n\n```yaml\ntunnel: your-mail\ncredentials-file: /Users/you/.cloudflared/f9e2….json   # the path create printed\nurl: http://localhost:8080\n```\n\n```bash\ncloudflared tunnel run your-mail\n```\n\nTo keep it running: on Linux, `sudo cloudflared service install`. On macOS,\ninstall it through Homebrew and use `brew services start cloudflared`,\nbecause the `sudo` install path looks for its certificate under the root\nuser's home and will not find the one `cloudflared tunnel login` wrote to\nyours.\n\nThen set `PUBLIC_URL=https://mail.example.com` in `.env` and\n`docker compose up -d`.\n\n</details>\n\n<details>\n<summary>ChatGPT, and Claude Code or Codex from another machine</summary>\n\n**ChatGPT.** Custom MCP connectors live behind developer mode, which needs a\nPro, Plus, Business, Enterprise or Education account and is only available\non the web.\n\n1. In ChatGPT on the web, open **Settings → Security and login** and turn on\n   **Developer mode**. On Business and Enterprise workspaces an admin may have\n   to allow it first.\n2. Add a connector for a remote MCP server with the URL `<PUBLIC_URL>/mcp`\n   and OAuth as the authentication. ChatGPT supports dynamic client\n   registration, so there is nothing to paste.\n3. Approve the consent page with your `OAUTH_PASSPHRASE`.\n4. Open ChatGPT on your phone and enable the connector in a chat.\n\nThese menus move. If the names above do not match what you see, look for\ndeveloper mode in settings, then for the place that adds a connector by URL.\nChatGPT disables some MCP write actions on mobile, which changes nothing\nhere because this server has none.\n\n**Claude Code:**\n\n```bash\nclaude mcp add --transport http your-mail https://your-host/mcp\n```\n\n**Codex:**\n\n```bash\ncodex mcp add your-mail --url https://your-host/mcp\ncodex mcp login your-mail\n```\n\n</details>\n\n## Run it on a server\n\nPick this when the mirror should stay up whether or not your machine is on.\nIt costs a few dollars a month and one real trade-off: a full plaintext copy\nof your mail moves onto a rented disk, with the app passwords next to it. The\ninstall is the quick start plus a tunnel, on someone else's computer, and the\nbox needs hardening before it holds your mail. Both are in\n[docs/server.md](docs/server.md).\n\n## The tools\n\nEleven tools, all read-only:\n\n| Tool | What it does |\n|---|---|\n| `search` | Search mail. Returns thread summaries as JSON. |\n| `ids` | Return the message ids matching a query. |\n| `files` | Return the maildir file paths matching a query. |\n| `count` | Count the messages matching a query. |\n| `show` | Show one message: headers and decoded body, as JSON. |\n| `thread` | Show the whole thread containing a message. Excludes junk/trash replies by default; set `include_excluded` to include them. |\n| `text` | Return the plain-text body of one message, converting HTML. |\n| `folders` | List accounts, their folders, index tags, and each account's last sync and last error. |\n| `refresh` | Sync every folder of one account or all accounts now, then reindex. Waits up to 20 seconds; if the pass is still running it says so. |\n| `status` | Sync health per account: first-sync completion, last sync, messages indexed, errors and backoff. |\n| `attachment` | One attachment or MIME part of a message, by part number from `show`. Images inline, text (JSON and XML included) as a marked block, other binaries as a signed download link. |\n\n`search`, `ids`, `files` and `count` take a notmuch query (`from:`, `to:`,\n`subject:`, `tag:`, `folder:`, `date:2026-01-01..2026-06-30`, combined with\nand/or/not), an optional `account` to scope to one account, and can include\njunk and trash with `include_excluded`. Junk and trash are discovered per\naccount over RFC 6154 SPECIAL-USE, so the exclusion works whatever those\nfolders are named and in whatever language. While an account's mirror is\nstill filling, these four tools prepend a note naming the account and how\nmany messages are indexed so far.\n\nAttachments are listed in `show` and `thread` and served by the `attachment`\ntool one part at a time: images inline up to 5MB, textual parts as marked\ntext, and other binaries as a short-lived signed link to\n`GET /attachment/{id}/{part}` (a bearer token works there too). Without an\nHTTP listener, an oversized binary is saved under `/index/attachments/` and\nthe tool returns the path to `docker cp`. That directory is capped at 1GB;\nthe oldest files go first.\n\n## Why not one of the others\n\nThere are around forty email MCP servers on GitHub. Nearly all of them talk\nlive IMAP and ship a send path, which is the opposite of both choices this\nserver rests on. Two are close enough to name.\n\n[igor47/notmuchproxy](https://github.com/igor47/notmuchproxy) is the nearest\nthing that already existed, and a large part of why this one has the shape\nit does. It reads a notmuch archive you keep up to date yourself, has no\nwrite path, and takes a bearer token or full OIDC. Its query validation,\nwhich rejects an unknown prefix with an explanation, is reimplemented here as\n`validateQuery`. Two things differ: it assumes you already run mbsync and\nnotmuch, where this server generates the mbsync configuration, syncs every\naccount in parallel and discovers junk and trash over IMAP; and it has no\n`account` parameter. If you already run a notmuch setup you are happy with,\nnotmuchproxy is the smaller thing to deploy and you should use it instead of\nthis.\n\n[hgn/mcp-server-notmuch](https://github.com/hgn/mcp-server-notmuch) is stdio\nonly, so one client on one machine, and its handling of untrusted content is\nthe best in the survey. The single `render()` chokepoint here, which marks\nevery byte of mail text in one place so that no individual tool can forget\nto, comes from its `render.py`.\n\nThe full survey, including which claims were read in source and which were\ntaken from a README, is in\n[`docs/research/email-mcp-landscape.md`](docs/research/email-mcp-landscape.md).\n\n## Security\n\nRead this before you point it at a mailbox you care about.\n\n**It cannot write.** The generated mbsync configuration for every account\ncarries `Sync Pull`, `Create Near`, `Remove None` and `Expunge None` (or,\nfor an account with `expunge_local` set, `Expunge Near`, which deletes local\nfiles only), and the program writes that file itself, so nothing in it can\nbe edited into a push.\nThe only IMAP operation in the Go code is `LIST`, issued per account at\nstartup and hourly to find the junk and trash folders; an account with\n`exclude_folders` set by hand skips even that. The process has no path that\nsends, deletes, moves or tags a message, and nothing in it holds write\naccess to any account.\n\n**Deleted mail stays in the mirror.** Mail you delete on the server is kept\non disk and hidden from search through notmuch's `deleted` tag. Set\n`expunge_local: true` on an account to physically remove those local copies\ninstead. The account is still never written to, but the mirror then stops\nbeing a backup: whatever disappears remotely disappears locally on the next\npass.\n\n**How proven this is.** One author, one operator, three real accounts: one\niCloud and two Gmail. No third-party security review, and nobody else has\ndeployed it. notmuchproxy has zero stars and a more convincing production\nstory than this does.\n\n**Passwords are plain text inside the container.** Account passwords come\nfrom the environment and are written at startup into a generated mbsync\nconfiguration at file mode `0600`. That file is not encrypted. Anything that\ncan read the container's environment, or that file, can read them. Disk\nencryption, who can exec into the container and access to the host are the\noperator's responsibility; the server makes no claim of encrypting\ncredentials at rest.\n\n**One passphrase, one consent.** `OAUTH_PASSPHRASE` is checked in constant\ntime and gates the whole server with a single shared secret. It is not a\nper-user credential system, and everyone holding the passphrase sees the\nwhole mailbox. It does not encrypt anything at rest. Treat it and the mail\npasswords with the same care.\n\n**A mailbox is a secret store.** Password resets, sign-in codes and magic\nlinks all arrive by mail, so read access alone is enough to take over\naccounts if it lands in the wrong hands or the wrong AI session. The\nread-only design and the untrusted-content markers remove the write path and\nthe instruction channel; they do not make mail contents harmless. Connect\nclients you trust. Agent workflows that need their own inboxes need their\nown addresses, which is a different tool.\n\n**A known gap.** `search`'s thread summaries include a display name for every\nmessage in a matching thread, which the sender controls. A message in a\nfolder excluded by default (junk, trash) can still put its own\nattacker-chosen name in front of you this way, even though its body never\ndoes. `thread` and `show` are not subject to this. It is not fixed in this\nrelease.\n\nFor a server that faces the internet,\n[docs/server.md](docs/server.md#hardening) lists the hardening steps in the\norder of how much each buys you.\n\n## Reference\n\n- [The accounts file](docs/reference.md#the-accounts-file): every key, with\n  defaults.\n- [Environment variables](docs/reference.md#environment-variables): sync\n  interval and timeout, listener address, `INIT_MIRROR`.\n- [Provider notes](docs/reference.md#provider-notes): iCloud, Gmail, Dovecot.\n- [Troubleshooting](docs/reference.md#troubleshooting): the startup\n  refusals, and why junk is sometimes not excluded.\n- [Without Docker](docs/reference.md#without-docker): release binaries and\n  what they shell out to.\n- [Running it on a server](docs/server.md): install script, hardening, your\n  own domain with Caddy.\n- [Upgrading from 0.3.x](docs/upgrading.md).\n- [Design spec](docs/specs/2026-08-19-your-mail-mcp-design.md) and\n  [the survey of email MCP servers](docs/research/email-mcp-landscape.md).\n",
  "bytes": 19149,
  "sha": "13667e2aae2a8dbe59fa262a7e1c319ec8aefc126fad7b3b2240edc50969f050",
  "repo_slug": "wildsurfer/your-mail-mcp",
  "fonte": "repo",
  "truncated": false,
  "api": "https://agentalog.com/api/listings/mcp_io_github_wildsurfer_your_mail_mcp_30b26322/readme"
}