Skip to main content
Glama
simkimsia

apple-mail-axi

by simkimsia

apple-mail-axi

An agent-first CLI and MCP server for Apple Mail drafts on macOS. It saves plain-text drafts with attachments in Mail.app, then lists, views and deletes them. After every save it reads the draft back from Mail and checks the recipients, subject and attachment count before it reports success.

It never sends mail. No command or tool can send a message or put one in Mail's outbox, and a test fails the build if any bundled script could.

The CLI follows the AXI principles: token-efficient TOON output, structured errors with a stable code and a next step, and definitive empty states. It is built on axi-sdk-js and modeled on gh-axi.

Status

v1, take 2. It covers drafts only. Reading and searching mail come in v2. Offline tests pass. The create path was probed live on one iCloud account; the full smoke test has not run yet. See STATUS.md.

Related MCP server: macos-mail-draft-mcp

Requirements

  • macOS with Mail.app set up with at least one account

  • Node.js >= 20

  • Mail.app running when you call it (open -a Mail)

Install

pnpm add -g @simkimsia/apple-mail-axi

Or run it without installing: npx -y @simkimsia/apple-mail-axi --help.

Check it: apple-mail-axi --version. Update later with apple-mail-axi update.

To work on it from a clone:

git clone https://github.com/simkimsia/apple-mail-axi
pnpm -C apple-mail-axi install
pnpm -C apple-mail-axi run build
pnpm add -g link:$PWD/apple-mail-axi   # puts `apple-mail-axi` on PATH

First run: permissions

apple-mail-axi drives Mail through osascript, so macOS asks once whether the app that runs it may control Mail. That app is your terminal for the CLI, or the MCP client (for example Claude) for the MCP server.

  • Click OK on the prompt "... wants access to control Mail".

  • If you clicked Don't Allow, commands fail with AUTOMATION_DENIED. Fix it in System Settings → Privacy & Security → Automation, then allow the terminal to control Mail.

  • If the app is not listed there, run tccutil reset AppleEvents and run any command again to get the prompt back.

Drafts need no Full Disk Access.

A compose window appears during create

draft create opens a real Mail compose window, fills it and saves it to Drafts. The window stays open while apple-mail-axi reads the saved draft back, because Mail writes attachments into the saved draft only after the save returns, and closing the window too early drops them. Once the draft matches the request (or the read-back gives up), the window is closed without saving again and without sending, and the draft is checked once more. The window is on screen for a few seconds, longer with attachments, and never longer than the 30-second read-back budget. Do not type into it while it is open. Mail does not save drafts from hidden compose windows, so there is no invisible mode. If a create is interrupted and a compose window titled with your subject stays open, close it and choose Don't Save; the error says so (COMPOSE_LEFT_OPEN). apple-mail-axi does not launch Mail for you; if Mail is closed, commands fail with MAIL_NOT_RUNNING and tell you to run open -a Mail.

CLI usage

apple-mail-axi                      # recent drafts
apple-mail-axi draft create --to ana@example.com --cc bo@example.com \
  --subject "Q3 notes" --body "Hi Ana, notes attached." \
  --attach ./notes.pdf --attach ./chart.png --from Work
printf 'Long body\n' | apple-mail-axi draft create --to ana@example.com --subject "Q3" --body-file -
apple-mail-axi draft list [--limit 50]
apple-mail-axi draft view <id> [--full]
apple-mail-axi draft delete <id>
apple-mail-axi draft --help
  • --to, --cc, --bcc and --attach repeat. Addresses are bare (ana@example.com).

  • The body comes from --body, --body-file <path>, or stdin with --body-file -. It is plain text only.

  • --from takes a Mail account name or one of its addresses. Leave it out to use Mail's default account.

  • --subject is required: it is how the saved draft is found again and checked.

  • draft delete takes the full numeric id from draft list and refuses any id that is not in Mail's Drafts mailbox.

Example output:

created:
  id: "48213"
  subject: Q3 notes
  from: Kim Sia <kim@work.example>
  account: Work
  to[1]: ana@example.com
  cc[1]: bo@example.com
  bcc: []
  attachments: 2
  verified: "recipients, subject and 2 attachment(s) read back from Mail"
  sent: false
help[2]:
  Run `apple-mail-axi draft view 48213` to check the body
  Open the draft in Mail's Drafts to review and send it yourself

Errors go to stdout in the same format, with exit code 1 (2 for a bad call):

Code

Meaning

VALIDATION_ERROR

bad flags or values; fix the call (exit 2)

AUTOMATION_DENIED

macOS blocked control of Mail (-1743); see First run above

MAIL_NOT_RUNNING

run open -a Mail

MAIL_NOT_SET_UP

Mail has no accounts

ATTACHMENT_NOT_FOUND / ATTACHMENT_UNREADABLE

the file is missing, a folder, or unreadable

UNKNOWN_SENDER

--from matches no enabled account; the help lists the valid ones

DRAFT_NOT_FOUND

no draft with that id in Drafts

DRAFT_NOT_SAVED / DRAFT_MISMATCH

the read-back did not find the draft, or found it different from the request

DELETE_NOT_CONFIRMED

Mail took the delete but the draft is still listed

COMPOSE_LEFT_OPEN

a compose window apple-mail-axi opened could not be closed; close it by hand

MAIL_TIMEOUT

Mail did not answer in time

UNSUPPORTED_PLATFORM

not macOS

MCP server

apple-mail-axi mcp runs a stdio MCP server with the same four operations as tools: draft_create, draft_list, draft_view and draft_delete. They take the same arguments as the CLI (snake_case: body_file) and return the same text and error codes. The one difference is wording in help: next steps: over MCP they name tools and arguments (Call the `draft_view` tool with `{id: '48213'}` ) instead of CLI commands. Stdin (--body-file -) is the one CLI-only input; pass body instead.

Claude Code:

claude mcp add apple-mail -- apple-mail-axi mcp

Claude Desktop, Cursor or any client that takes a JSON config:

{
  "mcpServers": {
    "apple-mail": {
      "command": "apple-mail-axi",
      "args": ["mcp"]
    }
  }
}

Without a global install, use "command": "npx" and "args": ["-y", "@simkimsia/apple-mail-axi", "mcp"]. The first tool call triggers the Automation prompt for the client app.

Agent skill

Install the bundled skill so your coding agent prefers apple-mail-axi:

npx skills add simkimsia/apple-mail-axi --skill apple-mail-axi -g

Source: skills/apple-mail-axi/SKILL.md.

Development

pnpm install
pnpm run dev          # run from source (tsx)
pnpm test             # vitest, offline: osascript is stubbed
pnpm run build        # tsc -> dist/
pnpm run format:check

The live smoke test creates and deletes one real draft from the iCloud account (override with APPLE_MAIL_AXI_LIVE_FROM), checks that no compose window is left open, and runs only when asked:

APPLE_MAIL_AXI_LIVE=1 pnpm vitest run test/live.smoke.test.ts

Changelog

Release notes live in CHANGELOG.md and on GitHub Releases once the first release is cut. release-please writes both from conventional commits, so do not edit the file by hand.

License

MIT

Related MCP Connectors

Related MCP Servers

  • A
    license
    Not graded
    quality
    D
    maintenance
    Creates email drafts in the macOS Mail app using AppleScript, with support for recipients, subject, body, attachments, and sender selection. No email is sent; drafts are saved locally and synced to the mail server by Mail.
    MIT
  • A
    license
    Not graded
    quality
    B
    maintenance
    Enables MCP clients to search, read, and inspect Apple Mail on macOS, including conversations and attachments. It can create new, reply, reply-all, or forward drafts, but cannot send or modify existing messages.
    MIT
  • A
    license
    A
    quality
    A
    maintenance
    Enables AI agents to create, list, update, and delete Microsoft 365 email drafts, with a hard guarantee that no message can be sent automatically, ensuring human review before any email goes out.
    12
    39 PyPI
    MIT