mail-mcp
About
Most email MCP servers only read from IMAP. mail-mcp does everything: 30 tools for reading, searching, sending, replying, forwarding, and bulk operations across IMAP, SMTP, Microsoft Graph API, and Exchange Web Services. Multi-account, native OAuth2, built in Rust. Works with Gmail, Microsoft 365, Hotmail/Outlook.com…
Details
- License
- MIT
Explore
| Provider | IMAP | SMTP | Graph API | EWS | OAuth2 | Multi-account |
|----------|:----:|:----:|:---------:|:---:|:------:|:-------------:|
| Microsoft 365 (enterprise) | Yes | Admin-dependent | Yes | Yes | Yes | Yes |
| Hotmail / Outlook.com | Yes | Blocked by MS | Yes | Yes | Yes | Yes |
| Gmail | Yes | Yes | — | — | Yes | Yes |
| Zoho | Yes | Yes | — | — | — | Yes |
| Fastmail | Yes | Yes | — | — | — | Yes |
| Any IMAP/SMTP server | Yes | Yes | — | — | — | Yes |
> EWS is the simplest way to add Microsoft accounts — single OAuth2 token for both reading and sending. Works even on tenants that block Graph API and IMAP.
Copy and paste this prompt into Claude Code and it will install, compile, and configure everything for you:
Install and configure the mail-mcp MCP server from https://github.com/tecnologicachile/mail-mcp
1. Clone the repo, build with cargo build --release
2. Add the MCP server to .claude.json with the binary path
3. For Microsoft accounts: use EWS (simplest) — run device code flow with
client_id d3590ed6-52b3-4102-aeff-aad2292ab01c and scope
https://outlook.office365.com/EWS.AccessAsUser.All offline_access
Then configure MAIL_EWS_<ID>_USER and MAIL_EWS_<ID>_REFRESH_TOKEN
4. For Gmail: configure MAIL_IMAP + MAIL_SMTP with App Password from
https://myaccount.google.com/apppasswords
5. For Zoho: configure MAIL_IMAP + MAIL_SMTP with standard password
6. Enable write/send: MAIL_IMAP_WRITE_ENABLED=true, MAIL_SMTP_WRITE_ENABLED=true
My email accounts to configure:
- <[email protected]>
Replace the last line with your email(s). Claude Code will guide you through each step including the OAuth2 device code flow for Microsoft accounts.
git clone https://github.com/tecnologicachile/mail-mcp.git
cd mail-mcp
cargo build --release
Add to your MCP client config (Claude Code, Cursor, etc.):
{
"mcpServers": {
"mail": {
"command": "./target/release/mail-mcp",
"env": {
"MAIL_IMAP_DEFAULT_HOST": "imap.gmail.com",
"MAIL_IMAP_DEFAULT_USER": "[email protected]",
"MAIL_IMAP_DEFAULT_PASS": "your-app-password",
"MAIL_SMTP_DEFAULT_HOST": "smtp.gmail.com",
"MAIL_SMTP_DEFAULT_PORT": "587",
"MAIL_SMTP_DEFAULT_USER": "[email protected]",
"MAIL_SMTP_DEFAULT_PASS": "your-app-password",
"MAIL_SMTP_DEFAULT_SECURE": "starttls",
"MAIL_IMAP_WRITE_ENABLED": "true",
"MAIL_SMTP_WRITE_ENABLED": "true"
}
}
}
}
That's it. Your AI agent can now read, search, send, reply, and manage emails.
| Tool | What it does |
|------|-------------|
| get_setup_guide | Provider-specific setup instructions (Microsoft OAuth2, Gmail App Passwords, Zoho, etc.) |
<details>
<summary>Full environment variable reference</summary>
list_all_accounts
**List all accounts with capabilities** (IMAP, SMTP, Graph, EWS)
imap_list_accounts
List IMAP accounts
imap_verify_account
Test connectivity and auth
imap_list_mailboxes
List folders
imap_mailbox_status
Message counts
imap_search_messages
Search with cursor pagination
imap_get_message
Parsed message (text, HTML, attachments)
imap_get_message_raw
RFC822 source
imap_update_message_flags
Add/remove flags
imap_copy_message
Copy (cross-account supported)
imap_move_message
Move to folder
imap_delete_message
Delete with confirmation
imap_create_mailbox
Create folder
imap_delete_mailbox
Delete folder
imap_rename_mailbox
Rename folder
imap_append_message
Append raw message
imap_bulk_move
Move up to 500 at once
imap_bulk_delete
Delete up to 500 at once
imap_bulk_update_flags
Flag up to 500 at once
smtp_send_message
Send email (text/HTML, CC/BCC)
smtp_reply_message
Reply with threading headers
smtp_forward_message
Forward with original inline
smtp_verify_account
Test SMTP connectivity
graph_send_message
Send via Microsoft Graph API (with reply threading)
ews_search_messages
Search emails via EWS (inbox, sent, drafts, etc.)
ews_get_message
Get full email content via EWS
ews_send_message
Send email via EWS
imap_search_and_move
Search + move matches
imap_search_and_delete
Search + delete matches
get_setup_guide
Provider-specific setup instructions (Microsoft OAuth2, Gmail App Passwords, Zoho, etc.)
| Tool | What it does |
|------|-------------|
| list_all_accounts | List all accounts with capabilities (IMAP, SMTP, Graph, EWS) |
| imap_list_accounts | List IMAP accounts |
| imap_verify_account | Test connectivity and auth |
| imap_list_mailboxes | List folders |
| imap_mailbox_status | Message counts |
| imap_search_messages | Search with cursor pagination |
| imap_get_message | Parsed message (text, HTML, attachments) |
| imap_get_message_raw | RFC822 source |
| Tool | What it does |
|------|-------------|
| imap_update_message_flags | Add/remove flags |
| imap_copy_message | Copy (cross-account supported) |
| imap_move_message | Move to folder |
| imap_delete_message | Delete with confirmation |
| imap_create_mailbox | Create folder |
| imap_delete_mailbox | Delete folder |
| imap_rename_mailbox | Rename folder |
| imap_append_message | Append raw message |
| imap_bulk_move | Move up to 500 at once |
| imap_bulk_delete | Delete up to 500 at once |
| imap_bulk_update_flags | Flag up to 500 at once |
| Tool | What it does |
|------|-------------|
| smtp_send_message | Send email (text/HTML, CC/BCC) |
| smtp_reply_message | Reply with threading headers |
| smtp_forward_message | Forward with original inline |
| smtp_verify_account | Test SMTP connectivity |
| graph_send_message | Send via Microsoft Graph API (with reply threading) |
| Tool | What it does |
|------|-------------|
| ews_search_messages | Search emails via EWS (inbox, sent, drafts, etc.) |
| ews_get_message | Get full email content via EWS |
| ews_send_message | Send email via EWS |
| Tool | What it does |
|------|-------------|
| imap_search_and_move | Search + move matches |
| imap_search_and_delete | Search + delete matches |
| Tool | What it does |
|------|-------------|
| get_setup_guide | Provider-specific setup instructions (Microsoft OAuth2, Gmail App Passwords, Zoho, etc.) |
<p align="center">
<h1 align="center">mail-mcp</h1>
<p align="center">
<strong>Production-ready email MCP server for AI agents</strong><br>
IMAP + SMTP + EWS + Microsoft Graph API — built in Rust
</p>
<p align="center">
<a href="https://github.com/tecnologicachile/mail-mcp/releases"></a>
<a href="LICENSE"></a>
<a href="https://github.com/tecnologicachile/mail-mcp/stargazers"></a>
</p>
</p>
---
Most email MCP servers only do IMAP reads. This one does everything: read, search, send, reply, forward, bulk operations, Microsoft Graph API, and Exchange Web Services — with real OAuth2, multi-account, and multi-provider support. Written in Rust for speed and safety.
What's New in v0.4.8
- SAVE_SENT is now per-account with a provider-aware default.
Previously, saving a copy of outgoing mail to the Sent folder via IMAP
APPEND was controlled by a single global flag, MAIL_SMTP_SAVE_SENT. The
problem: providers that already save sent mail server-side (Gmail,
Zoho) ended up with two identical copies in Sent, while a generic SMTP
server or Office 365 (which do not auto-save on SMTP submission) lost
the copy entirely when the flag was false.
- Provider-aware default (when nothing is configured):
- Gmail (smtp.gmail.com): saves server-side and deduplicates by
Message-ID → the MCP does not append (false).
- Zoho (smtp.zoho.com): saves server-side but does not
deduplicate → the MCP does not append (false), avoiding the
duplicate.
- Office 365 / generic SMTP: do not auto-save on SMTP submission →
the MCP does append (true), or the sent copy would be lost.
- Per-account override: MAIL_SMTP_<ID>_SAVE_SENT=true|false takes
priority over everything. The global MAIL_SMTP_SAVE_SENT still works as a
coarse override (wins over the provider default, loses to the per-account
override).
- Precedence: per-account → global → provider-aware default.
| Provider | Auto-saves server-side | MCP default |
|---|---|---|
| Gmail | Yes (with dedupe) | false |
| Zoho | Yes (no dedupe) | false |
| Office 365 (SMTP) | No | true |
| Generic SMTP / relays | No | true |
What's New in v0.4.7
- Critical fix — graph_send_message silently dropped attachments on
threaded replies. When called with in_reply_to + attachments, the
createReply → PATCH → send flow included the attachments in the PATCH
against /me/messages/{id}. Microsoft Graph treats Message.attachments
as a navigation property and silently discards the field on PATCH
(2xx response, no error), so the message went out as single-part
text/html with no file. The MCP returned status: ok and the caller
assumed success. Invisible data loss.
- The fix: in send_via_reply(), attachments are now uploaded one by one
to POST /me/messages/{draft_id}/attachments between the PATCH and the
send. Files < 3 MB go inline (JSON with base64 contentBytes); files
≥ 3 MB use createUploadSession with 4 MB chunked PUTs. The attachments
field was removed from the PatchDraftRequest struct so the regression
cannot be reintroduced by a type-correct edit.
- No change to flows that already worked. send_via_sendmail (new
messages without in_reply_to) uses POST /me/sendMail with attachments
inline in the JSON — Graph DOES accept the field on that endpoint and never
dropped it. That path is untouched.
- Regression test added: patch_draft_request_never_serializes_attachments
fails if anyone re-adds the field to the struct.
- Reference: BUG_GRAPH_ATTACHMENTS.md at the repo root documents the
full reproduction, root cause, and the empirical evidence behind the fix.
What's New in v0.4.6
- Server-side enforcement of HARD RULE #1. Three releases of prompt-only
hardening (v0.4.3 → v0.4.4 → v0.4.5) still left LLMs occasionally leaking
literal </body_text><parameter name="body_html"> markup into the
recipient's inbox. v0.4.6 adds a real validator that rejects the tool
call before any SMTP / Graph / EWS attempt if body_text or body_html
contains tool-call wrapper syntax. The check is wired into all 5 send
paths (smtp_send_message, smtp_reply_message, smtp_forward_message,
graph_send_message, ews_send_message).
- The forbidden markers are case-insensitive and tightly scoped — only
the pseudo-tags that have no legitimate use in human correspondence:
<body_text>, </body_text>, <body_html>, </body_html>,
<function_calls>, </function_calls>, <invoke name=, </invoke>,
and <parameter name="body_*">. Generic technical content that happens
to mention <parameter> for an XML schema or <invoke> in a code
example still passes.
- HARD RULE #1 wording updated to announce the server-side rejection,
so the LLM knows it's a hard contract — not a suggestion it can ignore.
- No breaking changes for clean callers: well-behaved messages send
exactly as before.
What's New in v0.4.5
- serverInfo now reports name="mail-mcp" + the crate version (the
framework previously returned its own rmcp 0.16.0, which never changes
between releases). Useful for verifying the active version with /mcp, and
so any client-side cache keyed by (server, version) invalidates on each bump.
- MCP instructions reorganized: the 3 critical anti-concatenation rules
(which in v0.4.3 and v0.4.4 sat at the end of the block and could be lost
to truncation / diluted attention) now appear as HARD RULE #1, #2, #3 at
the TOP, right after the title. Consolidated into 3 short paragraphs
(previously 3 long sections, ~1500 characters combined).
- No functional changes to the server. Same SMTP/IMAP/EWS/Graph, same
tool set, same behavior. Only the text exposed to the client changed.
Important for these rules to take effect
Clients that resume a session with claude --continue (or /resume) do
NOT refresh the MCP system_prompt — they keep the one from that
session's first handshake. If your session predates v0.4.5, the rules won't
reach your context even if the on-disk binary is updated. To receive them,
start a NEW session in the project (not --continue).
What's New in v0.4.4
- Preview hygiene rule in MCP instructions: when the LLM shows the
user the email preview before sending, it should render ONE clean
version of the body (markdown-style bullets, bold, links as text + URL)
and state that the message will go multipart — but it must NOT dump
the raw HTML source (<p>, <strong>, <a href>...) into the
preview. Two reasons:
1. The human reviewer wants to read the message, not audit markup —
showing the HTML is noise.
2. Exhibiting both the plain-text string AND the HTML string side by
side in the preview is exactly the context that has historically
led LLMs to concatenate them in the eventual tool call (the bug
v0.4.3 documented). Hiding the HTML source from the preview
removes the temptation.
Complements the PREVIEW DOES NOT EQUAL TOOL CALL rule introduced
in v0.4.3.
What's New in v0.4.3
…
Sign in to leave a review
Use Google, GitHub, or an email account so ratings stay tied to real people.
No reviews posted yet.



