mail-mcp
Production-ready email MCP server for AI agents
IMAP + SMTP + EWS + Microsoft Graph API β built in Rust
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.5.0
- New: draft tools (
draft_create, draft_update, draft_send,
draft_delete, draft_list) closing
#3 and
#17. An agent can
save a message in the account's own Drafts folder, revise it, and send it
later, so a person can review it in their mail client first. The same five
tools work on every backend: IMAP + SMTP, Exchange Web Services and
Microsoft Graph, picked per account (EWS config, else Graph config, else
IMAP). list_all_accounts reports the backend as drafts_with.
- Separate gate:
MAIL_DRAFTS_ENABLED (default false). It is
independent of MAIL_IMAP_WRITE_ENABLED: every operation on an existing
draft_id first checks that the target really is a draft (IMAP Drafts
folder, EWS IsDraft, Graph isDraft), so enabling drafts never grants
move or delete on other mail. draft_send also requires
MAIL_SMTP_WRITE_ENABLED.
- Graph accounts need the
Mail.ReadWrite scope for drafts; see
account setup. The EWS attachment flow
(CreateItem with SaveOnly, then CreateAttachment) follows the approach
proposed by @verdelyi in
#14.
- Fix: SMTP messages now carry a Message-ID on the sender's domain
(#7). Before, lettre
sent no Message-ID of its own, so the id returned by the send tools was
empty and the Sent copy had none.
- Verified live on IMAP (Zoho) and Microsoft Graph; the EWS draft path is
covered by unit tests.
What's New in v0.4.17
- New:
imap_search_all_mailboxes by
@tdabasinskas in
#35. Runs one search
in every selectable mailbox of an account and returns the newest matches,
each labelled with its mailbox. Built for providers without an "All Mail"
folder (iCloud), where one conversation is spread over INBOX, Sent Messages
and project folders. The work is spread over MAIL_IMAP_SEARCH_CONCURRENCY
connections (default 4, clamped to 1..=8); a mailbox that fails is
reported and does not abort the search. Scope it with include_mailboxes /
exclude_mailboxes.
- Thread search by Message-ID by
@tdabasinskas in
#34.
imap_search_messages takes a new thread_message_id criterion that
matches a message and every message whose In-Reply-To or References
carries it, so an agent can pull a whole conversation in one search.
What's New in v0.4.16
imap_list_mailboxes cap is now configurable and visible by
@tdabasinskas in
#33. The tool used to
silently drop everything past 200 mailboxes β on accounts with more folders
(e.g. 217 on iCloud), folders like Sent Messages simply never appeared.
The cap is now set with MAIL_IMAP_MAX_MAILBOXES (default 200, clamped to
1..=10000), and the response carries two additive fields, total and
truncated, so clients can always tell a capped list from a complete one.
Nothing changes unless the variable is set.
What's New in v0.4.15
- New: opt-in streamable HTTP transport by
@tdabasinskas in
#32. Set
MAIL_MCP_TRANSPORT=http to serve MCP streamable HTTP (stateless) instead
of stdio β useful for running the server on another machine behind an MCP
gateway. Binds to 127.0.0.1:8000 at /mcp by default, configurable via
MAIL_MCP_HTTP_HOST / MAIL_MCP_HTTP_PORT / MAIL_MCP_HTTP_PATH, with
graceful shutdown on SIGINT/SIGTERM. The endpoint has no built-in
authentication β keep it on loopback or behind an authenticating gateway;
see docs/advanced-configuration.md#remote-http-transport. stdio remains
the default: nothing changes unless the variable is set.
What's New in v0.4.14
Community release β both changes came from external contributors. Thank you!
- New:
MAIL_ATTACHMENT_UPLOAD_DIR β confine outbound file_path
attachments by @mjones-PL in
#31, closing
#11. Without a
restriction, send tools would read any file the server process can access β
a prompt-injected model could exfiltrate SSH keys or .env files as
attachments. Set this variable to a directory and every file_path is
canonicalized (.. and symlinks resolved) and must land inside it; missing
and out-of-scope files return the same error so the check cannot probe for
file existence. Opt-in: when unset, behavior is unchanged. See
docs/security.md#outbound-attachment-scope.
- Fixed:
FROM_EMAIL validation no longer rejects dotless internal hosts
by @arwack in
#30. Addresses the
v0.4.13 upgrade caveat: noreply@localhost / alerts@intranet style
addresses on corporate internal relays are accepted again, while the real
typo checks (multiple @, whitespace, empty local part) remain. If you held
the v0.4.13 upgrade because of this, v0.4.14 is safe.
What's New in v0.4.13
effective_from() helper + FROM_EMAIL startup validation by
@arwack in
#29 β the follow-up
to their #19. The from_email β user fallback now lives in one place
(SmtpAccountConfig::effective_from()), and MAIL_SMTP_<ID>_FROM_EMAIL is
validated when the server starts instead of failing on the first send.
- Behavior change β read before upgrading: a malformed
FROM_EMAIL
(multiple @, whitespace, empty local part, or a domain without a dot) now
prevents the server from starting, for all accounts. Note that dotless
domains such as user@localhost or alerts@intranet are currently rejected
too; if you use an internal relay address like that, hold the upgrade β a
follow-up relaxing the dot rule is under discussion in #29.
- Hardened APPEND wire-format tests by
@tordable in
#28: mailbox
quoting, announced literal length, and byte-for-byte payload are now
asserted for every append test.
What's New in v0.4.12
Community release β both changes came from external contributors. Thank you!
- iCloud mailbox aliases + reads no longer mark messages as read by
@felipefdl in
#16. Short mailbox
names now resolve to each provider's real folder (
Sent β Sent Messages
on iCloud / [Gmail]/Sent Mail on Gmail, Trash β Deleted Messages, and
so on, multi-language) in search, copy, and move. Raw message fetches now use
BODY.PEEK[], so reading a message through the MCP no longer sets \Seen
as a side effect β with a BODY[] fallback for servers that reject PEEK
(the deprecated RFC822 item, removed in #23, stays out). Validated against
a real iCloud mailbox by the author; includes alias-resolution tests and
iCloud setup docs.
- Optimized multi-stage Dockerfile + docker-compose by
@monssefbaakka in
#5. cargo-chef layer
caching, TARGETARCH-aware musl cross-builds (amd64/arm64), and a
scratch
runtime image β 16.9 MB, down from 25.5 MB β verified to respond to MCP
initialize/tools-list over stdio. The toolchain pin was bumped to
Rust 1.90 (the codebase's let-chains require >= 1.88).
What's New in v0.4.11
Community bugfix release β both fixes came from external contributors. Thank you!