apple-mail-mcp 2.19.8 → 2.19.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +386 -343
- package/build/cli.js +79 -3
- package/build/index.js +205 -4
- package/docs/IMAP-SETUP.md +44 -42
- package/docs/THREAT-MODEL.md +13 -12
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -96,16 +96,19 @@ Two more hosts can run the same `apple-mail` MCP server (`npx -y apple-mail-mcp`
|
|
|
96
96
|
```
|
|
97
97
|
|
|
98
98
|
Restart your Hermes session afterward so the tools load.
|
|
99
|
+
|
|
99
100
|
- **[Antigravity](https://antigravity.google/)** (Google) — add the server entry from [`.antigravity-plugin/mcp_config.json`](https://github.com/sweetrb/apple-mail-mcp/blob/main/.antigravity-plugin/mcp_config.json) to `~/.gemini/config/mcp_config.json` (or via Antigravity's MCP settings).
|
|
100
101
|
|
|
101
102
|
### Manual Installation
|
|
102
103
|
|
|
103
104
|
**1. Install the server:**
|
|
105
|
+
|
|
104
106
|
```bash
|
|
105
107
|
npm install -g apple-mail-mcp
|
|
106
108
|
```
|
|
107
109
|
|
|
108
110
|
**2. Add to Claude Desktop** (`~/Library/Application Support/Claude/claude_desktop_config.json`):
|
|
111
|
+
|
|
109
112
|
```json
|
|
110
113
|
{
|
|
111
114
|
"mcpServers": {
|
|
@@ -118,6 +121,7 @@ npm install -g apple-mail-mcp
|
|
|
118
121
|
```
|
|
119
122
|
|
|
120
123
|
**3. Restart Claude Desktop** and start using natural language:
|
|
124
|
+
|
|
121
125
|
```
|
|
122
126
|
"Show me my unread emails"
|
|
123
127
|
```
|
|
@@ -154,55 +158,55 @@ tool, and troubleshooting. Verify any time by running the **`doctor`** tool.
|
|
|
154
158
|
|
|
155
159
|
### Messages
|
|
156
160
|
|
|
157
|
-
| Feature
|
|
158
|
-
|
|
159
|
-
| **List Messages**
|
|
160
|
-
| **Search Messages**
|
|
161
|
-
| **Read Messages**
|
|
162
|
-
| **Read Headers**
|
|
163
|
-
| **Send Email**
|
|
164
|
-
| **Send Serial Email** | Mail merge — send personalized emails to a list of recipients with {{placeholder}} support
|
|
165
|
-
| **Create Draft**
|
|
166
|
-
| **Reply**
|
|
167
|
-
| **Forward**
|
|
168
|
-
| **Get Thread**
|
|
169
|
-
| **Mark Read/Unread**
|
|
170
|
-
| **Flag/Unflag**
|
|
171
|
-
| **Delete Messages**
|
|
172
|
-
| **Move Messages**
|
|
173
|
-
| **List Attachments**
|
|
174
|
-
| **Save Attachment**
|
|
175
|
-
| **Fetch Attachment**
|
|
161
|
+
| Feature | Description |
|
|
162
|
+
| --------------------- | ---------------------------------------------------------------------------------------------------------------------------------------- |
|
|
163
|
+
| **List Messages** | List messages with pagination, sender filter, date display |
|
|
164
|
+
| **Search Messages** | Search by sender, subject, content, date range, read/flagged status — across all accounts |
|
|
165
|
+
| **Read Messages** | Get full email content (plain text or HTML) |
|
|
166
|
+
| **Read Headers** | Get a message's raw RFC 5322 headers — the author's `Date:`, Message-ID, threading ids, `Received:` trace — without downloading the body |
|
|
167
|
+
| **Send Email** | Compose and send new emails (attach by file path or inline base64 content) |
|
|
168
|
+
| **Send Serial Email** | Mail merge — send personalized emails to a list of recipients with {{placeholder}} support |
|
|
169
|
+
| **Create Draft** | Save emails to Drafts folder (attach by file path or inline base64 content) |
|
|
170
|
+
| **Reply** | Reply to messages (with reply-all support) |
|
|
171
|
+
| **Forward** | Forward messages to new recipients |
|
|
172
|
+
| **Get Thread** | Group a conversation by normalized subject (across AppleScript or IMAP) |
|
|
173
|
+
| **Mark Read/Unread** | Change read status (single or batch) |
|
|
174
|
+
| **Flag/Unflag** | Flag or unflag messages (single or batch) |
|
|
175
|
+
| **Delete Messages** | Move messages to trash (single or batch) |
|
|
176
|
+
| **Move Messages** | Organize into mailboxes (single or batch) |
|
|
177
|
+
| **List Attachments** | View attachment metadata (name, type, size) |
|
|
178
|
+
| **Save Attachment** | Save attachments to disk |
|
|
179
|
+
| **Fetch Attachment** | Get an attachment's bytes as base64 (no disk write) |
|
|
176
180
|
|
|
177
181
|
Read/list/get tools also return **structured JSON** (`structuredContent`) alongside the text, so agents can consume results without parsing prose.
|
|
178
182
|
|
|
179
183
|
### Mailbox & Account Management
|
|
180
184
|
|
|
181
|
-
| Feature
|
|
182
|
-
|
|
183
|
-
| **List Mailboxes**
|
|
184
|
-
| **Create/Delete/Rename Mailbox** | Full mailbox lifecycle management
|
|
185
|
-
| **List Accounts**
|
|
186
|
-
| **Unread Count**
|
|
185
|
+
| Feature | Description |
|
|
186
|
+
| -------------------------------- | ------------------------------------------- |
|
|
187
|
+
| **List Mailboxes** | Show all folders with message/unread counts |
|
|
188
|
+
| **Create/Delete/Rename Mailbox** | Full mailbox lifecycle management |
|
|
189
|
+
| **List Accounts** | Show configured accounts |
|
|
190
|
+
| **Unread Count** | Get unread counts per mailbox |
|
|
187
191
|
|
|
188
192
|
### Rules, Contacts & Templates
|
|
189
193
|
|
|
190
|
-
| Feature
|
|
191
|
-
|
|
192
|
-
| **List Rules**
|
|
193
|
-
| **Enable/Disable Rules** | Toggle mail rules on or off
|
|
194
|
-
| **Create/Delete Rules**
|
|
195
|
-
| **Search Contacts**
|
|
196
|
-
| **Email Templates**
|
|
194
|
+
| Feature | Description |
|
|
195
|
+
| ------------------------ | ---------------------------------------------------------------------------------------- |
|
|
196
|
+
| **List Rules** | View all mail rules and their enabled status |
|
|
197
|
+
| **Enable/Disable Rules** | Toggle mail rules on or off |
|
|
198
|
+
| **Create/Delete Rules** | Create rules with conditions + actions, or delete by name |
|
|
199
|
+
| **Search Contacts** | Look up contacts from Contacts.app by name |
|
|
200
|
+
| **Email Templates** | Save, list, use, and delete reusable email templates (persisted to disk across restarts) |
|
|
197
201
|
|
|
198
202
|
### Diagnostics
|
|
199
203
|
|
|
200
|
-
| Feature
|
|
201
|
-
|
|
202
|
-
| **Health Check**
|
|
203
|
-
| **Doctor**
|
|
204
|
-
| **Statistics**
|
|
205
|
-
| **Sync Status**
|
|
204
|
+
| Feature | Description |
|
|
205
|
+
| ------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
206
|
+
| **Health Check** | Verify Mail.app connectivity |
|
|
207
|
+
| **Doctor** | Diagnose Mail permission, account state, and each IMAP/SMTP backend with actionable messages |
|
|
208
|
+
| **Statistics** | Message and unread counts per account, recently received stats |
|
|
209
|
+
| **Sync Status** | Check if Mail.app is actively syncing |
|
|
206
210
|
| **Effect reconciliation** | Every delete/move reports what it actually did to the mailbox (`countDelta`), and warns when more messages left than were operated on — see [Auditing destructive operations](#auditing-destructive-operations) |
|
|
207
211
|
|
|
208
212
|
### MCP resources & prompts
|
|
@@ -223,18 +227,19 @@ This section documents all available tools. AI agents should use these tool name
|
|
|
223
227
|
|
|
224
228
|
Search for messages matching criteria. Searches all accounts by default.
|
|
225
229
|
|
|
226
|
-
| Parameter
|
|
227
|
-
|
|
228
|
-
| `query`
|
|
229
|
-
| `
|
|
230
|
-
| `
|
|
231
|
-
| `
|
|
232
|
-
| `
|
|
233
|
-
| `
|
|
234
|
-
| `
|
|
235
|
-
| `
|
|
236
|
-
| `
|
|
237
|
-
| `
|
|
230
|
+
| Parameter | Type | Required | Description |
|
|
231
|
+
| ----------- | ------- | -------- | ----------------------------------------------------------------------------------------------------------------------------------------- |
|
|
232
|
+
| `query` | string | No | Text to search in subject/sender |
|
|
233
|
+
| `body` | string | No | Text to search in the message body. IMAP backend only (server-side `BODY` search); AppleScript-only accounts are reported as not searched |
|
|
234
|
+
| `from` | string | No | Filter by sender email address |
|
|
235
|
+
| `subject` | string | No | Filter by subject line |
|
|
236
|
+
| `mailbox` | string | No | Mailbox to search in (omit to search all mailboxes) |
|
|
237
|
+
| `account` | string | No | Account to search in (omit to search all accounts) |
|
|
238
|
+
| `isRead` | boolean | No | Filter by read status |
|
|
239
|
+
| `isFlagged` | boolean | No | Filter by flagged status |
|
|
240
|
+
| `dateFrom` | string | No | Start date filter (e.g., "January 1, 2026") |
|
|
241
|
+
| `dateTo` | string | No | End date filter (e.g., "March 1, 2026") |
|
|
242
|
+
| `limit` | number | No | Max results, 1–500 (default: 50) |
|
|
238
243
|
|
|
239
244
|
**Returns:** List of matching messages with ID, date, subject, sender, and read state.
|
|
240
245
|
|
|
@@ -272,16 +277,16 @@ to disable the guard and attempt every mailbox regardless of size).
|
|
|
272
277
|
|
|
273
278
|
**Coverage diagnostics (structured fields).** The warning above is prose for a
|
|
274
279
|
human reader; the same information is also returned as structured fields on
|
|
275
|
-
`search-messages` and `list-messages`, so a caller can tell
|
|
276
|
-
apart from
|
|
280
|
+
`search-messages` and `list-messages`, so a caller can tell _"nothing matched"_
|
|
281
|
+
apart from _"I did not look everywhere"_ without parsing the text:
|
|
277
282
|
|
|
278
|
-
| Field
|
|
279
|
-
|
|
280
|
-
| `partial`
|
|
281
|
-
| `skippedLargeMailboxes` | string[] | Mailboxes never scanned because their message count exceeded `APPLE_MAIL_MAX_SEARCH_MAILBOX`, formatted `"Account / Mailbox (count)"` — e.g. `"iCloud / Archive (90694)"`.
|
|
282
|
-
| `notSearchedMailboxes`
|
|
283
|
-
| `timedOutAccounts`
|
|
284
|
-
| `failedMailboxes`
|
|
283
|
+
| Field | Type | Meaning |
|
|
284
|
+
| ----------------------- | -------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
285
|
+
| `partial` | boolean | Coverage was incomplete — **the result is not a confirmed "no such mail"**. True whenever any field below is non-empty. |
|
|
286
|
+
| `skippedLargeMailboxes` | string[] | Mailboxes never scanned because their message count exceeded `APPLE_MAIL_MAX_SEARCH_MAILBOX`, formatted `"Account / Mailbox (count)"` — e.g. `"iCloud / Archive (90694)"`. |
|
|
287
|
+
| `notSearchedMailboxes` | string[] | Mailboxes that _were_ reached but timed out or errored mid-scan, formatted `"Account / Mailbox"`. Also carries the IMAP path's `failedMailboxes`. |
|
|
288
|
+
| `timedOutAccounts` | string[] | Accounts whose whole-account AppleScript was killed by the per-account time budget — nothing from that account was searched. |
|
|
289
|
+
| `failedMailboxes` | string[] | IMAP-backend mailboxes that errored. These are merged into `notSearchedMailboxes` as well; read that field unless you need to attribute the failure to the IMAP path specifically. |
|
|
285
290
|
|
|
286
291
|
Treat a non-empty `skippedLargeMailboxes` as actionable rather than
|
|
287
292
|
informational: re-run scoped to the named mailbox with a `dateFrom`/`dateTo`
|
|
@@ -295,12 +300,12 @@ coverage was complete.
|
|
|
295
300
|
|
|
296
301
|
Get the full content of a message.
|
|
297
302
|
|
|
298
|
-
| Parameter
|
|
299
|
-
|
|
300
|
-
| `id`
|
|
301
|
-
| `preferHtml` | boolean | No
|
|
302
|
-
| `mailbox`
|
|
303
|
-
| `account`
|
|
303
|
+
| Parameter | Type | Required | Description |
|
|
304
|
+
| ------------ | ------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
305
|
+
| `id` | string | Yes | Message ID |
|
|
306
|
+
| `preferHtml` | boolean | No | Return HTML source instead of plain text |
|
|
307
|
+
| `mailbox` | string | No | Mailbox holding the message (e.g. `"Sent Items"`). With `account`, opens that mailbox directly instead of scanning every mailbox — this is the fix for timeouts on large folders |
|
|
308
|
+
| `account` | string | No | Account holding the message. Pair with `mailbox` to skip the cross-mailbox scan |
|
|
304
309
|
|
|
305
310
|
**Returns:** Subject line and message body (plain text by default, HTML if `preferHtml` is true and HTML content is available). `structuredContent` also carries `rfcMessageId` and, since 2.19.0, two dates: `dateSent` (the message's `Date:` header — Mail's `date sent`) and `dateReceived` (arrival in the mailbox — Mail's `date received` / IMAP `INTERNALDATE`). They differ legitimately by transit time; when they differ by **years**, the mailbox was migrated or re-imported and the arrival timestamp was reset — trust `dateSent` for chronology ([#224](https://github.com/sweetrb/apple-mail-mcp/issues/224)). Since 2.19.6 `dateSent` is **omitted** when it is more than 7 days later than `dateReceived`: a message cannot be sent after it arrived, and Mail.app substitutes a timestamp of its own for a `Date:` header it cannot parse ([#234](https://github.com/sweetrb/apple-mail-mcp/issues/234)). `isHtml` reports what was actually returned — a message with no `text/plain` part returns its HTML part with `isHtml: true`. Bodies are decoded by each part's declared `charset`, falling back to windows-1252 for bytes that are not valid UTF-8.
|
|
306
311
|
|
|
@@ -317,15 +322,36 @@ Get the full content of a message.
|
|
|
317
322
|
|
|
318
323
|
Return a message's raw RFC 5322 header block — without fetching the body or any attachment — plus the parsed fields chronological and threading work needs. Added in 2.19.0 for mailboxes whose arrival timestamps were reset by a migration ([#224](https://github.com/sweetrb/apple-mail-mcp/issues/224)): the `Date:` header is the author's send time and survives such moves; `INTERNALDATE` / `date received` does not.
|
|
319
324
|
|
|
320
|
-
| Parameter | Type
|
|
321
|
-
|
|
322
|
-
| `id`
|
|
323
|
-
| `mailbox` | string | No
|
|
324
|
-
| `account` | string | No
|
|
325
|
+
| Parameter | Type | Required | Description |
|
|
326
|
+
| --------- | ------ | -------- | ---------------------------------------------------------------------------------------------------------- |
|
|
327
|
+
| `id` | string | Yes | Message ID (numeric or `imap:…`) |
|
|
328
|
+
| `mailbox` | string | No | Mailbox holding the message. With `account`, opens that mailbox directly instead of scanning every mailbox |
|
|
329
|
+
| `account` | string | No | Account holding the message. Pair with `mailbox` to skip the cross-mailbox scan |
|
|
325
330
|
|
|
326
331
|
**Returns:** The raw header block as text. `structuredContent` carries `raw`, every header as ordered `headers[]` (`{name, value}`, folded lines joined, duplicates such as `Received:` kept in wire order, values left RFC 2047-encoded), `headerCount`, and the decoded key fields: `date` (ISO 8601, from the `Date:` header), `dateHeader` (verbatim), `dateReceived` (mailbox arrival time — IMAP `INTERNALDATE` or Mail's `date received`), `messageId`, `subject`, `from`, `to`, `cc`, `replyTo`, `inReplyTo`, `references[]` and `received[]` (first entry = last hop). Fields the message does not carry are omitted. `backend` says which backend read the block (`"imap"` or `"applescript"`), and `warnings[]` appears when a malformed block was repaired ([#234](https://github.com/sweetrb/apple-mail-mcp/issues/234)).
|
|
327
332
|
|
|
328
|
-
**Backends:** an `imap:` id reads the first 64 KiB of `BODY.PEEK[]` over IMAP and cuts the header block out of it (cheap even for a 20 MB message). That is deliberate: iCloud rewrites 8-bit header bytes to `*` in ENVELOPE and `BODY[HEADER]`, and only `BODY[]` returns them as stored; each line is decoded as UTF-8, falling back to windows-1252, so a raw latin-1 display name survives. A header block larger than the window falls back to `BODY.PEEK[HEADER]`. A numeric id reads Mail's `all headers` property over AppleScript, with the same mailbox-scoped fast path as `get-message`. ⚠️ That property is Mail's own rendering, not the stored bytes: for a `Date:` value Mail cannot parse it drops the value and joins the next header onto the name (`Date: Subject: …`). The tool splits that back apart, reports the date as absent rather than as a Subject string, and says so in `warnings[]`; the `imap:` id for the same message has the real `Date:`.
|
|
333
|
+
**Backends (`get-message-headers`):** an `imap:` id reads the first 64 KiB of `BODY.PEEK[]` over IMAP and cuts the header block out of it (cheap even for a 20 MB message). That is deliberate: iCloud rewrites 8-bit header bytes to `*` in ENVELOPE and `BODY[HEADER]`, and only `BODY[]` returns them as stored; each line is decoded as UTF-8, falling back to windows-1252, so a raw latin-1 display name survives. A header block larger than the window falls back to `BODY.PEEK[HEADER]`. A numeric id reads Mail's `all headers` property over AppleScript, with the same mailbox-scoped fast path as `get-message`. ⚠️ That property is Mail's own rendering, not the stored bytes: for a `Date:` value Mail cannot parse it drops the value and joins the next header onto the name (`Date: Subject: …`). The tool splits that back apart, reports the date as absent rather than as a Subject string, and says so in `warnings[]`; the `imap:` id for the same message has the real `Date:`.
|
|
334
|
+
|
|
335
|
+
---
|
|
336
|
+
|
|
337
|
+
#### `get-message-rfc822`
|
|
338
|
+
|
|
339
|
+
Acquire a message's **complete original RFC 822 bytes exactly as the IMAP server stores them**, together with the IMAP identity that links the copy back to its source ([#244](https://github.com/sweetrb/apple-mail-mcp/issues/244)). Built for archival, forensic review and evidence preservation: nothing is decoded, charset-converted, line-ending-normalized or MIME-re-serialized, and the SHA-256 is computed over exactly the bytes returned. **IMAP-only** — it needs an `imap:` id, i.e. an IMAP-configured account ([docs/IMAP-SETUP.md](docs/IMAP-SETUP.md)). There is no AppleScript path on purpose: Mail's bridge exposes its own rendering of a message, not the stored bytes.
|
|
340
|
+
|
|
341
|
+
| Parameter | Type | Required | Description |
|
|
342
|
+
| ---------- | ------ | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
343
|
+
| `id` | string | Yes | An `imap:` message id (numeric Mail.app ids are refused with an explanation) |
|
|
344
|
+
| `savePath` | string | No | Directory inside the configured allowed roots. Writes the bytes to a **new** `.eml` there instead of returning them inline, and raises the size ceiling to 25 MiB |
|
|
345
|
+
| `fileName` | string | No | File name to use with `savePath` (no path separators, no `..`). Default `<account>-<mailbox>-uidv<UIDVALIDITY>-uid<UID>.eml` |
|
|
346
|
+
| `maxBytes` | number | No | Refuse — never truncate — a message larger than this many raw bytes. Default and inline maximum 6 MiB (`6291456`); with `savePath` up to 25 MiB (`26214400`) |
|
|
347
|
+
|
|
348
|
+
**Returns:** a one-line summary as text. `structuredContent` carries the acquisition record: `account`, `mailbox`, `uid`, `uidValidity` (decimal string), `internalDate` (ISO 8601 `INTERNALDATE` — arrival, not the `Date:` header), `flags[]`, `size` (the server's `RFC822.SIZE`), `bytes` (the count actually acquired and hashed), `sha256` (hex, over exactly those bytes), `messageId` (from `ENVELOPE`, for convenience — the authoritative copy is in the bytes), `readMethod` (the IMAP commands issued), `backend: "imap"` and `warnings[]` — plus **`contentBase64`** in inline mode or **`savedPath`** in file mode. The base64 is carried **only** in `structuredContent`; the text block never repeats it.
|
|
349
|
+
|
|
350
|
+
**Read-only by construction.** The mailbox is opened with `EXAMINE` and the body fetched with `BODY.PEEK[]`, so `\Seen` is not set and no `STORE`, `COPY`, `MOVE`, `APPEND` or `EXPUNGE` is issued. No Mail.app, AppleScript or osascript is involved. The only side effect is the optional file, created with `wx` (an existing file or symlink is refused, never overwritten) and mode `0600`, and only inside the allowed roots — the same policy as `save-attachment`.
|
|
351
|
+
|
|
352
|
+
**Sizes.** The inline ceiling exists because the MCP stdio transport drops any single message over 10 MB with no error text: 6 MiB of raw bytes is 8 MiB of base64. `maxBytes` is a ceiling, not a cut — an oversize message is refused with its `RFC822.SIZE`, so you can choose between raising `maxBytes` (inline, up to 6 MiB) and `savePath` (up to 25 MiB). `warnings[]` reports an `RFC822.SIZE` that disagrees with the byte count actually received (the hash still covers what was received) and a server that reported no `UIDVALIDITY`.
|
|
353
|
+
|
|
354
|
+
**Verifying a derivative.** `shasum -a 256 file.eml` must equal `sha256`. `account` + `mailbox` + `uidValidity` + `uid` is the durable pointer back to the source message — a UID on its own is meaningful only for one `UIDVALIDITY`, which the server may change (typically on a mailbox rebuild).
|
|
329
355
|
|
|
330
356
|
---
|
|
331
357
|
|
|
@@ -333,14 +359,14 @@ Return a message's raw RFC 5322 header block — without fetching the body or an
|
|
|
333
359
|
|
|
334
360
|
List messages in a mailbox.
|
|
335
361
|
|
|
336
|
-
| Parameter
|
|
337
|
-
|
|
338
|
-
| `mailbox`
|
|
339
|
-
| `account`
|
|
340
|
-
| `limit`
|
|
341
|
-
| `offset`
|
|
342
|
-
| `from`
|
|
343
|
-
| `unreadOnly` | boolean | No
|
|
362
|
+
| Parameter | Type | Required | Description |
|
|
363
|
+
| ------------ | ------- | -------- | ------------------------------------------------ |
|
|
364
|
+
| `mailbox` | string | No | Mailbox name (omit to list from all mailboxes) |
|
|
365
|
+
| `account` | string | No | Account name |
|
|
366
|
+
| `limit` | number | No | Max messages, 1–500 (default: 50) |
|
|
367
|
+
| `offset` | number | No | Number of messages to skip, ≥ 0 (for pagination) |
|
|
368
|
+
| `from` | string | No | Filter by sender email address or name |
|
|
369
|
+
| `unreadOnly` | boolean | No | Only show unread messages |
|
|
344
370
|
|
|
345
371
|
**Returns:** List of messages with ID, date, subject, and sender.
|
|
346
372
|
|
|
@@ -367,18 +393,19 @@ Send a new email immediately.
|
|
|
367
393
|
|
|
368
394
|
**⚠️ Safety:** Sends real mail immediately and cannot be unsent. Confirm the recipients, subject, and body with the user before calling.
|
|
369
395
|
|
|
370
|
-
| Parameter
|
|
371
|
-
|
|
372
|
-
| `to`
|
|
373
|
-
| `subject`
|
|
374
|
-
| `body`
|
|
375
|
-
| `cc`
|
|
376
|
-
| `bcc`
|
|
377
|
-
| `account`
|
|
378
|
-
| `attachments` | (string \| {filename, contentBase64})[] | No
|
|
379
|
-
| `transport`
|
|
396
|
+
| Parameter | Type | Required | Description |
|
|
397
|
+
| ------------- | --------------------------------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
398
|
+
| `to` | string[] | Yes | Recipient addresses |
|
|
399
|
+
| `subject` | string | Yes | Email subject |
|
|
400
|
+
| `body` | string | Yes | Email body (plain text) |
|
|
401
|
+
| `cc` | string[] | No | CC recipients |
|
|
402
|
+
| `bcc` | string[] | No | BCC recipients |
|
|
403
|
+
| `account` | string | No | Mail.app account label, or an email-form SMTP From override. An SMTP override must match `APPLE_MAIL_MCP_SMTP_USER`, `APPLE_MAIL_MCP_SMTP_FROM`, or an address in `APPLE_MAIL_MCP_SMTP_ALLOWED_FROM` |
|
|
404
|
+
| `attachments` | (string \| {filename, contentBase64})[] | No | Up to 20 attachments: absolute file paths inside the configured read roots (e.g., `"/Users/me/Documents/report.pdf"`) and/or inline `{filename, contentBase64}` objects up to 25 MiB decoded each |
|
|
405
|
+
| `transport` | `"applescript"` \| `"smtp"` | No | Send transport. If omitted, **SMTP is used automatically when configured** (otherwise AppleScript). Pass `"smtp"` to require clean MIME, or `"applescript"` to force the Mail.app path — see [SMTP transport](#smtp-transport) |
|
|
380
406
|
|
|
381
407
|
**Example:**
|
|
408
|
+
|
|
382
409
|
```json
|
|
383
410
|
{
|
|
384
411
|
"to": ["colleague@company.com"],
|
|
@@ -415,7 +442,7 @@ Two differences to know when SMTP is auto-preferred:
|
|
|
415
442
|
Use `transport: "applescript"` if you want Mail.app itself to file the copy.
|
|
416
443
|
- **`account` is a From override, not account selection.** Over SMTP, `account`
|
|
417
444
|
is used as the From address only when it is an email address; a Mail.app
|
|
418
|
-
account
|
|
445
|
+
account _label_ (e.g. `"Work"`) can't select an account over SMTP, so a call
|
|
419
446
|
that passes one is left on the AppleScript path automatically. To force
|
|
420
447
|
account selection, pass `transport: "applescript"` explicitly. For sender
|
|
421
448
|
safety, an email-form override must match the SMTP login user, the configured
|
|
@@ -436,18 +463,18 @@ trusted isolated server or test fixture; it disables the upgrade requirement and
|
|
|
436
463
|
can expose credentials and message content. The server emits a warning when it
|
|
437
464
|
is used. Keep the default unset.
|
|
438
465
|
|
|
439
|
-
| Variable
|
|
440
|
-
|
|
441
|
-
| `APPLE_MAIL_MCP_SMTP_HOST`
|
|
442
|
-
| `APPLE_MAIL_MCP_SMTP_USER`
|
|
443
|
-
| `APPLE_MAIL_MCP_SMTP_PORT`
|
|
444
|
-
| `APPLE_MAIL_MCP_SMTP_SECURE`
|
|
445
|
-
| `APPLE_MAIL_MCP_SMTP_ALLOW_PLAINTEXT`
|
|
446
|
-
| `APPLE_MAIL_MCP_SMTP_FROM`
|
|
447
|
-
| `APPLE_MAIL_MCP_SMTP_ALLOWED_FROM`
|
|
448
|
-
| `APPLE_MAIL_MCP_SMTP_PASSWORD`
|
|
449
|
-
| `APPLE_MAIL_MCP_SMTP_KEYCHAIN_SERVICE` | No
|
|
450
|
-
| `APPLE_MAIL_MCP_SMTP_KEYCHAIN_ACCOUNT` | No
|
|
466
|
+
| Variable | Required | Default | Description |
|
|
467
|
+
| -------------------------------------- | -------- | --------------------------- | -------------------------------------------------------------------------------------------- |
|
|
468
|
+
| `APPLE_MAIL_MCP_SMTP_HOST` | Yes | — | SMTP server hostname (e.g. `smtp.fastmail.com`) |
|
|
469
|
+
| `APPLE_MAIL_MCP_SMTP_USER` | Yes | — | SMTP username |
|
|
470
|
+
| `APPLE_MAIL_MCP_SMTP_PORT` | No | `465` if secure, else `587` | SMTP port |
|
|
471
|
+
| `APPLE_MAIL_MCP_SMTP_SECURE` | No | `false` | `true` for implicit TLS (port 465); otherwise STARTTLS |
|
|
472
|
+
| `APPLE_MAIL_MCP_SMTP_ALLOW_PLAINTEXT` | No | `0` | Set `1` only for an explicitly trusted plaintext test/server; otherwise STARTTLS is required |
|
|
473
|
+
| `APPLE_MAIL_MCP_SMTP_FROM` | No | = user | From address |
|
|
474
|
+
| `APPLE_MAIL_MCP_SMTP_ALLOWED_FROM` | No | — | Comma-separated sender aliases permitted as per-message From overrides |
|
|
475
|
+
| `APPLE_MAIL_MCP_SMTP_PASSWORD` | No | — | Password (if set, used instead of the Keychain) |
|
|
476
|
+
| `APPLE_MAIL_MCP_SMTP_KEYCHAIN_SERVICE` | No | = host | Keychain item service/server name |
|
|
477
|
+
| `APPLE_MAIL_MCP_SMTP_KEYCHAIN_ACCOUNT` | No | = user | Keychain item account |
|
|
451
478
|
|
|
452
479
|
Store the password in the Keychain once (an app-specific password for Gmail/
|
|
453
480
|
iCloud). A generic-password item with an explicit service name keeps it from
|
|
@@ -467,6 +494,7 @@ security add-generic-password -s apple-mail-mcp-smtp -a you@gmail.com -w
|
|
|
467
494
|
|
|
468
495
|
Once the env vars are set, a plain `send-email` (no `transport`) already goes
|
|
469
496
|
out clean:
|
|
497
|
+
|
|
470
498
|
```json
|
|
471
499
|
{
|
|
472
500
|
"to": ["colleague@company.com"],
|
|
@@ -513,7 +541,7 @@ What routes to IMAP when an account is IMAP-configured:
|
|
|
513
541
|
- **Folder ops:** `create-mailbox`, `rename-mailbox`, `delete-mailbox` — IMAP's `CREATE`/`RENAME`/`DELETE` succeed on the iCloud/Gmail/Workspace/Exchange mailboxes Mail.app's AppleScript bridge can't touch (#42).
|
|
514
542
|
- **Message mutations:** `mark-as-read`/`unread`, `flag-message`/`unflag-message`, `move-message`, `delete-message`.
|
|
515
543
|
- **Batch mutations (2.1):** `batch-mark-as-read`/`unread`, `batch-flag`/`unflag-messages`, `batch-move-messages`, `batch-delete-messages` — `imap:` ids are grouped by mailbox and applied as a single `UID STORE`/`UID MOVE`; numeric ids in the same batch still use AppleScript.
|
|
516
|
-
- **Counts & stats (2.1):** `get-unread-count` and `list-mailboxes` use `STATUS`; `get-mail-stats` uses `STATUS` + `SEARCH SINCE` — authoritative and fast even on huge mailboxes. As of v2.6.0 these prefer IMAP whenever it's configured (see
|
|
544
|
+
- **Counts & stats (2.1):** `get-unread-count` and `list-mailboxes` use `STATUS`; `get-mail-stats` uses `STATUS` + `SEARCH SINCE` — authoritative and fast even on huge mailboxes. As of v2.6.0 these prefer IMAP whenever it's configured (see _Read routing_ below), merging across accounts when no `account` is given.
|
|
517
545
|
- **Attachments (2.1):** `list-attachments`, `save-attachment`, `fetch-attachment` use `BODYSTRUCTURE` + `FETCH BODY[part]` for `imap:` ids — faster and able to see MIME-embedded attachments AppleScript misses.
|
|
518
546
|
- **Threading (2.1):** `get-thread` links a conversation via `References`/`Message-ID` (`HEADER SEARCH`) for an `imap:` seed, falling back to subject grouping otherwise.
|
|
519
547
|
|
|
@@ -533,9 +561,9 @@ matching `account` is passed. There are three cases:
|
|
|
533
561
|
- **Explicit IMAP account** — single-account IMAP (fast server-side path).
|
|
534
562
|
- **Explicit non-IMAP account** — AppleScript (that account isn't on IMAP).
|
|
535
563
|
- **No `account` given** — **merge across all accounts**: the query fans out over
|
|
536
|
-
|
|
564
|
+
_every_ configured IMAP account, **and** AppleScript runs **only for the
|
|
537
565
|
accounts no IMAP config covers** (the account list is partitioned — accounts
|
|
538
|
-
already served by IMAP are
|
|
566
|
+
already served by IMAP are _not_ re-scanned via AppleScript). If every Mail
|
|
539
567
|
account is IMAP-configured, AppleScript is skipped entirely. The results are
|
|
540
568
|
merged so no account is dropped. Message lists still de-duplicate as a safety
|
|
541
569
|
net (preferring the IMAP copy, which carries the round-trippable `imap:` id) and
|
|
@@ -554,7 +582,7 @@ matching `account` is passed. There are three cases:
|
|
|
554
582
|
one `SEARCH` + a bounded `FETCH` per mailbox over the pooled IMAP
|
|
555
583
|
connection — pin a `mailbox` to skip the fan-out when you already know
|
|
556
584
|
where to look. `list-messages` (no query) still defaults an omitted
|
|
557
|
-
mailbox to `INBOX` on every provider — only unscoped
|
|
585
|
+
mailbox to `INBOX` on every provider — only unscoped _search_ scans the
|
|
558
586
|
whole account.
|
|
559
587
|
|
|
560
588
|
If IMAP is **not** configured at all, every read behaves exactly as before
|
|
@@ -562,21 +590,21 @@ If IMAP is **not** configured at all, every read behaves exactly as before
|
|
|
562
590
|
`delete-mailbox`, `rename-mailbox`) remain conservative — they route to IMAP only
|
|
563
591
|
for an explicitly-named IMAP account, never on an omitted account.
|
|
564
592
|
|
|
565
|
-
| Variable
|
|
566
|
-
|
|
567
|
-
| `APPLE_MAIL_MCP_IMAP_USER`
|
|
568
|
-
| `APPLE_MAIL_MCP_IMAP_ACCOUNT`
|
|
569
|
-
| `APPLE_MAIL_MCP_IMAP_HOST`
|
|
570
|
-
| `APPLE_MAIL_MCP_IMAP_PORT`
|
|
571
|
-
| `APPLE_MAIL_MCP_IMAP_ALLOW_PLAINTEXT`
|
|
572
|
-
| `APPLE_MAIL_MCP_IMAP_PASSWORD`
|
|
573
|
-
| `APPLE_MAIL_MCP_IMAP_KEYCHAIN_SERVICE` | No
|
|
574
|
-
| `APPLE_MAIL_MCP_IMAP_KEYCHAIN_ACCOUNT` | No
|
|
575
|
-
| `APPLE_MAIL_MCP_IMAP_ACCOUNTS`
|
|
576
|
-
| `APPLE_MAIL_MCP_IMAP_IDLE`
|
|
577
|
-
| `APPLE_MAIL_MCP_IMAP_IDLE_MS`
|
|
578
|
-
| `APPLE_MAIL_MCP_STATS_BUDGET_MS`
|
|
579
|
-
| `APPLE_MAIL_MCP_STATS_DEADLINE_MS`
|
|
593
|
+
| Variable | Required | Default | Description |
|
|
594
|
+
| -------------------------------------- | -------- | ---------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
595
|
+
| `APPLE_MAIL_MCP_IMAP_USER` | Yes | — | Login address; setting it enables IMAP |
|
|
596
|
+
| `APPLE_MAIL_MCP_IMAP_ACCOUNT` | No | = user | Mail account name to match for routing |
|
|
597
|
+
| `APPLE_MAIL_MCP_IMAP_HOST` | No | `imap.gmail.com` | IMAP server hostname |
|
|
598
|
+
| `APPLE_MAIL_MCP_IMAP_PORT` | No | `993` | IMAP port (993 = implicit TLS) |
|
|
599
|
+
| `APPLE_MAIL_MCP_IMAP_ALLOW_PLAINTEXT` | No | `0` | Set `1` only for an explicitly trusted plaintext test/server; otherwise STARTTLS is required |
|
|
600
|
+
| `APPLE_MAIL_MCP_IMAP_PASSWORD` | No | — | Password (if set, used instead of the Keychain) |
|
|
601
|
+
| `APPLE_MAIL_MCP_IMAP_KEYCHAIN_SERVICE` | No | — | Keychain item service/server name |
|
|
602
|
+
| `APPLE_MAIL_MCP_IMAP_KEYCHAIN_ACCOUNT` | No | = user | Keychain item account |
|
|
603
|
+
| `APPLE_MAIL_MCP_IMAP_ACCOUNTS` | No | — | JSON array of **additional** IMAP accounts for multi-account setups (see below) |
|
|
604
|
+
| `APPLE_MAIL_MCP_IMAP_IDLE` | No | `0` | Set `1` to enable IMAP IDLE push notifications (new-mail alerts) for every configured account |
|
|
605
|
+
| `APPLE_MAIL_MCP_IMAP_IDLE_MS` | No | `30000` | Idle timeout (ms) before a pooled IMAP connection is closed (`0` = never close) |
|
|
606
|
+
| `APPLE_MAIL_MCP_STATS_BUDGET_MS` | No | `25000` | Per-account wall-clock budget for `get-mail-stats` (minimum `1000`). Raise it for very large accounts |
|
|
607
|
+
| `APPLE_MAIL_MCP_STATS_DEADLINE_MS` | No | `50000` | Overall wall-clock deadline for one `get-mail-stats` call (minimum `2000`), measured from when the request arrived and covering time queued behind other tool calls, account enumeration **and** every per-account read. Keep it below your client's request timeout |
|
|
580
608
|
|
|
581
609
|
**Multiple IMAP accounts (C2):** set `APPLE_MAIL_MCP_IMAP_ACCOUNTS` to a JSON array, e.g.
|
|
582
610
|
`[{"account":"Work","user":"me@co.com","host":"imap.co.com","keychainService":"imap.co.com"}]`.
|
|
@@ -622,9 +650,9 @@ those slots. This server keeps its footprint small:
|
|
|
622
650
|
polling every 30s, so it can't linger holding sockets after its session is gone.
|
|
623
651
|
|
|
624
652
|
The catch is **multiple concurrent instances**. A host like the Claude desktop
|
|
625
|
-
app spawns a
|
|
653
|
+
app spawns a _separate_ set of MCP servers per open conversation (and respawns
|
|
626
654
|
them after a crash), so the footprint is **per instance × accounts**. With IDLE
|
|
627
|
-
off, an idle instance trends to 0 connections; with many
|
|
655
|
+
off, an idle instance trends to 0 connections; with many _active_ conversations
|
|
628
656
|
or IDLE on, the per-account total climbs toward Gmail's 15-connection cap and can
|
|
629
657
|
starve Apple Mail of slots (→ intermittent "cannot connect"). If you hit that,
|
|
630
658
|
close idle Claude conversations, keep `APPLE_MAIL_MCP_IMAP_IDLE` off unless you
|
|
@@ -701,22 +729,23 @@ Enable it in your MCP client config alongside the IMAP settings:
|
|
|
701
729
|
|
|
702
730
|
Send individual personalized emails to a list of recipients (mail merge). Each recipient receives their own email — recipients don't see each other. Supports `{{placeholder}}` tokens in both subject and body.
|
|
703
731
|
|
|
704
|
-
| Parameter
|
|
705
|
-
|
|
706
|
-
| `recipients` | object[] | Yes
|
|
707
|
-
| `subject`
|
|
708
|
-
| `body`
|
|
709
|
-
| `account`
|
|
710
|
-
| `delayMs`
|
|
732
|
+
| Parameter | Type | Required | Description |
|
|
733
|
+
| ------------ | -------- | -------- | --------------------------------------------------- |
|
|
734
|
+
| `recipients` | object[] | Yes | List of recipients, max 100 (see below) |
|
|
735
|
+
| `subject` | string | Yes | Email subject — use `{{Key}}` for placeholders |
|
|
736
|
+
| `body` | string | Yes | Email body — use `{{Key}}` for placeholders |
|
|
737
|
+
| `account` | string | No | Send from specific account |
|
|
738
|
+
| `delayMs` | number | No | Delay between sends in ms (default: 500, max 10000) |
|
|
711
739
|
|
|
712
740
|
Each recipient object:
|
|
713
741
|
|
|
714
|
-
| Field
|
|
715
|
-
|
|
716
|
-
| `email`
|
|
717
|
-
| `variables` | object | Yes
|
|
742
|
+
| Field | Type | Required | Description |
|
|
743
|
+
| ----------- | ------ | -------- | ------------------------------------------- |
|
|
744
|
+
| `email` | string | Yes | Recipient email address |
|
|
745
|
+
| `variables` | object | Yes | Key-value pairs for placeholder replacement |
|
|
718
746
|
|
|
719
747
|
**Example:**
|
|
748
|
+
|
|
720
749
|
```json
|
|
721
750
|
{
|
|
722
751
|
"recipients": [
|
|
@@ -738,15 +767,15 @@ Each recipient object:
|
|
|
738
767
|
|
|
739
768
|
Save an email to Drafts without sending.
|
|
740
769
|
|
|
741
|
-
| Parameter
|
|
742
|
-
|
|
743
|
-
| `to`
|
|
744
|
-
| `subject`
|
|
745
|
-
| `body`
|
|
746
|
-
| `cc`
|
|
747
|
-
| `bcc`
|
|
748
|
-
| `account`
|
|
749
|
-
| `attachments` | (string \| {filename, contentBase64})[] | No
|
|
770
|
+
| Parameter | Type | Required | Description |
|
|
771
|
+
| ------------- | --------------------------------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
772
|
+
| `to` | string[] | Yes | Recipient addresses |
|
|
773
|
+
| `subject` | string | Yes | Email subject |
|
|
774
|
+
| `body` | string | Yes | Email body (plain text) |
|
|
775
|
+
| `cc` | string[] | No | CC recipients |
|
|
776
|
+
| `bcc` | string[] | No | BCC recipients |
|
|
777
|
+
| `account` | string | No | Account for draft |
|
|
778
|
+
| `attachments` | (string \| {filename, contentBase64})[] | No | Up to 20 attachments: absolute file paths inside the configured read roots and/or inline `{filename, contentBase64}` objects up to 25 MiB decoded each |
|
|
750
779
|
|
|
751
780
|
**Returns:** Confirmation that draft was created.
|
|
752
781
|
|
|
@@ -754,12 +783,12 @@ Save an email to Drafts without sending.
|
|
|
754
783
|
|
|
755
784
|
Group a conversation by normalized subject (across the AppleScript or IMAP backend).
|
|
756
785
|
|
|
757
|
-
| Parameter | Type
|
|
758
|
-
|
|
759
|
-
| `id`
|
|
760
|
-
| `account` | string | No
|
|
761
|
-
| `mailbox` | string | No
|
|
762
|
-
| `limit`
|
|
786
|
+
| Parameter | Type | Required | Description |
|
|
787
|
+
| --------- | ------ | -------- | ------------------------------------------------------ |
|
|
788
|
+
| `id` | string | Yes | A message ID in the conversation (numeric or `imap:…`) |
|
|
789
|
+
| `account` | string | No | Account to search (omit to search all) |
|
|
790
|
+
| `mailbox` | string | No | Mailbox to search (omit to search all) |
|
|
791
|
+
| `limit` | number | No | Max messages in the thread (default 50) |
|
|
763
792
|
|
|
764
793
|
**Returns:** The conversation's messages, oldest-first.
|
|
765
794
|
|
|
@@ -767,10 +796,10 @@ Group a conversation by normalized subject (across the AppleScript or IMAP backe
|
|
|
767
796
|
|
|
768
797
|
Return an attachment's bytes as base64 (the read counterpart to inline-base64 send).
|
|
769
798
|
|
|
770
|
-
| Parameter
|
|
771
|
-
|
|
772
|
-
| `id`
|
|
773
|
-
| `attachmentName` | string | Yes
|
|
799
|
+
| Parameter | Type | Required | Description |
|
|
800
|
+
| ---------------- | ------ | -------- | --------------------------------------------- |
|
|
801
|
+
| `id` | string | Yes | Message ID (numeric or `imap:…`) |
|
|
802
|
+
| `attachmentName` | string | Yes | Attachment filename (from `list-attachments`) |
|
|
774
803
|
|
|
775
804
|
**Returns:** The attachment bytes, base64-encoded (also in `structuredContent.contentBase64`).
|
|
776
805
|
|
|
@@ -780,9 +809,9 @@ Return an attachment's bytes as base64 (the read counterpart to inline-base64 se
|
|
|
780
809
|
|
|
781
810
|
Map `imap:` message IDs to their numeric Mail.app IDs, via each message's RFC 5322 `Message-ID` (the join key both backends share). Needed for the AppleScript reply/forward path; direct SMTP replies and forwards read `imap:` IDs without numeric conversion. Numeric IDs pass through unchanged.
|
|
782
811
|
|
|
783
|
-
| Parameter | Type
|
|
784
|
-
|
|
785
|
-
| `ids`
|
|
812
|
+
| Parameter | Type | Required | Description |
|
|
813
|
+
| --------- | -------- | -------- | ------------------------------------------- |
|
|
814
|
+
| `ids` | string[] | Yes | 1–100 message IDs, each numeric or `imap:…` |
|
|
786
815
|
|
|
787
816
|
**Returns:** For each input ID, its `numericId` (or `null` when it can't be resolved) and the `messageId` used, plus `count` and `resolvedCount`. The lookup scopes to the message's account and checks its INBOX first, to avoid scanning a large All Mail/Archive mailbox.
|
|
788
817
|
|
|
@@ -794,15 +823,16 @@ Map `imap:` message IDs to their numeric Mail.app IDs, via each message's RFC 53
|
|
|
794
823
|
|
|
795
824
|
Reply to an existing message.
|
|
796
825
|
|
|
797
|
-
| Parameter
|
|
798
|
-
|
|
799
|
-
| `id`
|
|
800
|
-
| `body`
|
|
801
|
-
| `replyAll`
|
|
802
|
-
| `send`
|
|
803
|
-
| `transport` | string
|
|
826
|
+
| Parameter | Type | Required | Description |
|
|
827
|
+
| ----------- | ------- | -------- | ---------------------------------------------------------------------------------------------- |
|
|
828
|
+
| `id` | string | Yes | Message ID to reply to |
|
|
829
|
+
| `body` | string | Yes | Reply body |
|
|
830
|
+
| `replyAll` | boolean | No | Reply to all recipients (default: false) |
|
|
831
|
+
| `send` | boolean | No | Send immediately (default: true, false = save as draft) |
|
|
832
|
+
| `transport` | string | No | `smtp` or `applescript`; omitted prefers configured SMTP when sending. Drafts use AppleScript. |
|
|
804
833
|
|
|
805
834
|
**Example - Reply to sender only:**
|
|
835
|
+
|
|
806
836
|
```json
|
|
807
837
|
{
|
|
808
838
|
"id": "12345",
|
|
@@ -811,6 +841,7 @@ Reply to an existing message.
|
|
|
811
841
|
```
|
|
812
842
|
|
|
813
843
|
**Example - Reply all, save as draft:**
|
|
844
|
+
|
|
814
845
|
```json
|
|
815
846
|
{
|
|
816
847
|
"id": "12345",
|
|
@@ -836,13 +867,13 @@ Success includes `transport` and, for SMTP when returned by the server, the new
|
|
|
836
867
|
|
|
837
868
|
Forward a message to new recipients.
|
|
838
869
|
|
|
839
|
-
| Parameter
|
|
840
|
-
|
|
841
|
-
| `id`
|
|
842
|
-
| `to`
|
|
843
|
-
| `body`
|
|
844
|
-
| `send`
|
|
845
|
-
| `transport` | string
|
|
870
|
+
| Parameter | Type | Required | Description |
|
|
871
|
+
| ----------- | -------- | -------- | ---------------------------------------------------------------------------------------------- |
|
|
872
|
+
| `id` | string | Yes | Message ID to forward |
|
|
873
|
+
| `to` | string[] | Yes | Recipients to forward to |
|
|
874
|
+
| `body` | string | No | Message to prepend |
|
|
875
|
+
| `send` | boolean | No | Send immediately (default: true, false = save as draft) |
|
|
876
|
+
| `transport` | string | No | `smtp` or `applescript`; omitted prefers configured SMTP when sending. Drafts use AppleScript. |
|
|
846
877
|
|
|
847
878
|
**Delivery:** uses the same source lookup, transport selection, 25 MiB source limit, account-identity check, and failure behavior as `reply-to-message`. A forward deliberately starts a new conversation, so it has no `In-Reply-To` or `References` headers. The existing plain-text forwarding behavior is unchanged: original attachments are not reattached. See [SMTP transport](#smtp-transport).
|
|
848
879
|
|
|
@@ -856,9 +887,9 @@ SMTP forwarding requires a readable plain-text original. HTML-only IMAP messages
|
|
|
856
887
|
|
|
857
888
|
Change read status of a message.
|
|
858
889
|
|
|
859
|
-
| Parameter | Type
|
|
860
|
-
|
|
861
|
-
| `id`
|
|
890
|
+
| Parameter | Type | Required | Description |
|
|
891
|
+
| --------- | ------ | -------- | ----------- |
|
|
892
|
+
| `id` | string | Yes | Message ID |
|
|
862
893
|
|
|
863
894
|
---
|
|
864
895
|
|
|
@@ -866,10 +897,10 @@ Change read status of a message.
|
|
|
866
897
|
|
|
867
898
|
Flag or unflag a message. `flag-message` optionally takes a flag **color**; `unflag-message` removes the flag entirely (which also clears any color).
|
|
868
899
|
|
|
869
|
-
| Parameter | Type
|
|
870
|
-
|
|
871
|
-
| `id`
|
|
872
|
-
| `color`
|
|
900
|
+
| Parameter | Type | Required | Description |
|
|
901
|
+
| --------- | ------ | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
902
|
+
| `id` | string | Yes | Message ID |
|
|
903
|
+
| `color` | string | No | (`flag-message` only) Flag color: `red`, `orange`, `yellow`, `green`, `blue`, `purple`, `gray` (`grey` accepted). Omit for Mail's default flag. |
|
|
873
904
|
|
|
874
905
|
**Flag colors** are an Apple Mail feature — the message's `flag index` (0 red, 1 orange, 2 yellow, 3 green, 4 blue, 5 purple, 6 gray), which is the property a Mail smart mailbox can match on. **The color is applied on both routes** (since 2.10.0): AppleScript sets the flag index directly, and for an **IMAP-routed** id (`imap:…`) the color is written as Mail.app's `$MailFlagBit0/1/2` keywords — a 3-bit field holding the same palette index. `\Flagged` on its own really is colorless, but those keywords ride alongside it in an ordinary `UID STORE`, so a smart mailbox keyed on flag color matches an IMAP-flagged message too. You do **not** need to resolve to a numeric id just to color a flag.
|
|
875
906
|
|
|
@@ -881,9 +912,9 @@ To **read** a color, the IMAP read path returns `flagColorIndex` in `structuredC
|
|
|
881
912
|
|
|
882
913
|
Delete a message (move to trash).
|
|
883
914
|
|
|
884
|
-
| Parameter | Type
|
|
885
|
-
|
|
886
|
-
| `id`
|
|
915
|
+
| Parameter | Type | Required | Description |
|
|
916
|
+
| --------- | ------ | -------- | ----------- |
|
|
917
|
+
| `id` | string | Yes | Message ID |
|
|
887
918
|
|
|
888
919
|
`structuredContent` carries `countDelta` — what the delete actually did to the
|
|
889
920
|
source mailbox. See [Auditing destructive operations](#auditing-destructive-operations).
|
|
@@ -896,11 +927,11 @@ source mailbox. See [Auditing destructive operations](#auditing-destructive-oper
|
|
|
896
927
|
|
|
897
928
|
Move a message to a different mailbox.
|
|
898
929
|
|
|
899
|
-
| Parameter | Type
|
|
900
|
-
|
|
901
|
-
| `id`
|
|
902
|
-
| `mailbox` | string | Yes
|
|
903
|
-
| `account` | string | No
|
|
930
|
+
| Parameter | Type | Required | Description |
|
|
931
|
+
| --------- | ------ | -------- | --------------------------------------------------------------------------------------------- |
|
|
932
|
+
| `id` | string | Yes | Message ID |
|
|
933
|
+
| `mailbox` | string | Yes | Destination mailbox — full path (`Work/Archive`) or a leaf name that is unique on the account |
|
|
934
|
+
| `account` | string | No | Account containing mailbox |
|
|
904
935
|
|
|
905
936
|
A destination is matched first as a full path, then as a leaf name. If a leaf
|
|
906
937
|
name matches **more than one** mailbox (e.g. `Archive` under both `Work` and
|
|
@@ -917,9 +948,9 @@ the full path. The same applies to `batch-move-messages`, `delete-mailbox` and
|
|
|
917
948
|
|
|
918
949
|
List attachments on a message.
|
|
919
950
|
|
|
920
|
-
| Parameter | Type
|
|
921
|
-
|
|
922
|
-
| `id`
|
|
951
|
+
| Parameter | Type | Required | Description |
|
|
952
|
+
| --------- | ------ | -------- | ----------- |
|
|
953
|
+
| `id` | string | Yes | Message ID |
|
|
923
954
|
|
|
924
955
|
**Returns:** List of attachments with name, MIME type, and size.
|
|
925
956
|
|
|
@@ -934,11 +965,11 @@ of overwriting an existing file. AppleScript and MIME fallback paths stage the
|
|
|
934
965
|
bytes privately, commit with an exclusive create, and leave the saved file
|
|
935
966
|
owner-readable/writable (`0600`).
|
|
936
967
|
|
|
937
|
-
| Parameter
|
|
938
|
-
|
|
939
|
-
| `id`
|
|
940
|
-
| `attachmentName` | string | Yes
|
|
941
|
-
| `savePath`
|
|
968
|
+
| Parameter | Type | Required | Description |
|
|
969
|
+
| ---------------- | ------ | -------- | -------------------------- |
|
|
970
|
+
| `id` | string | Yes | Message ID |
|
|
971
|
+
| `attachmentName` | string | Yes | Filename of the attachment |
|
|
972
|
+
| `savePath` | string | Yes | Directory to save to |
|
|
942
973
|
|
|
943
974
|
---
|
|
944
975
|
|
|
@@ -975,11 +1006,11 @@ therefore a count of messages, not of list positions.
|
|
|
975
1006
|
|
|
976
1007
|
#### `batch-delete-messages`
|
|
977
1008
|
|
|
978
|
-
| Parameter
|
|
979
|
-
|
|
980
|
-
| `ids`
|
|
981
|
-
| `sourceMailbox` | string
|
|
982
|
-
| `sourceAccount` | string
|
|
1009
|
+
| Parameter | Type | Required | Description |
|
|
1010
|
+
| --------------- | -------- | -------- | ---------------------------------------------------------------------------------------------------------------- |
|
|
1011
|
+
| `ids` | string[] | Yes | Message IDs to delete (max 100) |
|
|
1012
|
+
| `sourceMailbox` | string | No | Mailbox the **numeric** ids were listed from — pins them to it. Ignored for `imap:` ids. |
|
|
1013
|
+
| `sourceAccount` | string | No | Account the numeric ids were listed from. Required when `sourceMailbox` is supplied; on its own it pins nothing. |
|
|
983
1014
|
|
|
984
1015
|
`structuredContent` carries `countDelta` — what the batch actually did to each
|
|
985
1016
|
source mailbox. See [Auditing destructive operations](#auditing-destructive-operations).
|
|
@@ -988,33 +1019,33 @@ source mailbox. See [Auditing destructive operations](#auditing-destructive-oper
|
|
|
988
1019
|
|
|
989
1020
|
#### `batch-move-messages`
|
|
990
1021
|
|
|
991
|
-
| Parameter
|
|
992
|
-
|
|
993
|
-
| `ids`
|
|
994
|
-
| `mailbox`
|
|
995
|
-
| `account`
|
|
996
|
-
| `sourceMailbox` | string
|
|
997
|
-
| `sourceAccount` | string
|
|
1022
|
+
| Parameter | Type | Required | Description |
|
|
1023
|
+
| --------------- | -------- | -------- | ---------------------------------------------------------------------------------------------------------------- |
|
|
1024
|
+
| `ids` | string[] | Yes | Message IDs to move (max 100) |
|
|
1025
|
+
| `mailbox` | string | Yes | Destination mailbox |
|
|
1026
|
+
| `account` | string | No | Account containing mailbox |
|
|
1027
|
+
| `sourceMailbox` | string | No | Mailbox the **numeric** ids were listed from — pins them to it. Ignored for `imap:` ids. |
|
|
1028
|
+
| `sourceAccount` | string | No | Account the numeric ids were listed from. Required when `sourceMailbox` is supplied; on its own it pins nothing. |
|
|
998
1029
|
|
|
999
1030
|
`structuredContent` carries `countDelta` — what the batch actually did to each
|
|
1000
1031
|
**source** mailbox. See [Auditing destructive operations](#auditing-destructive-operations).
|
|
1001
1032
|
|
|
1002
1033
|
#### `batch-mark-as-read` / `batch-mark-as-unread`
|
|
1003
1034
|
|
|
1004
|
-
| Parameter
|
|
1005
|
-
|
|
1006
|
-
| `ids`
|
|
1007
|
-
| `sourceMailbox` | string
|
|
1008
|
-
| `sourceAccount` | string
|
|
1035
|
+
| Parameter | Type | Required | Description |
|
|
1036
|
+
| --------------- | -------- | -------- | ---------------------------------------------------------------------------------------------------------------- |
|
|
1037
|
+
| `ids` | string[] | Yes | Message IDs (max 100) |
|
|
1038
|
+
| `sourceMailbox` | string | No | Mailbox the **numeric** ids were listed from — pins them to it. Ignored for `imap:` ids. |
|
|
1039
|
+
| `sourceAccount` | string | No | Account the numeric ids were listed from. Required when `sourceMailbox` is supplied; on its own it pins nothing. |
|
|
1009
1040
|
|
|
1010
1041
|
#### `batch-flag-messages` / `batch-unflag-messages`
|
|
1011
1042
|
|
|
1012
|
-
| Parameter
|
|
1013
|
-
|
|
1014
|
-
| `ids`
|
|
1015
|
-
| `color`
|
|
1016
|
-
| `sourceMailbox` | string
|
|
1017
|
-
| `sourceAccount` | string
|
|
1043
|
+
| Parameter | Type | Required | Description |
|
|
1044
|
+
| --------------- | -------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
1045
|
+
| `ids` | string[] | Yes | Message IDs (max 100) |
|
|
1046
|
+
| `color` | string | No | (`batch-flag-messages` only) Flag color — see [`flag-message`](#flag-message--unflag-message). Applied on both routes, so a mixed batch of numeric and `imap:` ids all end up colored. |
|
|
1047
|
+
| `sourceMailbox` | string | No | Mailbox the **numeric** ids were listed from — pins them to it. Ignored for `imap:` ids. |
|
|
1048
|
+
| `sourceAccount` | string | No | Account the numeric ids were listed from. Required when `sourceMailbox` is supplied; on its own it pins nothing. |
|
|
1018
1049
|
|
|
1019
1050
|
---
|
|
1020
1051
|
|
|
@@ -1024,9 +1055,9 @@ source mailbox. See [Auditing destructive operations](#auditing-destructive-oper
|
|
|
1024
1055
|
|
|
1025
1056
|
List all mailboxes for an account.
|
|
1026
1057
|
|
|
1027
|
-
| Parameter | Type
|
|
1028
|
-
|
|
1029
|
-
| `account` | string | No
|
|
1058
|
+
| Parameter | Type | Required | Description |
|
|
1059
|
+
| --------- | ------ | -------- | ---------------------------------------------------------- |
|
|
1060
|
+
| `account` | string | No | Account to list from, or `"On My Mac"` for the local store |
|
|
1030
1061
|
|
|
1031
1062
|
**Returns:** List of mailbox **paths** (account-relative, e.g. `Archive/Inbox` for
|
|
1032
1063
|
a nested mailbox — a top-level `Inbox` stays `Inbox`) with message and unread
|
|
@@ -1066,10 +1097,10 @@ reported as ambiguous rather than silently resolving to the account copy.
|
|
|
1066
1097
|
|
|
1067
1098
|
Get unread message count.
|
|
1068
1099
|
|
|
1069
|
-
| Parameter | Type
|
|
1070
|
-
|
|
1071
|
-
| `mailbox` | string | No
|
|
1072
|
-
| `account` | string | No
|
|
1100
|
+
| Parameter | Type | Required | Description |
|
|
1101
|
+
| --------- | ------ | -------- | --------------------------------------------------- |
|
|
1102
|
+
| `mailbox` | string | No | Mailbox to check (omit for **INBOX**) |
|
|
1103
|
+
| `account` | string | No | Account to check (omit to sum each account's INBOX) |
|
|
1073
1104
|
|
|
1074
1105
|
**Returns:** The unread count for the requested scope.
|
|
1075
1106
|
|
|
@@ -1081,10 +1112,10 @@ Get unread message count.
|
|
|
1081
1112
|
|
|
1082
1113
|
Create a new mailbox.
|
|
1083
1114
|
|
|
1084
|
-
| Parameter | Type
|
|
1085
|
-
|
|
1086
|
-
| `name`
|
|
1087
|
-
| `account` | string | No
|
|
1115
|
+
| Parameter | Type | Required | Description |
|
|
1116
|
+
| --------- | ------ | -------- | -------------------- |
|
|
1117
|
+
| `name` | string | Yes | Mailbox name |
|
|
1118
|
+
| `account` | string | No | Account to create in |
|
|
1088
1119
|
|
|
1089
1120
|
---
|
|
1090
1121
|
|
|
@@ -1092,10 +1123,10 @@ Create a new mailbox.
|
|
|
1092
1123
|
|
|
1093
1124
|
Delete a mailbox.
|
|
1094
1125
|
|
|
1095
|
-
| Parameter | Type
|
|
1096
|
-
|
|
1097
|
-
| `name`
|
|
1098
|
-
| `account` | string | No
|
|
1126
|
+
| Parameter | Type | Required | Description |
|
|
1127
|
+
| --------- | ------ | -------- | -------------------------- |
|
|
1128
|
+
| `name` | string | Yes | Mailbox name |
|
|
1129
|
+
| `account` | string | No | Account containing mailbox |
|
|
1099
1130
|
|
|
1100
1131
|
**⚠️ Safety:** Destructive — deletes the mailbox and its contents. Requires explicit user confirmation; list mailboxes first to confirm the name.
|
|
1101
1132
|
|
|
@@ -1105,11 +1136,11 @@ Delete a mailbox.
|
|
|
1105
1136
|
|
|
1106
1137
|
Rename a mailbox (creates new, moves messages, deletes old).
|
|
1107
1138
|
|
|
1108
|
-
| Parameter | Type
|
|
1109
|
-
|
|
1110
|
-
| `oldName` | string | Yes
|
|
1111
|
-
| `newName` | string | Yes
|
|
1112
|
-
| `account` | string | No
|
|
1139
|
+
| Parameter | Type | Required | Description |
|
|
1140
|
+
| --------- | ------ | -------- | -------------------------- |
|
|
1141
|
+
| `oldName` | string | Yes | Current mailbox name |
|
|
1142
|
+
| `newName` | string | Yes | New mailbox name |
|
|
1143
|
+
| `account` | string | No | Account containing mailbox |
|
|
1113
1144
|
|
|
1114
1145
|
---
|
|
1115
1146
|
|
|
@@ -1135,12 +1166,12 @@ List existing smart mailboxes.
|
|
|
1135
1166
|
|
|
1136
1167
|
Create a smart mailbox with a simple contains rule.
|
|
1137
1168
|
|
|
1138
|
-
| Parameter
|
|
1139
|
-
|
|
1140
|
-
| `name`
|
|
1141
|
-
| `fromContains`
|
|
1142
|
-
| `subjectContains` | string | No
|
|
1143
|
-
| `bodyContains`
|
|
1169
|
+
| Parameter | Type | Required | Description |
|
|
1170
|
+
| ----------------- | ------ | -------- | ------------------------------ |
|
|
1171
|
+
| `name` | string | Yes | Name for the smart mailbox |
|
|
1172
|
+
| `fromContains` | string | No | Match if From contains this |
|
|
1173
|
+
| `subjectContains` | string | No | Match if Subject contains this |
|
|
1174
|
+
| `bodyContains` | string | No | Match if Body contains this |
|
|
1144
1175
|
|
|
1145
1176
|
Provide at least one of the three `*Contains` fields.
|
|
1146
1177
|
|
|
@@ -1152,9 +1183,9 @@ Provide at least one of the three `*Contains` fields.
|
|
|
1152
1183
|
|
|
1153
1184
|
Delete a smart mailbox by name.
|
|
1154
1185
|
|
|
1155
|
-
| Parameter | Type
|
|
1156
|
-
|
|
1157
|
-
| `name`
|
|
1186
|
+
| Parameter | Type | Required | Description |
|
|
1187
|
+
| --------- | ------ | -------- | ------------------ |
|
|
1188
|
+
| `name` | string | Yes | Smart mailbox name |
|
|
1158
1189
|
|
|
1159
1190
|
**⚠️ Safety:** destructive — removes the smart mailbox from `SyncedSmartMailboxes.plist` (backed up + atomic; every other smart mailbox is preserved). Not undoable in-app. Confirm the exact name with `list-smart-mailboxes` first, and quit Mail first for reliable results.
|
|
1160
1191
|
|
|
@@ -1164,11 +1195,11 @@ Delete a smart mailbox by name.
|
|
|
1164
1195
|
|
|
1165
1196
|
High-level tool: scan recent messages in your INBOXes, detect likely newsletters (volume + signals like List-Unsubscribe, noreply, repetitive subjects), and create smart mailboxes for them (names prefixed "NL: ...").
|
|
1166
1197
|
|
|
1167
|
-
| Parameter
|
|
1168
|
-
|
|
1169
|
-
| `dryRun`
|
|
1170
|
-
| `minCount` | number
|
|
1171
|
-
| `days`
|
|
1198
|
+
| Parameter | Type | Required | Description |
|
|
1199
|
+
| ---------- | ------- | -------- | ------------------------------------------ |
|
|
1200
|
+
| `dryRun` | boolean | No | Default true — only propose, do not create |
|
|
1201
|
+
| `minCount` | number | No | Min messages from a sender (default 3) |
|
|
1202
|
+
| `days` | number | No | Lookback window in days (default 90) |
|
|
1172
1203
|
|
|
1173
1204
|
Defaults to a **safe dry run** that only proposes. Pass `dryRun: false` to actually create the smart mailboxes for newsletters cluttering your Inbox.
|
|
1174
1205
|
|
|
@@ -1204,9 +1235,9 @@ List all mail rules.
|
|
|
1204
1235
|
|
|
1205
1236
|
Enable or disable a mail rule.
|
|
1206
1237
|
|
|
1207
|
-
| Parameter | Type
|
|
1208
|
-
|
|
1209
|
-
| `name`
|
|
1238
|
+
| Parameter | Type | Required | Description |
|
|
1239
|
+
| --------- | ------ | -------- | ----------- |
|
|
1240
|
+
| `name` | string | Yes | Rule name |
|
|
1210
1241
|
|
|
1211
1242
|
---
|
|
1212
1243
|
|
|
@@ -1214,13 +1245,13 @@ Enable or disable a mail rule.
|
|
|
1214
1245
|
|
|
1215
1246
|
Create a Mail rule with one or more conditions and actions.
|
|
1216
1247
|
|
|
1217
|
-
| Parameter
|
|
1218
|
-
|
|
1219
|
-
| `name`
|
|
1220
|
-
| `conditions` | object[] | Yes
|
|
1221
|
-
| `actions`
|
|
1222
|
-
| `matchAll`
|
|
1223
|
-
| `enabled`
|
|
1248
|
+
| Parameter | Type | Required | Description |
|
|
1249
|
+
| ------------ | -------- | -------- | ------------------------------------------------------------- |
|
|
1250
|
+
| `name` | string | Yes | Rule name (must be unique) |
|
|
1251
|
+
| `conditions` | object[] | Yes | One or more `{field, operator, value}` (see below) |
|
|
1252
|
+
| `actions` | object | Yes | At least one of `markRead`, `markFlagged`, `delete`, `moveTo` |
|
|
1253
|
+
| `matchAll` | boolean | No | `true` (default) = all conditions must match; `false` = any |
|
|
1254
|
+
| `enabled` | boolean | No | Whether the rule is enabled on creation (default `false`) |
|
|
1224
1255
|
|
|
1225
1256
|
Each condition is `{ field, operator, value }` where `field` is one of `from`, `to`, `cc`, `subject`, `content` and `operator` is one of `contains`, `notContains`, `equals`, `beginsWith`, `endsWith`. Actions: `markRead` / `markFlagged` / `delete` (booleans), `moveTo` (mailbox name) with optional `moveToAccount`.
|
|
1226
1257
|
|
|
@@ -1230,6 +1261,7 @@ then call `enable-rule` explicitly when the rule is approved. Set
|
|
|
1230
1261
|
`enabled: true` only when immediate activation is deliberate.
|
|
1231
1262
|
|
|
1232
1263
|
**Example:**
|
|
1264
|
+
|
|
1233
1265
|
```json
|
|
1234
1266
|
{
|
|
1235
1267
|
"name": "Newsletters",
|
|
@@ -1244,9 +1276,9 @@ then call `enable-rule` explicitly when the rule is approved. Set
|
|
|
1244
1276
|
|
|
1245
1277
|
Delete a mail rule by name.
|
|
1246
1278
|
|
|
1247
|
-
| Parameter | Type
|
|
1248
|
-
|
|
1249
|
-
| `name`
|
|
1279
|
+
| Parameter | Type | Required | Description |
|
|
1280
|
+
| --------- | ------ | -------- | ----------- |
|
|
1281
|
+
| `name` | string | Yes | Rule name |
|
|
1250
1282
|
|
|
1251
1283
|
**⚠️ Safety:** Destructive. Requires explicit user confirmation; list rules first to confirm the name.
|
|
1252
1284
|
|
|
@@ -1260,9 +1292,9 @@ Search the macOS Contacts database by name, organization, nickname, or email sub
|
|
|
1260
1292
|
|
|
1261
1293
|
Since 2.8.7 this reads the AddressBook SQLite files directly rather than driving Contacts.app over AppleScript, so **Contacts.app need not be running and no Automation grant is involved** — but the Node runtime does need **Full Disk Access**, and **Node 22.5+** (see [Requirements](#requirements)). Without either, the tool returns an empty list rather than an error.
|
|
1262
1294
|
|
|
1263
|
-
| Parameter | Type
|
|
1264
|
-
|
|
1265
|
-
| `query`
|
|
1295
|
+
| Parameter | Type | Required | Description |
|
|
1296
|
+
| --------- | ------ | -------- | --------------------------------------------------------------------------------- |
|
|
1297
|
+
| `query` | string | Yes | Substring matched against full name, organization, nickname, or any email address |
|
|
1266
1298
|
|
|
1267
1299
|
**Returns:** List of contacts with name, email addresses, and phone numbers. Results are **not** truncated — a broad query returns every match.
|
|
1268
1300
|
|
|
@@ -1276,14 +1308,14 @@ Email templates are **persisted to disk** so they survive server restarts, store
|
|
|
1276
1308
|
|
|
1277
1309
|
Save or update an email template.
|
|
1278
1310
|
|
|
1279
|
-
| Parameter | Type
|
|
1280
|
-
|
|
1281
|
-
| `name`
|
|
1282
|
-
| `subject` | string
|
|
1283
|
-
| `body`
|
|
1284
|
-
| `to`
|
|
1285
|
-
| `cc`
|
|
1286
|
-
| `id`
|
|
1311
|
+
| Parameter | Type | Required | Description |
|
|
1312
|
+
| --------- | -------- | -------- | -------------------------- |
|
|
1313
|
+
| `name` | string | Yes | Template name |
|
|
1314
|
+
| `subject` | string | Yes | Default subject line |
|
|
1315
|
+
| `body` | string | Yes | Template body |
|
|
1316
|
+
| `to` | string[] | No | Default recipients |
|
|
1317
|
+
| `cc` | string[] | No | Default CC recipients |
|
|
1318
|
+
| `id` | string | No | Template ID (for updating) |
|
|
1287
1319
|
|
|
1288
1320
|
---
|
|
1289
1321
|
|
|
@@ -1299,9 +1331,9 @@ List all saved templates.
|
|
|
1299
1331
|
|
|
1300
1332
|
Get a template by ID.
|
|
1301
1333
|
|
|
1302
|
-
| Parameter | Type
|
|
1303
|
-
|
|
1304
|
-
| `id`
|
|
1334
|
+
| Parameter | Type | Required | Description |
|
|
1335
|
+
| --------- | ------ | -------- | ----------- |
|
|
1336
|
+
| `id` | string | Yes | Template ID |
|
|
1305
1337
|
|
|
1306
1338
|
---
|
|
1307
1339
|
|
|
@@ -1309,9 +1341,9 @@ Get a template by ID.
|
|
|
1309
1341
|
|
|
1310
1342
|
Delete a template.
|
|
1311
1343
|
|
|
1312
|
-
| Parameter | Type
|
|
1313
|
-
|
|
1314
|
-
| `id`
|
|
1344
|
+
| Parameter | Type | Required | Description |
|
|
1345
|
+
| --------- | ------ | -------- | ----------- |
|
|
1346
|
+
| `id` | string | Yes | Template ID |
|
|
1315
1347
|
|
|
1316
1348
|
**⚠️ Safety:** Destructive — removes the template from the on-disk store. Requires explicit user confirmation; list templates first to confirm the id.
|
|
1317
1349
|
|
|
@@ -1321,13 +1353,13 @@ Delete a template.
|
|
|
1321
1353
|
|
|
1322
1354
|
Create a draft from a template, with optional overrides.
|
|
1323
1355
|
|
|
1324
|
-
| Parameter | Type
|
|
1325
|
-
|
|
1326
|
-
| `id`
|
|
1327
|
-
| `to`
|
|
1328
|
-
| `cc`
|
|
1329
|
-
| `subject` | string
|
|
1330
|
-
| `body`
|
|
1356
|
+
| Parameter | Type | Required | Description |
|
|
1357
|
+
| --------- | -------- | -------- | ------------------- |
|
|
1358
|
+
| `id` | string | Yes | Template ID |
|
|
1359
|
+
| `to` | string[] | No | Override recipients |
|
|
1360
|
+
| `cc` | string[] | No | Override CC |
|
|
1361
|
+
| `subject` | string | No | Override subject |
|
|
1362
|
+
| `body` | string | No | Override body |
|
|
1331
1363
|
|
|
1332
1364
|
---
|
|
1333
1365
|
|
|
@@ -1357,9 +1389,9 @@ Run a full setup diagnostic: Mail.app automation permission, account state (flag
|
|
|
1357
1389
|
|
|
1358
1390
|
Get mail statistics.
|
|
1359
1391
|
|
|
1360
|
-
| Parameter | Type
|
|
1361
|
-
|
|
1362
|
-
| `account` | string | No
|
|
1392
|
+
| Parameter | Type | Required | Description |
|
|
1393
|
+
| --------- | ------ | -------- | ----------------------------------------------------------------------------------------------------------------------- |
|
|
1394
|
+
| `account` | string | No | Limit to one account (uses fast IMAP `STATUS` when that account is IMAP-configured). Omit to merge across all accounts. |
|
|
1363
1395
|
|
|
1364
1396
|
**Returns:** Total and per-account message/unread counts, plus recently received stats (24h, 7d, 30d). The scoped IMAP path also returns a `perMailbox` breakdown.
|
|
1365
1397
|
|
|
@@ -1442,20 +1474,20 @@ returns the comparison in `structuredContent`:
|
|
|
1442
1474
|
|
|
1443
1475
|
`status` is deliberately not a pass/fail flag:
|
|
1444
1476
|
|
|
1445
|
-
| `status`
|
|
1446
|
-
|
|
1447
|
-
| `match`
|
|
1448
|
-
| `over`
|
|
1449
|
-
| `unknown` | No comparison this server is willing to assert. `unknownReason` says which of four situations produced it. | No
|
|
1477
|
+
| `status` | Meaning | Warns? |
|
|
1478
|
+
| --------- | ---------------------------------------------------------------------------------------------------------- | ------- |
|
|
1479
|
+
| `match` | Exactly as many messages left the mailbox as the operation acted on. | No |
|
|
1480
|
+
| `over` | **More** left than were operated on. Messages are unaccounted for. | **Yes** |
|
|
1481
|
+
| `unknown` | No comparison this server is willing to assert. `unknownReason` says which of four situations produced it. | No |
|
|
1450
1482
|
|
|
1451
|
-
`unknownReason` distinguishes four cases that are
|
|
1483
|
+
`unknownReason` distinguishes four cases that are _not_ interchangeable:
|
|
1452
1484
|
|
|
1453
|
-
| `unknownReason`
|
|
1454
|
-
|
|
1455
|
-
| `count-unreadable`
|
|
1456
|
-
| `no-expectation`
|
|
1485
|
+
| `unknownReason` | Meaning |
|
|
1486
|
+
| -------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
1487
|
+
| `count-unreadable` | Mail would not report a count at all (`before`/`after` null). |
|
|
1488
|
+
| `no-expectation` | No expectation is predictable, so no comparison exists — a move whose destination **is** the source mailbox. |
|
|
1457
1489
|
| `count-did-not-move` | The count did not move. On a store that flags deletions instead of removing them this is the **ordinary, correct** reading for an operation that fully succeeded. |
|
|
1458
|
-
| `count-partial`
|
|
1490
|
+
| `count-partial` | The count moved, but by less than the operation accounted for. A flag-only store cannot produce this, which is why it is worth telling apart. |
|
|
1459
1491
|
|
|
1460
1492
|
> **`under` was removed in 2.11.0.** It used to mean "fewer left than expected"
|
|
1461
1493
|
> and was documented as routine. Field evidence retired it — see
|
|
@@ -1478,8 +1510,8 @@ trusted:
|
|
|
1478
1510
|
|
|
1479
1511
|
**Concurrent departure is the benign cause to rule out first**, and the warning
|
|
1480
1512
|
text says so. What the asymmetry argument actually buys is the other half:
|
|
1481
|
-
concurrent
|
|
1482
|
-
mid-operation
|
|
1513
|
+
concurrent _arrivals_ cannot produce `over`, because a message arriving
|
|
1514
|
+
mid-operation _raises_ the after-count and biases the reading short.
|
|
1483
1515
|
That is why `over` is the interesting direction — a strong signal, not a proof.
|
|
1484
1516
|
|
|
1485
1517
|
Setting `APPLE_MAIL_MCP_AUDIT_LOG` is what settles which one you have: the
|
|
@@ -1496,7 +1528,7 @@ On iCloud, @scottstern0325 ran the check that settled this: for two batches
|
|
|
1496
1528
|
reporting `observed: 0`, the messages were located **in Trash**, matched by
|
|
1497
1529
|
`date received` + sender against the audit log's pre-image. The deletes had
|
|
1498
1530
|
happened. Mail's count had not caught up. Across four readings — 0 of 4, 0 of 1
|
|
1499
|
-
(a
|
|
1531
|
+
(a _single-id_ delete), 15 of 16, and 14 of 15 — the shortfall bore no relation
|
|
1500
1532
|
to batch size, which is what a lagging count looks like and not what a
|
|
1501
1533
|
store-behaviour rule looks like.
|
|
1502
1534
|
|
|
@@ -1513,14 +1545,14 @@ Two things follow:
|
|
|
1513
1545
|
— a warning that fires on every ordinary Gmail delete would be ignored exactly
|
|
1514
1546
|
when it matters.
|
|
1515
1547
|
- **To confirm where messages went, match them at the destination by `date
|
|
1516
|
-
|
|
1548
|
+
received` plus sender — not by the numeric ids you passed.** Ids are renumbered
|
|
1517
1549
|
by the move and do not survive it.
|
|
1518
1550
|
|
|
1519
1551
|
**Removed in 2.11.0: the "reported success with no observed effect" warning.**
|
|
1520
1552
|
Shipped in 2.10.30, it fired when the count was flat, the snapshot read cleanly
|
|
1521
1553
|
and nothing had disappeared. Its premise was that the snapshot corroborated the
|
|
1522
1554
|
count — but both are read back-to-back in the same script, and the record that
|
|
1523
|
-
prompted it turns out to have had
|
|
1555
|
+
prompted it turns out to have had _both_ instruments stale at once. It therefore
|
|
1524
1556
|
fired on stores that had done exactly what they were asked. It is gone rather
|
|
1525
1557
|
than narrowed; `over` is the only surviving assertion.
|
|
1526
1558
|
|
|
@@ -1546,6 +1578,7 @@ Three more honesty rules:
|
|
|
1546
1578
|
clean — but it does not flag it either. The collateral diff still names anything
|
|
1547
1579
|
that vanished, so **enable `APPLE_MAIL_MCP_AUDIT_LOG` if you need coverage for
|
|
1548
1580
|
same-mailbox moves.**
|
|
1581
|
+
|
|
1549
1582
|
- A **repeated id is one message**. The batch tools operate on each distinct id
|
|
1550
1583
|
once and return one result per distinct id, so `success` counts messages rather
|
|
1551
1584
|
than list positions — and `expected` stays comparable with the mailbox instead
|
|
@@ -1580,10 +1613,10 @@ Single-message tools carry a post-condition check instead. `delete-message` and
|
|
|
1580
1613
|
}
|
|
1581
1614
|
```
|
|
1582
1615
|
|
|
1583
|
-
| `verdict`
|
|
1584
|
-
|
|
1585
|
-
| `verified`
|
|
1586
|
-
| `unverified` | The server **accepted** the command and nothing could confirm the effect. Populates `why`.
|
|
1616
|
+
| `verdict` | Meaning |
|
|
1617
|
+
| ------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
1618
|
+
| `verified` | The effect was **observed** — either the server's UIDPLUS `COPYUID` named the message's new UID in the destination, or the UID is no longer in the source mailbox. |
|
|
1619
|
+
| `unverified` | The server **accepted** the command and nothing could confirm the effect. Populates `why`. |
|
|
1587
1620
|
|
|
1588
1621
|
`unverified` is **not a failure** and must not be rendered as one — it means
|
|
1589
1622
|
"accepted, no observation either way". It is reported rather than hidden because
|
|
@@ -1594,12 +1627,12 @@ store can legitimately keep a message visible in an all-mail view after a move.
|
|
|
1594
1627
|
|
|
1595
1628
|
### `APPLE_MAIL_MCP_AUDIT_LOG` — opt-in forensic log
|
|
1596
1629
|
|
|
1597
|
-
| Variable
|
|
1598
|
-
|
|
1599
|
-
| `APPLE_MAIL_MCP_AUDIT_LOG`
|
|
1600
|
-
| `APPLE_MAIL_MCP_AUDIT_SUBJECTS`
|
|
1601
|
-
| `APPLE_MAIL_MCP_AUDIT_SNAPSHOT_MAX`
|
|
1602
|
-
| `APPLE_MAIL_MCP_AUDIT_SNAPSHOT_CHUNK` | `250`
|
|
1630
|
+
| Variable | Default | Description |
|
|
1631
|
+
| ------------------------------------- | ------- | ------------------------------------------------------------------------------------------------------------------------------- |
|
|
1632
|
+
| `APPLE_MAIL_MCP_AUDIT_LOG` | _(off)_ | Absolute path to an NDJSON file. Setting it enables the audit log **and** the collateral diff below |
|
|
1633
|
+
| `APPLE_MAIL_MCP_AUDIT_SUBJECTS` | `0` | Set `1` to also record message **subjects**. Separate, deliberate second opt-in — see Privacy |
|
|
1634
|
+
| `APPLE_MAIL_MCP_AUDIT_SNAPSHOT_MAX` | `2000` | Skip the collateral snapshot for mailboxes larger than this many messages. `0` disables the snapshot entirely |
|
|
1635
|
+
| `APPLE_MAIL_MCP_AUDIT_SNAPSHOT_CHUNK` | `250` | How many messages the collateral snapshot reads from Mail per request. Lower it if Mail declines slices on a very large mailbox |
|
|
1603
1636
|
|
|
1604
1637
|
When set, each destructive operation appends **one JSON object per line**
|
|
1605
1638
|
containing: timestamp, tool name, server version, the arguments it was called
|
|
@@ -1681,12 +1714,12 @@ When a slice still will not read, the snapshot is reported as `partial` and it
|
|
|
1681
1714
|
Each half of the diff is withheld when the snapshot that could **refute** it has
|
|
1682
1715
|
a hole, because a wrong name here is worse than a missing one:
|
|
1683
1716
|
|
|
1684
|
-
| Hole in
|
|
1685
|
-
|
|
1686
|
-
| neither (`"ok"`) | reported
|
|
1687
|
-
| `before`
|
|
1688
|
-
| `after`
|
|
1689
|
-
| both
|
|
1717
|
+
| Hole in | `disappeared` / `unrequested` | `appeared` |
|
|
1718
|
+
| ---------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- | ---------- |
|
|
1719
|
+
| neither (`"ok"`) | reported | reported |
|
|
1720
|
+
| `before` | reported (an undercount — a message never read before cannot be missed after) | withheld |
|
|
1721
|
+
| `after` | **withheld** — a message absent from a partial `after` may merely be unread, and naming it would present an innocent message as evidence of data loss | reported |
|
|
1722
|
+
| both | withheld | withheld |
|
|
1690
1723
|
|
|
1691
1724
|
**An absent field means "not computable", never "empty".** Check
|
|
1692
1725
|
`"snapshot": "ok"` before reading `disappeared` as a clean bill of health.
|
|
@@ -1702,7 +1735,7 @@ a hole, because a wrong name here is worse than a missing one:
|
|
|
1702
1735
|
### Privacy, and what the file costs you
|
|
1703
1736
|
|
|
1704
1737
|
- **Default:** identifying metadata only — Message-ID, date, mailbox, account,
|
|
1705
|
-
numeric id. Enough to say
|
|
1738
|
+
numeric id. Enough to say _which_ message, nothing about what it says.
|
|
1706
1739
|
- **Subjects are behind their own opt-in** (`APPLE_MAIL_MCP_AUDIT_SUBJECTS=1`)
|
|
1707
1740
|
because a subject line is frequently the entire sensitive payload, and it is
|
|
1708
1741
|
not needed to diagnose #155.
|
|
@@ -1817,6 +1850,7 @@ The repo ships prebuilt, dependency-free `build/index.js` and `build/cli.js` bun
|
|
|
1817
1850
|
> You can also install straight from GitHub with `npm install -g github:sweetrb/apple-mail-mcp`, but that builds from source (requires pnpm) — prefer the registry package above.
|
|
1818
1851
|
|
|
1819
1852
|
If installed from source, use this configuration:
|
|
1853
|
+
|
|
1820
1854
|
```json
|
|
1821
1855
|
{
|
|
1822
1856
|
"mcpServers": {
|
|
@@ -1838,11 +1872,11 @@ The entrypoint is written as:
|
|
|
1838
1872
|
"args": ["${CLAUDE_PROJECT_DIR:-.}/build/index.js"]
|
|
1839
1873
|
```
|
|
1840
1874
|
|
|
1841
|
-
`CLAUDE_PROJECT_DIR` is the variable Claude Code injects into a project/user-scoped server's environment, and it resolves to the repo root. **You must launch `claude` from inside the repo** for this to work — the bare `.` fallback is only a last resort and is
|
|
1875
|
+
`CLAUDE_PROJECT_DIR` is the variable Claude Code injects into a project/user-scoped server's environment, and it resolves to the repo root. **You must launch `claude` from inside the repo** for this to work — the bare `.` fallback is only a last resort and is _not_ reliable, because it resolves against the launching process's working directory, not the repo.
|
|
1842
1876
|
|
|
1843
|
-
> **Why not `${CLAUDE_PLUGIN_ROOT}`?** `CLAUDE_PLUGIN_ROOT` is set **only** for marketplace plugin installs, never for a project-scope clone, so it can't drive the clone workflow. Conversely, a plugin install can't use `CLAUDE_PROJECT_DIR` (in a plugin, that points at the
|
|
1877
|
+
> **Why not `${CLAUDE_PLUGIN_ROOT}`?** `CLAUDE_PLUGIN_ROOT` is set **only** for marketplace plugin installs, never for a project-scope clone, so it can't drive the clone workflow. Conversely, a plugin install can't use `CLAUDE_PROJECT_DIR` (in a plugin, that points at the _user's_ project, not the plugin's own directory). Claude Code does **not** support nested defaults like `${CLAUDE_PLUGIN_ROOT:-${CLAUDE_PROJECT_DIR:-.}}`, so a single entrypoint string cannot serve both contexts. The two distribution paths are therefore decoupled: the **plugin** carries its own MCP config in `.claude-plugin/plugin.json` (using `${CLAUDE_PLUGIN_ROOT}`), while the root `.mcp.json` is dedicated to the **clone** workflow (using `${CLAUDE_PROJECT_DIR:-.}`). Because `plugin.json` declares its own `mcpServers`, the plugin does not also auto-load the root `.mcp.json`, so there is no double-registration.
|
|
1844
1878
|
|
|
1845
|
-
> **Heads-up on scope precedence:** project-scope (`.mcp.json`) outranks user-scope. If you
|
|
1879
|
+
> **Heads-up on scope precedence:** project-scope (`.mcp.json`) outranks user-scope. If you _also_ have an `apple-mail` entry registered at user scope (e.g. an absolute path in `~/.claude.json`), the project-scope entry wins and the user-scope one is ignored entirely. Pick one — for local development on this repo, the project-scope `.mcp.json` is the intended source. To pin a specific local build instead, register it at **local** scope (`claude mcp add apple-mail -s local -- node /abs/path/build/index.js`), which outranks project scope.
|
|
1846
1880
|
|
|
1847
1881
|
---
|
|
1848
1882
|
|
|
@@ -1867,20 +1901,20 @@ The entrypoint is written as:
|
|
|
1867
1901
|
|
|
1868
1902
|
## Known Limitations
|
|
1869
1903
|
|
|
1870
|
-
| Limitation
|
|
1871
|
-
|
|
1872
|
-
| macOS only
|
|
1873
|
-
| MCP `send-email` is plain-text
|
|
1874
|
-
| Attachment read path restrictions
|
|
1875
|
-
| Smart mailboxes need Mail quit
|
|
1876
|
-
| Very large mailboxes not searchable
|
|
1877
|
-
| Can't delete/rename server-side mailboxes or mutate drafts
|
|
1878
|
-
| Message ID format
|
|
1879
|
-
| Batch size cap
|
|
1880
|
-
| Date filter format
|
|
1881
|
-
| Attachment save path restrictions
|
|
1882
|
-
| Attachment count limit
|
|
1883
|
-
| IMAP attachment fetch size
|
|
1904
|
+
| Limitation | Reason |
|
|
1905
|
+
| ---------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
1906
|
+
| macOS only | Apple Mail and AppleScript are macOS-specific |
|
|
1907
|
+
| MCP `send-email` is plain-text | The `send-email` tool sends plain text (reading HTML content is supported). To send HTML, use the bundled `apple-mail-send` CLI with `--html-body-file` (sends `multipart/alternative` via SMTP) |
|
|
1908
|
+
| Attachment read path restrictions | Outbound file attachments must use full absolute paths inside the default home-directory, `/Volumes`, or temporary roots, except hidden files and protected credential/configuration locations. Set `APPLE_MAIL_MCP_ATTACHMENT_READ_ROOTS` to add an explicit absolute root for another deliberate location; symlink escapes are rejected. |
|
|
1909
|
+
| Smart mailboxes need Mail quit | Smart mailboxes are supported (see [Smart Mailbox Operations](#smart-mailbox-operations-intelligente-postfächer)), but `create-`/`delete-smart-mailbox` edit `SyncedSmartMailboxes.plist` directly — a running Mail may not show a new one until relaunched, and can overwrite plist edits it didn't make. Quit Mail first. Reading them needs Full Disk Access for the Node runtime |
|
|
1910
|
+
| Very large mailboxes not searchable _via AppleScript_ | Apple Mail's AppleScript bridge times out on mailboxes with tens of thousands of messages, so unscoped `search-messages` skips mailboxes above `APPLE_MAIL_MAX_SEARCH_MAILBOX` (default 5000) and reports them as a partial result. Scope with `mailbox` + a date window — or configure the [IMAP backend](#imap-backend--opt-in), which searches these server-side in well under a second. ([#24](https://github.com/sweetrb/apple-mail-mcp/issues/24)) |
|
|
1911
|
+
| Can't delete/rename server-side mailboxes or mutate drafts _via AppleScript_ | Mail.app's AppleScript bridge can only `delete`/`rename` **local "On My Mac"** mailboxes and cannot delete/move drafts — it throws `AppleEvent handler failed` for IMAP/Gmail/Workspace/iCloud/Exchange mailboxes (the GUI can do it). Without IMAP configured, `delete-mailbox`/`rename-mailbox`/`delete-message`/`move-message` return a clear "do it in Mail.app directly" error instead of a generic failure. With the [IMAP backend](#imap-backend--opt-in) configured for the account, these operations run via IMAP and succeed. ([#42](https://github.com/sweetrb/apple-mail-mcp/issues/42)) |
|
|
1912
|
+
| Message ID format | Message IDs must be numeric (AppleScript ids) or `imap:…` tokens from the IMAP read path (validated by schema) |
|
|
1913
|
+
| Batch size cap | Batch operations are limited to 100 messages per request |
|
|
1914
|
+
| Date filter format | Date filters must be valid parseable dates (e.g., "January 1, 2026" or "2026-03-15"); bare numbers or non-date strings are rejected |
|
|
1915
|
+
| Attachment save path restrictions | `save-attachment` only allows saving to home directory, `/tmp`, `/private/tmp`, and `/Volumes`; path traversal is blocked |
|
|
1916
|
+
| Attachment count limit | `send-email` and `create-draft` accept a maximum of 20 file attachments |
|
|
1917
|
+
| IMAP attachment fetch size | `fetch-attachment` / `save-attachment` over IMAP refuse a part larger than 25 MiB — rejected before download when the server declares the size, and the stream is cut off at the limit when it does not |
|
|
1884
1918
|
|
|
1885
1919
|
### Mail.app `<blockquote>` wrapping on macOS 15+ (workaround in v1.6.0)
|
|
1886
1920
|
|
|
@@ -1946,11 +1980,13 @@ In a JSON string literal, `\\` — two characters — denotes **one** literal ba
|
|
|
1946
1980
|
## Troubleshooting
|
|
1947
1981
|
|
|
1948
1982
|
### "Mail.app not responding"
|
|
1983
|
+
|
|
1949
1984
|
- Ensure Mail.app is not frozen
|
|
1950
1985
|
- Try opening Mail.app manually
|
|
1951
1986
|
- Restart the MCP server
|
|
1952
1987
|
|
|
1953
1988
|
### "Permission denied"
|
|
1989
|
+
|
|
1954
1990
|
- macOS needs automation permission
|
|
1955
1991
|
- Go to System Settings > Privacy & Security > Automation
|
|
1956
1992
|
- Ensure your terminal/Claude has permission to control Mail
|
|
@@ -1958,17 +1994,19 @@ In a JSON string literal, `\\` — two characters — denotes **one** literal ba
|
|
|
1958
1994
|
language — `Not authorised to send Apple events to Mail. (-1743)` on en-GB/en-AU/en-IE,
|
|
1959
1995
|
and fully translated on fr/de/es. Before **v2.17.10** the server didn't recognise
|
|
1960
1996
|
those spellings, so `health-check`/`doctor` reported `permissions: ok` and then blamed
|
|
1961
|
-
missing accounts (
|
|
1997
|
+
missing accounts (_"No Mail accounts found. Set up an account in Mail.app first."_).
|
|
1962
1998
|
If you see that on a Mac whose Mail accounts are already configured, upgrade and
|
|
1963
1999
|
re-run `doctor`.
|
|
1964
2000
|
|
|
1965
2001
|
### "Message not found"
|
|
2002
|
+
|
|
1966
2003
|
- Message may have been deleted or moved
|
|
1967
2004
|
- Message IDs change if the message is moved between mailboxes
|
|
1968
2005
|
- Use `search-messages` to find the current message ID
|
|
1969
2006
|
|
|
1970
2007
|
### "... is present in more than one mailbox"
|
|
1971
|
-
|
|
2008
|
+
|
|
2009
|
+
- A bare numeric ID identifies a message only _within a mailbox_, and a label store (Gmail, iCloud)
|
|
1972
2010
|
reports the same message under the same ID in `INBOX`, `Important` and `All Mail` at once. The
|
|
1973
2011
|
server refuses rather than guessing which copy you meant.
|
|
1974
2012
|
- Fix it by running `list-messages`/`search-messages` on the mailbox you actually want to act on,
|
|
@@ -1977,29 +2015,34 @@ In a JSON string literal, `\\` — two characters — denotes **one** literal ba
|
|
|
1977
2015
|
by hand). `imap:…` IDs encode their own mailbox and never hit this.
|
|
1978
2016
|
|
|
1979
2017
|
### `search-messages` says "Partial results" or skips a mailbox
|
|
2018
|
+
|
|
1980
2019
|
- This is expected for very large IMAP/Gmail mailboxes (e.g. Gmail's `All Mail`, `Important`): Apple Mail can't scan them via AppleScript before timing out, so they're skipped and named in the result rather than silently returning empty.
|
|
1981
2020
|
- To search inside one, scope the call with `mailbox` **and** a `dateFrom`/`dateTo` window.
|
|
1982
2021
|
- Raise or disable the threshold with `APPLE_MAIL_MAX_SEARCH_MAILBOX` (default `5000`; `0` disables the guard) — note that disabling it can make a single search take minutes.
|
|
1983
2022
|
- A `Partial results` warning means coverage was incomplete; it is **not** a confirmed "no such mail."
|
|
1984
2023
|
|
|
1985
2024
|
### "Account not found"
|
|
2025
|
+
|
|
1986
2026
|
- Account names must match exactly (case-sensitive)
|
|
1987
2027
|
- Use `list-accounts` to see exact account names
|
|
1988
2028
|
|
|
1989
2029
|
### "Failed to send email"
|
|
2030
|
+
|
|
1990
2031
|
- Check your network connection
|
|
1991
2032
|
- Verify Mail.app can send emails manually
|
|
1992
2033
|
- Check if the account is configured correctly in Mail.app
|
|
1993
2034
|
|
|
1994
2035
|
### "invalid outputSchema … unsupported dialect" — every tool is refused
|
|
2036
|
+
|
|
1995
2037
|
- Full text: `Tool '<name>' has an invalid outputSchema: JSON Schema declares an unsupported dialect ("$schema": "http://json-schema.org/draft-07/schema#"). The default validator supports JSON Schema 2020-12 only.` The server connects, but **no tool is usable**.
|
|
1996
2038
|
- **Upgrade to 2.10.12 or later.** Earlier versions advertised their tool schemas in JSON Schema **draft-07** (the MCP SDK's converter default); MCP has since standardized on **2020-12** and clients reject anything else. 2.10.12 normalizes every advertised `inputSchema`/`outputSchema` to 2020-12 on the way out. See [issue #147](https://github.com/sweetrb/apple-mail-mcp/issues/147).
|
|
1997
2039
|
- Nothing to configure — restart your host app after upgrading so it re-reads the tool list.
|
|
1998
2040
|
|
|
1999
2041
|
### `apple-mail` server fails to connect when run from a clone
|
|
2042
|
+
|
|
2000
2043
|
- The root `.mcp.json` resolves its entrypoint via `${CLAUDE_PROJECT_DIR:-.}/build/index.js`. **Launch `claude` from inside the repo directory** — `CLAUDE_PROJECT_DIR` only resolves to the repo root in that case; the bare `.` fallback uses the launching shell's working directory and will point at the wrong place otherwise.
|
|
2001
2044
|
- If you've been editing the source, rerun `npm run build` — the server is `build/index.js`, and the committed bundle only reflects your changes after a rebuild.
|
|
2002
|
-
- Run `claude mcp list` to check status. If you see a
|
|
2045
|
+
- Run `claude mcp list` to check status. If you see a _conflicting scopes_ warning for `apple-mail`, you have it registered at more than one scope; project-scope wins. See [Running from a clone](#running-from-a-clone-in-claude-code-project-scope-mcpjson) for how scope precedence resolves.
|
|
2003
2046
|
- If `claude mcp get apple-mail` shows **⏸ Pending approval**, approve the project-scope server (Claude Code prompts on startup, or run it again after approving).
|
|
2004
2047
|
|
|
2005
2048
|
---
|