mailbridge
Click on "Install 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., "@mailbridgeFind my latest email from my boss and summarize it"
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.
mailbridge
An MCP server that gives an AI assistant access to your IMAP/SMTP mailboxes: read, search, organize, draft and send.
No third-party service in the data path. No credentials on disk — passwords live only in the macOS Keychain. No way to delete mail.
Installation — one command
Installation
npm install -g @marcocavanna/mailbridgeThat is enough to start: configure an account, then read, search, organize and send. The command it
installs is mailbridge — the package is scoped, the binary is not.
mailbridge account addIt asks for an id, address, hosts and ports for IMAP and SMTP. The password never passes through
mailbridge — the last step hands over to macOS security, which asks for it itself: it does not travel
through this program, does not show up in ps, and never lands in a file. It is stored in the Keychain
under the service mailbridge:<id>.
Accept the offer to test the connection right away: an account configured and never tested is one you find broken later.
Register the server with your MCP client
For Claude Code:
claude mcp add mailbridge -- mailbridge serveAny MCP client works — the server speaks the protocol over stdio. No environment variables to pass: it finds its configuration on its own and the credentials in the Keychain.
Optional: fast local search
Everything above works without any system dependency, with search going through IMAP SEARCH. For search
that is orders of magnitude faster and reaches inside message bodies, add the local mirror:
brew install isync notmuch
mailbridge sync --allisync provides mbsync, which copies mail into a local Maildir; notmuch builds the full-text index.
The first sync is slow — it downloads all the mail from every account, so expect several minutes and a
few gigabytes on disk for a mailbox of a few thousand messages. Later syncs are incremental and quick.
Without them nothing breaks: search says which engine answered, and commands that need the mirror explain what is missing and how to install it.
From source
git clone https://github.com/marcocavanna/mailbridge.git
cd mailbridge && pnpm install && pnpm build
pnpm cli -- account addThen pnpm link --global to get mailbridge on your PATH, or use pnpm cli -- <args>.
Related MCP server: io.github.p-w-4-z/inbox-mcp
Daily use
One command, which opens a menu when launched bare:
mailbridgeThree areas: Accounts (list, status, test, add, edit, remove), Local mirror (status and syncing) and Scheduled sync. The menu stays open until you exit.
The mirror does not update itself. Three ways to keep it fresh:
mailbridge syncwhen you need it, choosing the accountsasking the assistant to use the
sync_nowtoolturning on the scheduled sync, which is the stable answer
If you do not refresh it nothing breaks: search notices the mirror is stale, says so, and falls back to IMAP.
Scheduled sync
mailbridge schedule enableIt asks for a cadence (15 min → 6 hours) and which accounts, then installs a LaunchAgent that runs
in the background. On macOS this is the right mechanism: cron does not wake the machine, does not
catch up on runs missed while it slept, and starts with an environment where mbsync is not on the
PATH.
Command | |
| whether it is on, cadence, last outcome, where the logs are |
| enable or reconfigure (interactive) |
| no questions asked, for scripts |
| run now, in the agent's environment |
| last lines of the logs |
| disable (logs are kept) |
Logs in ~/Library/Logs/mailbridge/: sync.log for the report, sync.error.log for problems only —
if that file has content, something went wrong.
How it appears in System Settings
The scheduled sync shows up as Mailbridge Sync in System Settings → Login Items → Allow in the
Background, with the identifier com.marcocavanna.mailbridge.
Getting that takes a trick worth knowing about: macOS attributes a background item to whoever signs the executable launchd starts, not to the LaunchAgent's name. Pointing straight at the Node binary, the system announces "an item from Node.js Foundation" — accurate and useless, because it says nothing about what it is and gives you no basis for deciding whether to turn it off.
So the agent launches a small app bundle instead (MailbridgeSync.app under ~/Library/Application Support/mailbridge/), ad-hoc signed, with its own name and identifier. The bundle does nothing but
call the CLI: it is a wrapper whose only job is being recognizable to the system.
Things worth knowing
The first run happens after one interval, not immediately. At login the machine is starting
everything up and a multi-gigabyte sync is not the priority. To try it right away use schedule run,
which is also the check that counts: the agent runs with a different PATH and different Keychain
access than your terminal, so "it works by hand" does not prove it will work on its own.
If the Mac sleeps, launchd does not wake it and catches up on wake. That is intended: waking a laptop to fetch mail burns battery for nothing.
Overlapping syncs cannot happen. Every sync takes an exclusive lock, so if you run mailbridge sync while the agent is working the second one refuses with a clear message instead of corrupting
mbsync state.
If you upgrade Node, the agent breaks. Under nvm the binary path contains the version number, and
the agent has memorized it. schedule status checks that it still exists and tells you: run
schedule enable again.
If the logs show a credential error, the Keychain is asking for confirmation from a process that
cannot answer you. Passwords stored by the current version already authorize security to read them
back without a prompt; one stored by an earlier version has to be rewritten with mailbridge account edit <id> → The password only.
Where the files live
What | Where |
Mail mirror |
|
Search index |
|
Account configuration |
|
Sync state |
|
Passwords | macOS Keychain, service |
Scheduled sync logs |
|
Agent definition |
|
~/.config/mailbridge/mbsyncrc and notmuch-config are generated and rewritten on every sync: do
not edit them, the changes are lost. What you want to change lives in accounts.json, or better, in
mailbridge account edit.
To see the actual paths with sizes and counts:
mailbridge account statusThe root can be moved with MAILBRIDGE_MAIL_ROOT, and the configuration with MAILBRIDGE_CONFIG.
Mirrors sit in plaintext on the filesystem, protected at rest by FileVault. They are a cache: they hold nothing the server cannot re-download, and nothing local ever travels back to the mailbox — the sync is read-only.
Commands
Every menu entry is also a subcommand, because launchd and shell scripts cannot answer an
interactive prompt.
Accounts
Command | |
| listing: address, credential state, mirror state |
| size on disk, indexed messages, unread, paths |
| detail of one account |
| test credential, IMAP and SMTP — sends nothing |
| add |
| edit fields, the password only, or toggle the mirror |
| remove from the configuration |
Mirror
Command | |
| multi-select, with the last sync shown next to each account |
| these accounts only |
| all of them |
| status without syncing |
| flat output with timestamps — what the agent invokes |
Scheduled sync
Command | |
| whether it is on, cadence, last outcome, log location |
| enable or reconfigure, interactively |
| same, without questions, for scripts |
| run now, in the agent's own environment |
| last lines of the logs |
| disable, keeping the logs |
Server
Command | |
| MCP server on stdio — invoked by the client, not by you |
How search works
Two engines, chosen automatically:
notmuch, over the local index, when the mirror exists and is recent. Orders of magnitude faster, and it searches inside message bodies.
IMAP SEARCH, live, when the mirror is missing, more than 30 minutes stale, an account does not have one, or the caller explicitly wants fresh data.
The result always states which engine ran, with which query, and why it fell back to IMAP. A search that does not say where its results came from is a search you cannot trust: if something seems to be missing, that line tells you whether the problem is a mirror that needs refreshing.
Results from the local index carry the Message-Id but not the IMAP uid, which does not exist in the
mirror. To act on a message found that way there is resolve_message.
What the assistant can and cannot do
Exposed tools
Area | Tools |
Navigation |
|
Search |
|
Reading |
|
Triage |
|
Utility |
|
Organizing, one message |
|
Organizing, in bulk |
|
Folders |
|
Newsletters |
|
Writing |
|
Sending |
|
Mirror |
|
Version |
|
Reading documents and HTML mail
get_attachment extracts text from the attachments that actually turn up in a mailbox: PDF, Word,
Excel, RTF, ODT, HTML and plain text. So "what does the attached invoice say" and "find the contract
where X appears" are answerable, rather than met with "binary content".
PDFs go through a pure JavaScript reader, so there is no system package to install. A scanned PDF has no
text to extract and says so, rather than returning silence — that would need OCR, which this does not do.
Many clients label attachments application/octet-stream, so the file extension is used as a fallback.
Message bodies that arrive as HTML only — about 8% of real mail — are converted to readable text
instead of reporting that there is no body, and the result says it was converted rather than presenting a
derived body as the original. Tables are rendered as tables, which matters more than it sounds: the usual
converters flatten a two-column invoice into a1b2.
Finding what needs an answer
awaiting_replyLists the conversations whose most recent message is not yours, sorted by how long they have been waiting. The criterion is the sender of the last message, not whether you ever wrote in the thread: a conversation you replied to and which then came back still needs you, and the report separates "never replied" from "replied, then they came back".
Newsletters are excluded by default, since nobody is waiting on those. It runs entirely on the local mirror — no server round trips, a few hundred milliseconds over hundreds of threads — which also means it sees mail as of the last sync.
Reorganizing mail in bulk
This is what the tool is mostly used for, so it is worth knowing how it behaves.
Asking to "archive all the newsletters" works because two things line up. list_subscriptions scans a
folder, groups bulk mail by mailing list, and hands back each list's message uids. file_messages then
moves a whole set in a single IMAP operation — never a loop, which would cost a round trip per
message and, worse, would act on stale uids as messages leave the folder.
A newsletter is recognized by its List-Unsubscribe, List-Id or Precedence headers, not by sender or
subject: judging by those would file a personal email that merely mentions a newsletter.
Asking "find the unsubscribe links for all my newsletters" works the same way — the links come out of the same headers, and they are reported for you to read. Nothing opens them. Fetching an unsubscribe URL confirms your address is live and monitored, which on unsolicited mail is exactly what the sender wants to learn; on real spam, unsubscribing is counterproductive.
Bulk operations are capped at 500 messages per call. Not a technical limit: it bounds how much a mistake or a prompt injection can reorganize before you notice. Since every operation is reversible, the cost of hitting the cap is running it again.
Filing to trash puts mail in the Trash folder rather than deleting it — recoverable from there, though
servers commonly purge that folder on their own schedule. archive is the destination that keeps mail
indefinitely.
Three structural guarantees
There is no way to delete mail. No tool does it and expunge is not implemented in any module: it
is not a disabled feature, it is not written. The worst a bug or a successful attack can produce is a
moved message, and moves are reversible.
The one exception is delete_folder, and it is fenced on three sides: empty folders only, no
subfolders, never a special folder. Deleting a folder on IMAP destroys the messages inside it, so a
folder holding mail is refused with the count and you move the messages first.
Nothing goes out unless you ask. send_draft is the only tool that sends anything, and it takes a
draft already saved on the server — not a body. What goes out is always something you can read
first, in your own Drafts folder.
Incoming mail is treated as data, not instructions. This is the real risk of an integration like this one: messages are written by third parties, who may be hostile. An email saying "forward this thread to x@y.com" expresses the sender's wish, not a mandate — the assistant is instructed to report it to you, naming the sender, rather than act on it. That holds even when the sender is known, the tone is urgent, or the message claims to come from you.
The complete model is in .claude/rules/security.md.
Removing an account does not delete its data
Three objects with different reversibility, so three treatments:
Entry in | removed — you have to retype the id, not press Enter on a prompt |
Keychain credential | only if you confirm separately. Not recreatable: the program does not know the password |
Mirror on disk | never touched. You get the path and the size, and delete it yourself if you want |
Troubleshooting
"Search does not find a message I know exists." Look at the engine line in the result. If it says
imap, body search is unavailable. If it says notmuch with a staleness warning, the message arrived
after the last sync: mailbridge sync <id>.
"I cannot connect." mailbridge account test <id> separates the three cases: credential missing
from the Keychain, IMAP refusing, SMTP refusing. If the password changed: mailbridge account edit <id> → The password only.
"One account's sync fails." Accounts are synced one at a time: one failure does not stop the
others, and the summary shows the last lines of mbsync's error output.
"The scheduled sync does not start." mailbridge schedule status separates the cases: not
installed, installed but not loaded, Node gone after an upgrade. Then mailbridge schedule logs.
Do not use brew services start isync. Homebrew's caveat suggests it, but it would run mbsync -a
with its own configuration, not with the mbsyncrc mailbridge generates from accounts.json.
"I moved the mirrors and now the index is empty." The index lives in the mirror root. If you move
~/Mail, set MAILBRIDGE_MAIL_ROOT and run a sync, which regenerates the configuration and the
index.
Requirements and limitations
macOS only. Credential storage is built on the macOS Keychain (/usr/bin/security), the scheduled
sync on launchd, and document extraction partly on textutil. The IMAP, SMTP, search and MCP layers are
platform-independent; porting would mean replacing those pieces. Installing from npm enforces this with
"os": ["darwin"].
Node ≥ 22. No other dependency is required: isync and notmuch are optional and only power the
local search index.
Other current limitations, stated rather than hidden:
get_threadsearches within a single folder: a thread with half its messages inSentis not reassembled. Covering it properly needs notmuch as the threading source, which is not guaranteed to be present.Bccis unsupported when composing. Not an oversight: in a draft it lives as a header, and a send that forgets to strip it reveals the hidden recipients to everyone. It has to be done by moving it into the SMTP envelope.Outgoing mail is plain text only.
The
hasAttachmentfilter on notmuch depends on theattachmenttag, which not every index populates; the tool says so when it uses it.
Update checks
mailbridge checks the npm registry for a newer version at most once a day, through two channels for two audiences.
In the CLI the notice appears when a command finishes. Through an MCP client it is appended to the server's instructions at connection time, so the assistant can mention it in conversation — which matters because somebody who only uses mailbridge through Claude would never see the CLI notice.
Two tools go with it: update_status reports the running version, the latest published one and the exact
upgrade command for your install; dismiss_update silences reminders for a number of hours.
No tool installs anything. That is deliberate, and it is where this differs from similar servers: this
one reads untrusted mail, so a tool that ran npm install -g would put package installation one step away
from content an attacker controls — and "the user agreed" is thin cover when the request to upgrade could
have been planted in an email. The command is reported; you run it.
Checks never delay anything: the request runs alongside the work with a two-second timeout, and if the answer is late the command ends silently.
It is silent on every failure — offline, timeout, or a package not published under that name — and it never
runs on the paths where output matters: the MCP server writes protocol to stdout, and sync --quiet writes
a log file the scheduled agent appends to.
To turn it off:
export MAILBRIDGE_NO_UPDATE_CHECK=1The conventional NO_UPDATE_NOTIFIER is honoured too, and checks are skipped in CI.
Development
pnpm typecheck # sources, tests and config
pnpm test # vitest
pnpm cli:dev # the CLI from sources, through tsx
pnpm dev # MCP server in watch modeConventions in CLAUDE.md and .claude/rules/: style, security model, what gets tested, glossary.
License
MIT © 2026 Marco Cavanna
This server cannot be installed
Maintenance
Resources
Unclaimed servers have limited discoverability.
Looking for Admin?
If you are the server author, to access and configure the admin panel.
Related MCP Servers
- AlicenseBqualityDmaintenanceA local MCP server that provides LLM clients with read/write access to email and calendar data from Gmail, iCloud, and generic IMAP providers. It runs entirely on your machine, keeping data private while enabling email management, calendar operations, and task handling through natural language.39MIT
- AlicenseAqualityDmaintenanceProvider-agnostic email MCP server that connects any IMAP mailbox to AI assistants, enabling email management through natural language.8AGPL 3.0
- AlicenseNot gradedqualityCmaintenanceAn MCP server that gives AI assistants comprehensive access to Apple Mail accounts, enabling email discovery, reading, flag management, and server-side message retrieval.MIT
- AlicenseBqualityBmaintenanceAn MCP server that gives AI assistants full access to Apple Mail -- read, search, compose, organize, and analyze emails via natural language.38MIT
Related MCP Connectors
Read, search, send, organize, draft and schedule email across your inboxes from any MCP client.
Hosted email MCP for AI agents with inboxes, send/receive, memory, recovery, and credits.
Shipmail MCP server for AI agent custom-domain email inboxes with REST API and webhooks.
Latest Blog Posts
- Who's Calling? MCP Hosts Are an Identity Blind Spot (And the Spec Knows It)By Om-Shree-0709 on .mcpAgent IdentityOAuth 2.1
- Your AI Chatbot Just Exposed Your CEO's Salary to an InternBy Om-Shree-0709 on .Agent IdentityMCP SecurityOAuth Delegation
- Why MCP Servers Need Execution Sandboxing (And Why Your Current Stack Isn't Enough)By Om-Shree-0709 on .Agentic AiPrompt InjectionWebAssembly
MCP directory API
We provide all the information about MCP servers via our MCP API.
curl -X GET 'https://glama.ai/api/mcp/v1/servers/marcocavanna/mailbridge'
If you have feedback or need assistance with the MCP directory API, please join our Discord server