A Model Context Protocol (MCP) server providing programmatic access to macOS Mail.app using JavaScript for Automation (JXA).
- Overview
- Security & Privacy
- Features
- Requirements
- Installation
- Usage
- Permissions
- Troubleshooting
- Available Tools
- Custom Styling
- Upgrading
- Uninstalling
- Architecture
- Development
- Error Handling
- Limitations
- License
This MCP server enables AI assistants and other MCP clients to interact with Apple Mail on macOS. It provides read-only access to mailboxes, messages, and search functionality through a clean, typed interface.
- Human-in-the-loop design: No emails are sent automatically - all drafts require manual sending. This prevents agents from sending emails without human oversight.
- No data transmitted outside of the MCP connection
- Runs locally on your machine
- Grant automation and accessibility permissions to the MCP server alone, not to the terminal or any other application like Claude Code.
- No credentials to a mail account ot SMTP server required, all interactions happen transparently with the Mail.app.
- List Accounts: Enumerate all configured email accounts with their properties
- List Mailboxes: Enumerate all available mailboxes and accounts
- Get Message Content: Fetch detailed content of individual messages
- Get Selected Messages: Retrieve currently selected message(s) in Mail.app
- Find Messages: Search messages with efficient filtering by subject, sender, read status, flags, and date ranges
- Create Reply Draft: Create a reply to a message with preserved quotes using the Accessibility API.
- Create Outgoing Message: Create new email drafts with Markdown rendering to rich text.
- Replace Drafts: Robustly update existing drafts (replies or standalone) while preserving quotes and signatures.
- Rich Text Support: Native support for Markdown (headings, bold, italic, links, strikethrough, lists, code blocks, and more) using native Mail.app rendering via the Accessibility API.
- macOS (Mail.app is macOS-only)
- Mail.app configured with at least one email account (does not need to be running at server startup)
- Automation and Accessibility permissions for Mail.app (see Permissions below)
# Add the tap
brew tap dastrobu/tap
# Install
brew install mail-mcp
# Start the service (Standard)
brew services start mail-mcp
# OR use the built-in subcommand for more customization (port, debug)
mail-mcp launchd createImportant: For proper automation permissions, you must run the server as a service (not from Terminal). Using brew services start is the standard way, while mail-mcp launchd create offers more customization.
Note: When you upgrade via brew upgrade mail-mcp, the launchd service will automatically restart with the new version if it's already running. You don't need to manually recreate the service.
➡️ See Usage for how to configure and use the server.
Download the latest release from GitHub Releases:
- Intel Mac:
mail-mcp_*_darwin_amd64.tar.gz - Apple Silicon:
mail-mcp_*_darwin_arm64.tar.gz
# Extract
tar -xzf mail-mcp_*.tar.gz
# Set up launchd service (uses full path to binary)
mail-mcp launchd create➡️ See Usage for how to configure and use the server.
# Install directly from GitHub (requires Go 1.26+)
go install github.com/dastrobu/mail-mcp@latest
# Set up launchd service
mail-mcp launchd createNote: Ensure $GOPATH/bin (or $HOME/go/bin) is in your PATH, or use the full path:
~/go/bin/mail-mcp launchd create➡️ See Usage for how to configure and use the server.
git clone https://github.com/dastrobu/mail-mcp.git
cd mail-mcp
# Build locally
go build -v -o mail-mcp .
# Set up launchd service
./mail-mcp launchd create➡️ See Usage for how to configure and use the server.
The server supports two transport modes: HTTP (recommended) and STDIO.
HTTP mode runs the server as a standalone daemon, allowing automation permissions to be granted directly to the mail-mcp binary rather than the parent application.
Create a launch agent to run the server in the background.
Quick setup using the built-in subcommand:
# Run the setup subcommand
mail-mcp launchd create➡️ See MCP Client Configuration to connect your MCP client.
Or alternatively, create the launch agent manually:
# See available options
mail-mcp launchd create -h
# With custom port
mail-mcp --port=3000 launchd create
# With debug logging enabled
mail-mcp --debug launchd create
# Disable automatic startup on login (start manually instead)
mail-mcp launchd create --disable-run-at-load
# The subcommand will:
# - Create the launchd plist
# - Load and start the service
# - Show you the connection URL and useful commandsTo remove the service:
mail-mcp launchd removeCheck logs: tail -f ~/Library/Logs/com.github.dastrobu.mail-mcp/mail-mcp.log ~/Library/Logs/com.github.dastrobu.mail-mcp/mail-mcp.err
To stop: launchctl stop com.github.dastrobu.mail-mcp
To unload: launchctl unload ~/Library/LaunchAgents/com.github.dastrobu.mail-mcp.plist
If you launch from Terminal, Terminal will be asked for permissions, not the binary:
# This will prompt for Terminal's permissions (not ideal)
mail-mcp --transport=http
# Custom port
mail-mcp --transport=http --port=3000
# Custom host and port
mail-mcp --transport=http --host=0.0.0.0 --port=3000This is fine for quick testing, but for production use launchd.
Connect MCP clients to: http://localhost:8787
➡️ See MCP Client Configuration to connect your MCP client.
STDIO mode runs the server as a child process of the MCP client. Note that automation permissions will be required for the parent application (Terminal, Claude Desktop, etc.).
mail-mcp➡️ See MCP Client Configuration to connect your MCP client.
Make sure the server is running, see HTTP Transport
Configure VS Code (~/Library/Application Support/Code/User/mcp.json on macOS):
{
"servers": {
"mail-mcp": {
"type": "http",
"url": "http://localhost:8787"
}
}
}Make sure the server is running, see HTTP Transport
Configure Zed (~/.config/zed/settings.json):
{
"context_servers": {
"mail-mcp": {
"url": "http://localhost:8787"
}
}
}Make sure the server is running, see HTTP Transport
Configure Claude Desktop (~/Library/Application Support/Claude/claude_desktop_config.json):
{
"mcpServers": {
"mail-mcp": {
"url": "http://localhost:8787"
}
}
}Use -h or --help with any command to see available options:
mail-mcp -h # Show main help
mail-mcp launchd -h # Show launchd subcommands
mail-mcp launchd create -h # Show launchd create optionsAvailable options:
--transport=[stdio|http] Transport type (default: stdio)
--port=PORT HTTP port (default: 8787, only used with --transport=http)
--host=HOST HTTP host (default: localhost, only used with --transport=http)
--debug Enable debug logging of tool calls and results to stderr
--rich-text-styles=PATH Path to custom rich text styles YAML file (uses embedded default if not specified)
-h, --help Show help message
Commands:
launchd create Set up launchd service for automatic startup (HTTP mode)
Use --debug flag to enable debug logging in the service
Use --disable-run-at-load to prevent automatic startup on login
launchd remove Remove launchd service
completion bash Generate bash completion script
Options can also be set via environment variables:
APPLE_MAIL_MCP_TRANSPORT=http
APPLE_MAIL_MCP_PORT=8787
APPLE_MAIL_MCP_HOST=localhost
APPLE_MAIL_MCP_DEBUG=true
APPLE_MAIL_MCP_RICH_TEXT_STYLES=/path/to/custom_styles.yaml
➡️ See MCP Client Configuration to connect your MCP client.
macOS requires both Automation and Accessibility permissions for full functionality.
The draft creation and replacement tools (create_reply_draft, replace_reply_draft, create_outgoing_message, replace_outgoing_message) use the macOS Accessibility API to simulate pasting content.
This is the only reliable way to support rich text (Markdown) while preserving original message quotes and signatures.
If you only want to use tools that read emails, you can skip granting the accessibility permission.
To ensure the highest level of security, grant accessibility permissions directly to the mail-mcp binary alone:
- Open System Settings → Privacy & Security → Accessibility.
- Click the + (plus) button at the bottom of the list.
- In the file picker that appears, navigate to the path where
mail-mcpis installed.- Tip: Press
Cmd + Shift + Gto enter the path manually (e.g./usr/local/bin/mail-mcp).
- Tip: Press
- Select the binary and click Open.
- Ensure the toggle switch next to
mail-mcpis ON.
If permissions are missing, these tools will return an error explaining what to do.
macOS requires automation permissions to control Mail.app. The permission behavior depends on which transport mode you use:
When using --transport=http, permissions can be granted to the mail-mcp binary itself, but only if launched without Terminal as the parent process.
Using launchd (recommended):
- Set up the launchd service:
mail-mcp launchd create - macOS will prompt for automation permissions for
mail-mcpbinary - Click OK to grant access
- The server is now ready to use
Using Finder:
- Double-click the
mail-mcpbinary in Finder - macOS will prompt for automation permissions for
mail-mcpbinary - Click OK to grant access
Using Terminal (quick testing only):
- Run
mail-mcp --transport=httpfrom Terminal - macOS will prompt for automation permissions for Terminal.app (not the binary)
- Click OK to grant access to Terminal
- Note: This grants permission to Terminal, not the binary
Advantage: With launchd or Finder launch, permissions stay with the binary and work with all MCP clients. With Terminal launch, only Terminal gets permissions.
When using STDIO mode (default), permissions are granted to the parent process (Terminal, Claude Desktop, etc.) that launches the server:
- Start the server (or let your MCP client start it)
- macOS will prompt for automation permissions on first run
- Click OK to grant access to the parent application
- The server is now ready to use
Note: If you switch between different applications (e.g., Terminal vs Claude Desktop), each will need its own automation permission.
If the prompt doesn't appear or you need to change permissions:
- Open System Settings → Privacy & Security → Automation
- Find
mail-mcp(HTTP mode) or the parent application (STDIO mode) - Enable the checkbox next to Mail
- Restart the server
To reset automation permissions (useful for testing or troubleshooting):
# Reset all automation permissions (will prompt again on next run)
tccutil reset AppleEvents
# Reset for a specific application (e.g., Terminal)
tccutil reset AppleEvents com.apple.Terminal
# Reset for a specific application (e.g., Mail)
tccutil reset AccessibilityAfter resetting, the next time the server tries to control Mail.app, macOS will show the permission prompt again.
If you see:
Mail.app startup check failed: osascript execution failed: signal: killed
Solution: Grant automation permissions using the steps in Automation Permissions above.
The server can start without Mail.app running. When you try to use a tool and Mail.app is not running, you'll receive a clear error message:
- "Mail.app is not running. Please start Mail.app and try again" - Simply open Mail.app and retry
- "Mail.app automation permission denied..." - Grant automation permissions in System Settings > Privacy & Security > Automation
Tool calls will automatically work once Mail.app is started and permissions are granted.
When --debug is enabled, the server logs all MCP protocol interactions and JXA script diagnostics to stderr, including tool calls, results, and JXA script logs. See DEBUG_LOGGING.md for details.
mail-mcp --debugEnable tab completion for commands and flags:
# Generate completion script
mail-mcp completion bash > /usr/local/etc/bash_completion.d/mail-mcp
# Or add to your ~/.bashrc or ~/.bash_profile
source <(mail-mcp completion bash)After sourcing, you can use tab completion:
mail-mcp --transport=<TAB> # Completes: http, stdio
mail-mcp launchd <TAB> # Completes: create, removeLists all configured email accounts in Apple Mail.
Parameters:
enabled(boolean, optional): Filter to only show enabled accounts (default: false)
Output:
{
"accounts": [
{
"name": "Exchange",
"enabled": true,
"emailAddresses": ["[email protected]"],
"mailboxCount": 22
}
],
"count": 1
}Lists all available mailboxes across all Mail accounts.
Fetches the full content of a specific message including body, headers, recipients, and attachments.
Parameters:
account(string, required): Name of the email accountmailbox(string, required): Name of the mailbox (e.g., "INBOX", "Sent")message_id(integer, required): The unique ID of the message
Output:
- Full message object including:
- Basic fields: id, subject, sender, replyTo
- Dates: dateReceived, dateSent
- Content: content (body text), allHeaders
- Status: readStatus, flaggedStatus
- Recipients: toRecipients, ccRecipients, bccRecipients (with name and address)
- Attachments: array of attachment objects with name, fileSize, and downloaded status
Gets the currently selected message(s) in the frontmost Mail.app viewer window.
Parameters:
- None (operates on current selection)
Output:
{
"count": 1,
"messages": [
{
"id": 123456,
"subject": "Meeting Tomorrow",
"sender": "[email protected]",
"dateReceived": "2024-02-11T10:30:00Z",
"dateSent": "2024-02-11T10:25:00Z",
"readStatus": true,
"flaggedStatus": false,
"junkMailStatus": false,
"mailbox": "INBOX",
"account": "Work"
}
]
}Finds messages in a mailbox using efficient whose() filtering. Supports filtering by subject, sender, read status, flagged status, and date ranges. Uses constant-time filtering for optimal performance.
Important: At least one filter criterion must be specified to prevent accidentally fetching all messages.
Parameters:
account(string, required): Name of the email accountmailboxPath(array of strings, required): Mailbox path array (e.g.,["Inbox"]or["Inbox", "GitHub"])subject(string, optional): Filter by subject (substring match)sender(string, optional): Filter by sender email address (substring match)readStatus(boolean, optional): Filter by read status (true for read, false for unread)flaggedOnly(boolean, optional): Filter for flagged messages only (default: false)dateAfter(string, optional): Filter for messages received after this ISO date (e.g., "2024-01-01T00:00:00Z")dateBefore(string, optional): Filter for messages received before this ISO date (e.g., "2024-12-31T23:59:59Z")limit(integer, optional): Maximum number of messages to return (1-1000, default: 50)
Note: While all filter parameters are individually optional, you must provide at least one filter criterion. The tool will return an error if no filters are specified.
Output:
{
"messages": [
{
"id": 123456,
"subject": "Meeting Tomorrow",
"sender": "[email protected]",
"date_received": "2024-02-11T10:30:00Z",
"date_sent": "2024-02-11T10:25:00Z",
"read_status": true,
"flagged_status": false,
"message_size": 2048,
"content_preview": "Hi team, just wanted to remind everyone about...",
"content_length": 500,
"to_count": 3,
"cc_count": 1,
"total_recipients": 4,
"mailbox_path": ["Inbox"],
"account": "Work"
}
],
"count": 1,
"total_matches": 15,
"limit": 50,
"has_more": false,
"filters_applied": {
"subject": "meeting",
"sender": null,
"read_status": null,
"flagged_only": false,
"date_after": "2024-02-01T00:00:00Z",
"date_before": null
}
}Performance:
The tool uses JXA's whose() method for constant-time O(1) filtering, which is approximately 150x faster than iterating through messages. This makes it efficient even for mailboxes with thousands of messages.
Examples:
Find unread messages from a specific sender:
{
"account": "Work",
"mailboxPath": ["Inbox"],
"sender": "[email protected]",
"readStatus": false,
"limit": 10
}Find flagged messages from last week:
{
"account": "Personal",
"mailboxPath": ["Inbox"],
"flaggedOnly": true,
"dateAfter": "2024-02-04T00:00:00Z",
"limit": 50
}Find all messages with specific subject in nested mailbox:
{
"account": "Work",
"mailboxPath": ["Inbox", "GitHub", "notifications"],
"subject": "Pull Request",
"limit": 100
}Lists persistent draft messages from the Drafts mailbox for a specific account.
Parameters:
account(string, required): Name of the email accountlimit(integer, optional): Maximum number of drafts to return (1-1000, default: 50)
Creates a reply to a specific message using the Accessibility API. This approach preserves the original message quote and signature. It requires Accessibility permissions for the mail-mcp binary. The message is NOT sent automatically.
Parameters:
account(string, required): Name of the email accountmailboxPath(array of strings, required): Path to the mailbox as an array (e.g.["Inbox"]). Use themailboxPathfield fromget_selected_messagesorfind_messages.message_id(integer, required): The unique ID of the message to reply toreply_content(string, required): The content/body of the reply messagecontent_format(string, optional): Content format: "plain" or "markdown". Default is "markdown"reply_to_all(boolean, optional): Whether to reply to all recipients. Default is false.
Output:
draft_id: ID of the created draft messagesubject: Subject line of the replyoriginal_message_id: ID of the message replied tomessage: Confirmation message
Replaces an existing reply draft with new content while preserving the original message quote and signature. It achieves this by deleting the old draft and creating a fresh reply to the original message before pasting the new content. Requires Accessibility permissions.
Parameters:
outgoing_id(integer, required): The ID of the reply draft to replace (fromlist_outgoing_messages)original_message_id(integer, required): The ID of the original message being replied toaccount(string, required): The account name of the original messagemailbox_path(array of strings, required): The mailbox path of the original messagecontent(string, required): New email body content (supports Markdown)content_format(string, optional): Content format: "plain" or "markdown". Default is "markdown"subject(string, optional): New subject line (optional)to_recipients(array of strings, optional): New list of To recipientscc_recipients(array of strings, optional): New list of CC recipientsbcc_recipients(array of strings, optional): New list of BCC recipientssender(string, optional): New sender email address
Creates a new outgoing email message using the Accessibility API to support rich text content. The message is saved but NOT sent automatically. Requires Accessibility permissions.
Parameters:
subject(string, required): Subject line of the emailcontent(string, required): Email body content (supports Markdown formatting whencontent_formatis "markdown")content_format(string, optional): Content format: "plain" or "markdown". Default is "markdown"to_recipients(array of strings, required): List of To recipient email addressescc_recipients(array of strings, optional): List of CC recipient email addressesbcc_recipients(array of strings, optional): List of BCC recipient email addressessender(string, optional): Sender email address (uses default account if omitted)
Lists all OutgoingMessage objects currently in memory in Mail.app. These are unsent messages that were created with create_outgoing_message or create_reply_draft. Returns outgoing_id for each message which can be used with replacement tools.
Replaces an existing outgoing message (draft) with new content using the Accessibility API. This tool is for standalone drafts (not replies). It deletes the old draft and creates a fresh instance before pasting the new content. Requires Accessibility permissions.
Parameters:
outgoing_id(integer, required): The ID of the outgoing message to replacecontent(string, required): New email body content (supports Markdown)content_format(string, optional): Content format: "plain" or "markdown". Default is "markdown"subject(string, optional): New subject lineto_recipients(array of strings, optional): New list of To recipientscc_recipients(array of strings, optional): New list of CC recipientsbcc_recipients(array of strings, optional): New list of BCC recipientssender(string, optional): New sender email address
Rich Text Formatting:
When content_format is set to "markdown", the content is parsed as Markdown and rendered with rich text styling:
Supported Markdown Elements:
- Headings:
# H1through###### H6 - Bold:
**bold text** - Italic:
*italic text* - Bold+Italic:
***bold and italic text*** - Strikethrough:
~~strikethrough text~~(natively supported) - Inline Code:
`code` - Code Blocks:
```code block``` - Blockquotes:
> quote - Lists: Unordered (
-,*) and ordered (1.,2.) - Nested Lists: Up to 4 levels deep
- Links:
[text](url)(rendered as native, clickable links) - Horizontal Rules:
--- - Hard Line Breaks: Two spaces at end of line creates line break within paragraph
Example:
{
"subject": "Project Update",
"content": "# Weekly Report\n\nThis week we:\n\n- Completed **Phase 1**\n- Started *Phase 2*\n\n## Code Changes\n\n```\nfunction example() {\n return true;\n}\n```",
"content_format": "markdown",
"to_recipients": ["[email protected]"],
"opening_window": false
}You can customize rich text styling by providing a YAML configuration file:
mail-mcp --rich-text-styles=/path/to/custom_styles.yamlFull Configuration Example:
Here's a complete example based on the default styles:
# Color definitions using YAML anchors (x- prefix makes them ignorable extensions)
x-colors:
dark_gray: &50_percent_gray "#7F7F7F"
blue: &blue "#0000FF"
defaults:
font: "Helvetica"
size: 12
color: null
styles:
# Headings - customize font, size, color, and spacing
h1:
font: "Helvetica-Bold"
size: 20
color: null
margin_top: 10
margin_bottom: 5
h2:
font: "Helvetica-Bold"
size: 18
color: null
margin_top: 8
margin_bottom: 4
h3:
font: "Helvetica-Bold"
size: 16
color: null
margin_top: 6
margin_bottom: 2
# Inline styles
bold:
font: "Helvetica-Bold"
italic:
font: "Helvetica-Oblique"
strikethrough:
font: "Helvetica"
color: *50_percent_gray
bold_italic:
font: "Helvetica-BoldOblique"
code:
font: "Menlo-Regular"
size: 11
color: null
# Block styles
code_block:
font: "Menlo-Regular"
size: 11
color: null
margin_top: 6
margin_bottom: null
prefix:
content: " " # Indent code blocks with 2 spaces
blockquote:
font: "Helvetica-Oblique"
size: null # Inherits from defaults
color: *50_percent_gray
margin_top: 6
margin_bottom: 6
prefix:
content: "> "
list:
margin_top: 6
margin_bottom: 6
list_item:
font: "Helvetica"
size: 12
color: null
horizontal_rule:
font: "Helvetica"
size: 1
color: *50_percent_gray
link:
color: *blue # Or null to use Mail.app's default link colorKey Points:
- Colors use web format (
#RRGGBB) and support YAML anchors for reusability margin_topandmargin_bottomare measured in font points- Set properties to
nullto inherit from defaults or parent elements - Margins apply to entire blocks (e.g., whole list), not individual items
- Use
prefix.contentto add indentation or markers (code blocks, blockquotes)
See docs/RICH_TEXT_DESIGN.md for the complete styling specification and advanced examples.
Output:
- Object containing:
outgoing_id: ID of the created OutgoingMessagesubject: Subject linesender: Sender email addressto_recipients: Array of To recipient addressescc_recipients: Array of CC recipient addressesbcc_recipients: Array of BCC recipient addressesmessage: Confirmation messagewarning: (optional) Warning if some recipients couldn't be added
Important Notes:
- The OutgoingMessage only exists in memory while Mail.app is running
- For persistent drafts that survive Mail.app restart, use
create_reply_draftor save the draft after creation - The message is NOT sent automatically - manual sending required
- Default format is Markdown (rich text enabled by default)
- Plain text content works as Markdown with no special characters (renders as single paragraph)
- Use
content_format: "plain"to explicitly bypass Markdown parsing - Rich text rendering errors fail immediately with clear error messages (no silent fallback to plain text)
Note on Permissions & Service Restart: After upgrading, macOS may prompt you to re-grant Automation and Accessibility permissions to the new binary. If features like "Get Selected Messages" or "Create Reply Draft" stop working, please re-enable these permissions in System Settings > Privacy & Security. You may also need to restart the service for the changes to take effect.
When you upgrade via Homebrew, the launchd service will automatically restart with the new version:
brew upgrade mail-mcpThe upgrade process:
- Downloads and installs the new version
- Updates the symlink at
/opt/homebrew/bin/mail-mcpto point to the new version - If a launchd service exists, automatically recreates it with the new binary while preserving your settings:
- Port (if customized)
- Host (if customized)
- Debug flag (if enabled)
- RunAtLoad setting (automatic startup behavior)
- No manual intervention required
Note: The upgrade preserves all your custom settings by parsing the existing plist and recreating the service with the same configuration.
If you installed manually (via binary download or Go install), you'll need to restart the launchd service after upgrading:
# After upgrading the binary
mail-mcp launchd createThis will recreate the service with the new binary path.
# Step 1: Stop the service (whichever method you used)
brew services stop mail-mcp
# OR
mail-mcp launchd remove
# Step 2: Uninstall the package
brew uninstall mail-mcp
# Step 3 (Optional): Remove logs
# If you used brew services:
rm $(brew --prefix)/var/log/mail-mcp.*
# If you used mail-mcp launchd create:
rm -r ~/Library/Logs/com.github.dastrobu.mail-mcp/Why this order matters: The launchd remove command needs the binary to properly unload and remove the service. If you uninstall first, you'll need to manually remove the plist file.
If you already uninstalled without removing the service:
# Manually remove the plist and unload the service
launchctl unload ~/Library/LaunchAgents/com.github.dastrobu.mail-mcp.plist
rm ~/Library/LaunchAgents/com.github.dastrobu.mail-mcp.plistIf you installed manually, remove the launchd service first, then delete the binary:
# Remove the launchd service
mail-mcp launchd remove
# Remove the binary (adjust path as needed)
sudo rm /usr/local/bin/mail-mcp
# Optionally remove logs
rm -r ~/Library/Logs/com.github.dastrobu.mail-mcp/- Go: Main server implementation using the MCP Go SDK
- JXA (JavaScript for Automation): Scripts embedded in the binary for Mail.app interaction
- Dual Transport Support: HTTP (recommended) and STDIO transports for flexible deployment
All JXA scripts are embedded at compile time using //go:embed, making the server a single, self-contained binary.
make buildInstall pre-commit hooks that run go fmt:
make install-hooksmake fmtThe Table of Contents is auto-generated using doctoc. To update it after making changes to section headings:
# Using make
make doctoc
# Or directly with npx
npx doctoc --maxlevel 3 README.md --github --title "## Table of Contents"The TOC is wrapped in special comments (<!-- START doctoc --> ... <!-- END doctoc -->) and should never be edited manually. The --maxlevel 3 flag limits the TOC to main sections (h2 headings only) for better readability.
Note: The pre-commit hook automatically runs doctoc when README.md is staged, so the TOC stays in sync with section changes.
make cleanThe server provides detailed error messages including:
- Script errors with clear descriptions
- Missing data with descriptive errors
- Invalid parameters with usage hints
- Argument context for debugging
- macOS only: Relies on Mail.app and JXA
- Mail.app required: Mail.app must be running
- Attachment MIME types: Not available due to Mail.app API limitations
The move to the Accessibility-based pasting strategy has resolved many previous JXA-related constraints.
- Tables: Markdown tables are currently rendered as plain text. Native table support is planned.
- Images: Inline images are not currently supported; please use the standard Mail.app attachment feature.
- Dark Mode: Mail.app automatically adapts the colors of pasted HTML content to match your current system theme (Light or Dark).
Previously documented limitations regarding Strikethrough and Links are now resolved—they are rendered as native, functional Mail.app elements.
MIT License - see LICENSE file for details