Apple Mail MCP Server

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

Connect your client

Local (npm)

Add in one click

Opens the app, which asks before it adds the server.

Or add it by command or config

Claude CodeTerminal
claude mcp add apple-mail -- npx -y apple-mail-mcp
Claude Desktopclaude_desktop_config.json
{
  "mcpServers": {
    "apple-mail": {
      "command": "npx",
      "args": [
        "-y",
        "apple-mail-mcp"
      ]
    }
  }
}
Cursor~/.cursor/mcp.json
{
  "mcpServers": {
    "apple-mail": {
      "command": "npx",
      "args": [
        "-y",
        "apple-mail-mcp"
      ]
    }
  }
}
VS Code.vscode/mcp.json
{
  "servers": {
    "apple-mail": {
      "type": "stdio",
      "command": "npx",
      "args": [
        "-y",
        "apple-mail-mcp"
      ]
    }
  }
}
Codex~/.codex/config.toml
[mcp_servers.apple-mail]
command = "npx"
args = ["-y", "apple-mail-mcp"]
Gemini CLITerminal
gemini mcp add --scope user apple-mail npx -- -y apple-mail-mcp
Local (from source)

Add in one click

Opens the app, which asks before it adds the server. Then replace each <NAME> in its settings with your value.

Or add it by command or config

FirstTerminal
git clone https://github.com/sweetrb/apple-mail-mcp.git
pnpm install
pnpm run build
Claude CodeTerminal
claude mcp add apple-mail -- node '<ABSOLUTE_PATH_TO_CLONE>/build/index.js'
Claude Desktopclaude_desktop_config.json
{
  "mcpServers": {
    "apple-mail": {
      "command": "node",
      "args": [
        "<ABSOLUTE_PATH_TO_CLONE>/build/index.js"
      ]
    }
  }
}
Cursor~/.cursor/mcp.json
{
  "mcpServers": {
    "apple-mail": {
      "command": "node",
      "args": [
        "<ABSOLUTE_PATH_TO_CLONE>/build/index.js"
      ]
    }
  }
}
VS Code.vscode/mcp.json
{
  "servers": {
    "apple-mail": {
      "type": "stdio",
      "command": "node",
      "args": [
        "<ABSOLUTE_PATH_TO_CLONE>/build/index.js"
      ]
    }
  }
}
Codex~/.codex/config.toml
[mcp_servers.apple-mail]
command = "node"
args = ["<ABSOLUTE_PATH_TO_CLONE>/build/index.js"]
Gemini CLITerminal
gemini mcp add --scope user apple-mail node -- '<ABSOLUTE_PATH_TO_CLONE>/build/index.js'

Settings 12

Set together: imap
APPLE_MAIL_MCP_IMAP_USER Optional: IMAP login address; setting it turns on the IMAP back end for one account. For IMAP back end.
APPLE_MAIL_MCP_IMAP_HOST Optional: IMAP server host. Default imap.gmail.com. For IMAP back end.
APPLE_MAIL_MCP_IMAP_PASSWORD secret Optional: IMAP app password; when unset the Keychain is used. For IMAP back end.
APPLE_MAIL_MCP_IMAP_ACCOUNTS Optional: JSON array of IMAP accounts, used in place of the single-account settings. For IMAP back end, several accounts.
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+. Default off. For search-messages on accounts without IMAP.
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. Default off. For body search on accounts without IMAP.
APPLE_MAIL_MCP_BODY_INDEX_FILE Optional: Where the body index file is kept. Default ~/Library/Application Support/apple-mail-mcp/body-index.db. For local body index.
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. For compose and account-scoped tools.
APPLE_MAIL_MCP_CONFIG_FILE Optional: Path of a config.json holding these settings, for plugin installs with no env block. Default ~/Library/Application Support/apple-mail-mcp/config.json. For all settings.
Set together: smtp
APPLE_MAIL_MCP_SMTP_HOST Optional: SMTP server host; together with the SMTP login it turns on sending over SMTP. For SMTP sending and apple-mail-send.
APPLE_MAIL_MCP_SMTP_USER Optional: SMTP login, also the sender address unless one is set. For SMTP sending and apple-mail-send.
APPLE_MAIL_MCP_SMTP_PASSWORD secret Optional: SMTP app password; when unset the Keychain is used. For SMTP sending and apple-mail-send.

What it does

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.

What it needs

RuntimeNode.js 20+
Runs onmacOS
Also
  • Apple Mail with at least one account set up

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.

Limits

  • Platform macOS only: the npm package declares darwin as its only platform and works through Mail.app
  • Size cap Batch tools take at most 100 message IDs, and a message takes at most 20 attachments
  • Rate limit send-serial-email sends to at most 100 recipients per call, 500 ms apart by default
  • Not covered Without IMAP or the opt-in local indexes, message bodies cannot be searched and big mailboxes can time out over AppleScript
  • Not covered get-message-rfc822 works only on accounts set up for IMAP
  • Size cap IMAP attachment fetches, the original of a reply or forward, and .emlx files for the body index stop at 25 MiB
  • Limit The local body index matches whole words and word beginnings, so Chinese, Japanese or Thai text and text with no letters are reported as not searched
  • Limit native-editor composition cannot be combined with SMTP or hand-built threading headers, and a new message needs an explicit account
  • Limit Smart mailbox tools edit Mail's plist file directly and do not quit Mail, so quit it first
  • Limit On Node older than 22.5 or without Full Disk Access, search-contacts returns an empty list instead of an error

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.

Who it suits

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.

Tools 52

20 only read, 28 change things, 4 can delete or overwrite. A ? marks an optional parameter.

search-messages
Finds messages by text, sender, subject, body, date range, read state, flag or flag colour; body search needs IMAPquery?: stringbody?: stringfrom?: stringsubject?: stringmailbox?: stringaccount?: stringisRead?: booleanisFlagged?: booleanflagColor?: enumdateFrom?: stringdateTo?: stringlimit?: integeroffset?: integer
get-message
Returns one message's full content as plain text or HTMLid: stringpreferHtml?: booleanmailbox?: stringaccount?: string
get-message-headers
Returns a message's headers without fetching the bodyid: stringmailbox?: stringaccount?: string
get-message-rfc822 changes
Returns or saves a message's original RFC 822 bytes with a SHA-256; IMAP accounts onlyid: stringsavePath?: stringfileName?: stringmaxBytes?: integer
get-thread
Gathers the messages of one conversation by normalised subjectid: stringaccount?: stringmailbox?: stringlimit?: number
list-messages
Lists a mailbox's messages with paging, a sender filter and an unread-only switchmailbox?: stringaccount?: stringlimit?: integeroffset?: integerfrom?: stringunreadOnly?: boolean
send-email changes
Sends a new message at once through Mail.app or, when set up, SMTP; can type into Mail's own editor insteadto: arraysubject: stringbody: stringcc?: arraybcc?: arrayaccount?: stringcomposition?: enumattachments?: arrayinReplyTo?: stringreferences?: arrayreplyTo?: stringtransport?: enum
send-serial-email changes
Sends a personalised copy to each of up to 100 recipients, filling {{placeholders}}recipients: arraysubject: stringbody: stringaccount?: stringtransport?: enumdelayMs?: number
create-draft changes
Saves a message to Drafts for review instead of sendingto: arraysubject: stringbody: stringcomposition?: enumcc?: arraybcc?: arrayaccount?: stringattachments?: arrayinReplyTo?: stringreferences?: array
reply-to-message changes
Replies to a message, optionally to all and with attachments, as a draft or sentid: stringtransport?: enumcomposition?: enumbody: stringreplyAll?: booleansend?: booleanattachments?: array
forward-message changes
Forwards a message to new recipients, as a draft or sentid: stringtransport?: enumcomposition?: enumto: arraybody?: stringsend?: booleanattachments?: array
mark-as-read changes
Marks a message as readid: string
mark-as-unread changes
Marks a message as unreadid: string
flag-message changes
Flags a message, optionally with a colourid: stringcolor?: enum
unflag-message changes
Removes a message's flagid: string
delete-message changes
Moves a message to the trashid: string
move-message changes
Moves a message to another mailboxid: stringmailbox: stringaccount?: string
batch-delete-messages changes
Moves up to 100 messages to the trash in one callids: arraysourceMailbox?: stringsourceAccount?: string
batch-move-messages changes
Moves up to 100 messages to another mailbox in one callids: arraymailbox: stringaccount?: stringsourceMailbox?: stringsourceAccount?: string
batch-mark-as-read changes
Marks up to 100 messages as readids: arraysourceMailbox?: stringsourceAccount?: string
batch-mark-as-unread changes
Marks up to 100 messages as unreadids: arraysourceMailbox?: stringsourceAccount?: string
batch-flag-messages changes
Flags up to 100 messages, optionally with a colourids: arraycolor?: enumsourceMailbox?: stringsourceAccount?: string
batch-unflag-messages changes
Removes flags from up to 100 messagesids: arraysourceMailbox?: stringsourceAccount?: string
resolve-message-id
Finds messages from their Message-ID headersids: array
list-attachments
Lists a message's attachments with name, type and sizeid: string
save-attachment changes
Writes an attachment to a folder under home, /tmp or /Volumesid: stringattachmentName: stringsavePath: string
fetch-attachment
Returns an attachment's bytes as base64 without writing a fileid: stringattachmentName: string
list-mailboxes
Lists mailboxes with message and unread countsaccount?: string
get-unread-count
Gives unread counts per mailboxmailbox?: stringaccount?: string
create-mailbox changes
Creates a mailboxname: stringaccount?: string
delete-mailbox deletes
Deletes a mailboxname: stringaccount?: string
rename-mailbox changes
Renames a mailboxoldName: stringnewName: stringaccount?: string
list-smart-mailboxes
Lists Mail's smart mailboxes
create-smart-mailbox changes
Creates a smart mailbox by editing Mail's settings filename: stringfromContains?: stringsubjectContains?: stringbodyContains?: string
delete-smart-mailbox deletes
Removes a smart mailboxname: string
create-newsletter-smart-mailboxes changes
Sets up smart mailboxes for frequent newsletter senders, with a dry-run optiondryRun?: booleanminCount?: integerdays?: integer
list-accounts
Lists the accounts set up in Mail
list-rules
Lists Mail rules and whether each is on
enable-rule changes
Turns a Mail rule onname: string
disable-rule changes
Turns a Mail rule offname: string
create-rule changes
Creates a Mail rule from conditions and actionsname: stringconditions?: arrayactions?: objectmatchAll?: booleanenabled?: boolean
delete-rule deletes
Deletes a Mail rule by namename: string
search-contacts
Looks up people in Contacts by namequery: string
save-template changes
Stores a reusable message template on diskname: stringsubject: stringbody: stringto?: arraycc?: arrayid?: string
list-templates
Lists stored templates
get-template
Returns one stored templateid: string
delete-template deletes
Removes a stored templateid: string
use-template changes
Creates a draft in Mail from a stored templateid: stringto?: arraycc?: arraysubject?: stringbody?: string
health-check
Checks that Mail.app answers
doctor
Checks Mail permission, accounts and each IMAP or SMTP connection and says what to fix
get-mail-stats
Gives message and unread counts per account and recent arrivalsaccount?: string
get-sync-status
Reports whether Mail is syncing

Resources 3

mail://accounts
The accounts set up in Mail, with address and whether each is enabled
mail://templates
The stored message templates
mail://mailboxes/{account}
The mailboxes of one account

Prompts 3

triage-inbox
Goes through unread mail and suggests what to file, answer, flag or delete
compose-reply
Drafts a reply to one message in a chosen tone
weekly-summary
Sums up the week's mail as a short briefing

Similar servers