mcp-email-server
Read, search, file and send mail on any IMAP and SMTP account from an MCP client
Read, search, send and organise mail in the macOS Mail app from an MCP client
By Rob Sweet (individual) TypeScript Local · stdio CommunicationProductivity
62 popularity score ★ 107 ↓ 6.7k/wk
Opens the app, which asks before it adds the server.
claude mcp add apple-mail -- npx -y apple-mail-mcp{
"mcpServers": {
"apple-mail": {
"command": "npx",
"args": [
"-y",
"apple-mail-mcp"
]
}
}
}{
"mcpServers": {
"apple-mail": {
"command": "npx",
"args": [
"-y",
"apple-mail-mcp"
]
}
}
}{
"servers": {
"apple-mail": {
"type": "stdio",
"command": "npx",
"args": [
"-y",
"apple-mail-mcp"
]
}
}
}[mcp_servers.apple-mail]
command = "npx"
args = ["-y", "apple-mail-mcp"]gemini mcp add --scope user apple-mail npx -- -y apple-mail-mcpOpens the app, which asks before it adds the server. Then replace each <NAME> in its settings with your value.
git clone https://github.com/sweetrb/apple-mail-mcp.git
pnpm install
pnpm run buildclaude mcp add apple-mail -- node '<ABSOLUTE_PATH_TO_CLONE>/build/index.js'{
"mcpServers": {
"apple-mail": {
"command": "node",
"args": [
"<ABSOLUTE_PATH_TO_CLONE>/build/index.js"
]
}
}
}{
"mcpServers": {
"apple-mail": {
"command": "node",
"args": [
"<ABSOLUTE_PATH_TO_CLONE>/build/index.js"
]
}
}
}{
"servers": {
"apple-mail": {
"type": "stdio",
"command": "node",
"args": [
"<ABSOLUTE_PATH_TO_CLONE>/build/index.js"
]
}
}
}[mcp_servers.apple-mail]
command = "node"
args = ["<ABSOLUTE_PATH_TO_CLONE>/build/index.js"]gemini mcp add --scope user apple-mail node -- '<ABSOLUTE_PATH_TO_CLONE>/build/index.js'| Set together: imap | |
|---|---|
APPLE_MAIL_MCP_IMAP_USER |
Optional: IMAP login address; setting it turns on the IMAP back end for one account. |
APPLE_MAIL_MCP_IMAP_HOST |
Optional: IMAP server host. |
APPLE_MAIL_MCP_IMAP_PASSWORD secret |
Optional: IMAP app password; when unset the Keychain is used. |
APPLE_MAIL_MCP_IMAP_ACCOUNTS |
Optional: JSON array of IMAP accounts, used in place of the single-account settings. |
APPLE_MAIL_MCP_ENVELOPE_INDEX |
Optional: Set to 1, true, yes or on to answer header searches from Mail's on-disk Envelope Index, read-only; needs Full Disk Access and Node 22.5+. |
APPLE_MAIL_MCP_BODY_INDEX |
Optional: Set to 1, true, yes or on to build a local full-text index of Mail's .emlx files; works only with APPLE_MAIL_MCP_ENVELOPE_INDEX also on. |
APPLE_MAIL_MCP_BODY_INDEX_FILE |
Optional: Where the body index file is kept. |
APPLE_MAIL_MCP_DEFAULT_ACCOUNT |
Optional: Account name or address used when a tool gets no account; when unset Mail's default sending account or the first enabled account is used. |
APPLE_MAIL_MCP_CONFIG_FILE |
Optional: Path of a config.json holding these settings, for plugin installs with no env block. |
| Set together: smtp | |
APPLE_MAIL_MCP_SMTP_HOST |
Optional: SMTP server host; together with the SMTP login it turns on sending over SMTP. |
APPLE_MAIL_MCP_SMTP_USER |
Optional: SMTP login, also the sender address unless one is set. |
APPLE_MAIL_MCP_SMTP_PASSWORD secret |
Optional: SMTP app password; when unset the Keychain is used. |
Apple Mail MCP Server is a local Node.js server that drives Mail on a Mac through AppleScript. A client can search and read messages, send, reply, forward and draft mail, and move, flag or delete messages singly or 100 at a time. It also manages mailboxes, smart mailboxes and Mail rules, looks people up in Contacts and keeps reusable templates.
No settings are needed to start. Two optional back ends are switched on with APPLE_MAIL_MCP_* settings, given in the client's env block or a config.json file. IMAP adds server-side and body search, and SMTP sends without going through Mail. The README says passwords live in the Keychain, but the code also reads APPLE_MAIL_MCP_IMAP_PASSWORD and APPLE_MAIL_MCP_SMTP_PASSWORD and uses them before trying the Keychain.
Accounts without IMAP get two opt-in search paths that read Mail's own files from disk. With APPLE_MAIL_MCP_ENVELOPE_INDEX=1, header searches go to Mail's Envelope Index database, opened read-only, so big mailboxes are searched in full. Add APPLE_MAIL_MCP_BODY_INDEX=1 and the server also builds a local full-text index of the .emlx files under ~/Library/Mail for body search. The code only turns the body index on when both flags are set. It keeps search tokens rather than message copies and reads at most 100,000 characters of each body. Both need Node 22.5+ and Full Disk Access, and anything the index can't answer falls back to AppleScript.
The compose tools (send-email, create-draft, reply-to-message, forward-message) take composition: "native-editor". That types the text into Mail's own compose window, keeping Mail's signature and a reply's quoted history, and it stops without sending if it can't confirm the result.
src/index.ts registers 52 tools. send-serial-email takes at most 100 recipients per call and waits 500 ms between sends unless delayMs says otherwise (up to 10000). The package also installs apple-mail-send, a command-line SMTP sender that exits with code 78 when SMTP is not configured.
| Runtime | Node.js 20+ |
|---|---|
| Runs on | macOS |
| Also |
|
macOS asks for permission to automate Mail on first use. search-contacts needs Node.js 22.5 or later and Full Disk Access, and smart mailbox tools also need Full Disk Access. The opt-in Envelope Index and body index searches need the same two things: Node 22.5+ for node:sqlite and Full Disk Access for the Node binary that runs the server. The native-editor composition needs Accessibility and Automation permission and an unlocked desktop. The IMAP and SMTP back ends need the provider's host, the login and an app password, kept in the Keychain or in the password setting. Codex users can import a standalone plugin archive from the release assets, which gets no updates.
macOS only. send-email sends plain text, and HTML needs the separate apple-mail-send command. Batch tools take at most 100 message IDs, send-email and create-draft at most 20 file attachments. Without IMAP or APPLE_MAIL_MCP_ENVELOPE_INDEX, huge mailboxes time out over AppleScript, and without IMAP mailboxes or drafts on server accounts cannot be deleted, renamed or changed. save-attachment writes only under the home folder, /tmp and /Volumes. Attachment fetches over IMAP stop at 25 MiB. Smart mailbox edits change Mail's plist directly, so Mail should be quit first. On an older Node or without Full Disk Access, search-contacts returns an empty list rather than an error.
Suits someone on a Mac who already keeps their mail in Apple Mail and wants a client to triage, search, file and draft across those accounts, with no mail provider token handed to the server by default. It is a poor fit off macOS or on a headless machine. Because it can send mail at once and delete messages, people who want a review step can ask for create-draft instead of send-email.
20 only read, 28 change things, 4 can delete or overwrite. A ? marks an optional parameter.
search-messagesquery?: stringbody?: stringfrom?: stringsubject?: stringmailbox?: stringaccount?: stringisRead?: booleanisFlagged?: booleanflagColor?: enumdateFrom?: stringdateTo?: stringlimit?: integeroffset?: integerget-messageid: stringpreferHtml?: booleanmailbox?: stringaccount?: stringget-message-headersid: stringmailbox?: stringaccount?: stringget-message-rfc822 changesid: stringsavePath?: stringfileName?: stringmaxBytes?: integerget-threadid: stringaccount?: stringmailbox?: stringlimit?: numberlist-messagesmailbox?: stringaccount?: stringlimit?: integeroffset?: integerfrom?: stringunreadOnly?: booleansend-email changesto: arraysubject: stringbody: stringcc?: arraybcc?: arrayaccount?: stringcomposition?: enumattachments?: arrayinReplyTo?: stringreferences?: arrayreplyTo?: stringtransport?: enumsend-serial-email changesrecipients: arraysubject: stringbody: stringaccount?: stringtransport?: enumdelayMs?: numbercreate-draft changesto: arraysubject: stringbody: stringcomposition?: enumcc?: arraybcc?: arrayaccount?: stringattachments?: arrayinReplyTo?: stringreferences?: arrayreply-to-message changesid: stringtransport?: enumcomposition?: enumbody: stringreplyAll?: booleansend?: booleanattachments?: arrayforward-message changesid: stringtransport?: enumcomposition?: enumto: arraybody?: stringsend?: booleanattachments?: arraymark-as-read changesid: stringmark-as-unread changesid: stringflag-message changesid: stringcolor?: enumunflag-message changesid: stringdelete-message changesid: stringmove-message changesid: stringmailbox: stringaccount?: stringbatch-delete-messages changesids: arraysourceMailbox?: stringsourceAccount?: stringbatch-move-messages changesids: arraymailbox: stringaccount?: stringsourceMailbox?: stringsourceAccount?: stringbatch-mark-as-read changesids: arraysourceMailbox?: stringsourceAccount?: stringbatch-mark-as-unread changesids: arraysourceMailbox?: stringsourceAccount?: stringbatch-flag-messages changesids: arraycolor?: enumsourceMailbox?: stringsourceAccount?: stringbatch-unflag-messages changesids: arraysourceMailbox?: stringsourceAccount?: stringresolve-message-idids: arraylist-attachmentsid: stringsave-attachment changesid: stringattachmentName: stringsavePath: stringfetch-attachmentid: stringattachmentName: stringlist-mailboxesaccount?: stringget-unread-countmailbox?: stringaccount?: stringcreate-mailbox changesname: stringaccount?: stringdelete-mailbox deletesname: stringaccount?: stringrename-mailbox changesoldName: stringnewName: stringaccount?: stringlist-smart-mailboxescreate-smart-mailbox changesname: stringfromContains?: stringsubjectContains?: stringbodyContains?: stringdelete-smart-mailbox deletesname: stringcreate-newsletter-smart-mailboxes changesdryRun?: booleanminCount?: integerdays?: integerlist-accountslist-rulesenable-rule changesname: stringdisable-rule changesname: stringcreate-rule changesname: stringconditions?: arrayactions?: objectmatchAll?: booleanenabled?: booleandelete-rule deletesname: stringsearch-contactsquery: stringsave-template changesname: stringsubject: stringbody: stringto?: arraycc?: arrayid?: stringlist-templatesget-templateid: stringdelete-template deletesid: stringuse-template changesid: stringto?: arraycc?: arraysubject?: stringbody?: stringhealth-checkdoctorget-mail-statsaccount?: stringget-sync-statusmail://accountsmail://templatesmail://mailboxes/{account}triage-inboxcompose-replyweekly-summary