apple-mail-mcp 2.19.9 → 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 -344
- package/build/cli.js +78 -3
- package/build/index.js +180 -3
- 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,19 +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
|
-
| `body`
|
|
230
|
-
| `from`
|
|
231
|
-
| `subject`
|
|
232
|
-
| `mailbox`
|
|
233
|
-
| `account`
|
|
234
|
-
| `isRead`
|
|
235
|
-
| `isFlagged` | boolean | No
|
|
236
|
-
| `dateFrom`
|
|
237
|
-
| `dateTo`
|
|
238
|
-
| `limit`
|
|
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) |
|
|
239
243
|
|
|
240
244
|
**Returns:** List of matching messages with ID, date, subject, sender, and read state.
|
|
241
245
|
|
|
@@ -273,16 +277,16 @@ to disable the guard and attempt every mailbox regardless of size).
|
|
|
273
277
|
|
|
274
278
|
**Coverage diagnostics (structured fields).** The warning above is prose for a
|
|
275
279
|
human reader; the same information is also returned as structured fields on
|
|
276
|
-
`search-messages` and `list-messages`, so a caller can tell
|
|
277
|
-
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:
|
|
278
282
|
|
|
279
|
-
| Field
|
|
280
|
-
|
|
281
|
-
| `partial`
|
|
282
|
-
| `skippedLargeMailboxes` | string[] | Mailboxes never scanned because their message count exceeded `APPLE_MAIL_MAX_SEARCH_MAILBOX`, formatted `"Account / Mailbox (count)"` — e.g. `"iCloud / Archive (90694)"`.
|
|
283
|
-
| `notSearchedMailboxes`
|
|
284
|
-
| `timedOutAccounts`
|
|
285
|
-
| `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. |
|
|
286
290
|
|
|
287
291
|
Treat a non-empty `skippedLargeMailboxes` as actionable rather than
|
|
288
292
|
informational: re-run scoped to the named mailbox with a `dateFrom`/`dateTo`
|
|
@@ -296,12 +300,12 @@ coverage was complete.
|
|
|
296
300
|
|
|
297
301
|
Get the full content of a message.
|
|
298
302
|
|
|
299
|
-
| Parameter
|
|
300
|
-
|
|
301
|
-
| `id`
|
|
302
|
-
| `preferHtml` | boolean | No
|
|
303
|
-
| `mailbox`
|
|
304
|
-
| `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 |
|
|
305
309
|
|
|
306
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.
|
|
307
311
|
|
|
@@ -318,15 +322,36 @@ Get the full content of a message.
|
|
|
318
322
|
|
|
319
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.
|
|
320
324
|
|
|
321
|
-
| Parameter | Type
|
|
322
|
-
|
|
323
|
-
| `id`
|
|
324
|
-
| `mailbox` | string | No
|
|
325
|
-
| `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 |
|
|
326
330
|
|
|
327
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)).
|
|
328
332
|
|
|
329
|
-
**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).
|
|
330
355
|
|
|
331
356
|
---
|
|
332
357
|
|
|
@@ -334,14 +359,14 @@ Return a message's raw RFC 5322 header block — without fetching the body or an
|
|
|
334
359
|
|
|
335
360
|
List messages in a mailbox.
|
|
336
361
|
|
|
337
|
-
| Parameter
|
|
338
|
-
|
|
339
|
-
| `mailbox`
|
|
340
|
-
| `account`
|
|
341
|
-
| `limit`
|
|
342
|
-
| `offset`
|
|
343
|
-
| `from`
|
|
344
|
-
| `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 |
|
|
345
370
|
|
|
346
371
|
**Returns:** List of messages with ID, date, subject, and sender.
|
|
347
372
|
|
|
@@ -368,18 +393,19 @@ Send a new email immediately.
|
|
|
368
393
|
|
|
369
394
|
**⚠️ Safety:** Sends real mail immediately and cannot be unsent. Confirm the recipients, subject, and body with the user before calling.
|
|
370
395
|
|
|
371
|
-
| Parameter
|
|
372
|
-
|
|
373
|
-
| `to`
|
|
374
|
-
| `subject`
|
|
375
|
-
| `body`
|
|
376
|
-
| `cc`
|
|
377
|
-
| `bcc`
|
|
378
|
-
| `account`
|
|
379
|
-
| `attachments` | (string \| {filename, contentBase64})[] | No
|
|
380
|
-
| `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) |
|
|
381
406
|
|
|
382
407
|
**Example:**
|
|
408
|
+
|
|
383
409
|
```json
|
|
384
410
|
{
|
|
385
411
|
"to": ["colleague@company.com"],
|
|
@@ -416,7 +442,7 @@ Two differences to know when SMTP is auto-preferred:
|
|
|
416
442
|
Use `transport: "applescript"` if you want Mail.app itself to file the copy.
|
|
417
443
|
- **`account` is a From override, not account selection.** Over SMTP, `account`
|
|
418
444
|
is used as the From address only when it is an email address; a Mail.app
|
|
419
|
-
account
|
|
445
|
+
account _label_ (e.g. `"Work"`) can't select an account over SMTP, so a call
|
|
420
446
|
that passes one is left on the AppleScript path automatically. To force
|
|
421
447
|
account selection, pass `transport: "applescript"` explicitly. For sender
|
|
422
448
|
safety, an email-form override must match the SMTP login user, the configured
|
|
@@ -437,18 +463,18 @@ trusted isolated server or test fixture; it disables the upgrade requirement and
|
|
|
437
463
|
can expose credentials and message content. The server emits a warning when it
|
|
438
464
|
is used. Keep the default unset.
|
|
439
465
|
|
|
440
|
-
| Variable
|
|
441
|
-
|
|
442
|
-
| `APPLE_MAIL_MCP_SMTP_HOST`
|
|
443
|
-
| `APPLE_MAIL_MCP_SMTP_USER`
|
|
444
|
-
| `APPLE_MAIL_MCP_SMTP_PORT`
|
|
445
|
-
| `APPLE_MAIL_MCP_SMTP_SECURE`
|
|
446
|
-
| `APPLE_MAIL_MCP_SMTP_ALLOW_PLAINTEXT`
|
|
447
|
-
| `APPLE_MAIL_MCP_SMTP_FROM`
|
|
448
|
-
| `APPLE_MAIL_MCP_SMTP_ALLOWED_FROM`
|
|
449
|
-
| `APPLE_MAIL_MCP_SMTP_PASSWORD`
|
|
450
|
-
| `APPLE_MAIL_MCP_SMTP_KEYCHAIN_SERVICE` | No
|
|
451
|
-
| `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 |
|
|
452
478
|
|
|
453
479
|
Store the password in the Keychain once (an app-specific password for Gmail/
|
|
454
480
|
iCloud). A generic-password item with an explicit service name keeps it from
|
|
@@ -468,6 +494,7 @@ security add-generic-password -s apple-mail-mcp-smtp -a you@gmail.com -w
|
|
|
468
494
|
|
|
469
495
|
Once the env vars are set, a plain `send-email` (no `transport`) already goes
|
|
470
496
|
out clean:
|
|
497
|
+
|
|
471
498
|
```json
|
|
472
499
|
{
|
|
473
500
|
"to": ["colleague@company.com"],
|
|
@@ -514,7 +541,7 @@ What routes to IMAP when an account is IMAP-configured:
|
|
|
514
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).
|
|
515
542
|
- **Message mutations:** `mark-as-read`/`unread`, `flag-message`/`unflag-message`, `move-message`, `delete-message`.
|
|
516
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.
|
|
517
|
-
- **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.
|
|
518
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.
|
|
519
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.
|
|
520
547
|
|
|
@@ -534,9 +561,9 @@ matching `account` is passed. There are three cases:
|
|
|
534
561
|
- **Explicit IMAP account** — single-account IMAP (fast server-side path).
|
|
535
562
|
- **Explicit non-IMAP account** — AppleScript (that account isn't on IMAP).
|
|
536
563
|
- **No `account` given** — **merge across all accounts**: the query fans out over
|
|
537
|
-
|
|
564
|
+
_every_ configured IMAP account, **and** AppleScript runs **only for the
|
|
538
565
|
accounts no IMAP config covers** (the account list is partitioned — accounts
|
|
539
|
-
already served by IMAP are
|
|
566
|
+
already served by IMAP are _not_ re-scanned via AppleScript). If every Mail
|
|
540
567
|
account is IMAP-configured, AppleScript is skipped entirely. The results are
|
|
541
568
|
merged so no account is dropped. Message lists still de-duplicate as a safety
|
|
542
569
|
net (preferring the IMAP copy, which carries the round-trippable `imap:` id) and
|
|
@@ -555,7 +582,7 @@ matching `account` is passed. There are three cases:
|
|
|
555
582
|
one `SEARCH` + a bounded `FETCH` per mailbox over the pooled IMAP
|
|
556
583
|
connection — pin a `mailbox` to skip the fan-out when you already know
|
|
557
584
|
where to look. `list-messages` (no query) still defaults an omitted
|
|
558
|
-
mailbox to `INBOX` on every provider — only unscoped
|
|
585
|
+
mailbox to `INBOX` on every provider — only unscoped _search_ scans the
|
|
559
586
|
whole account.
|
|
560
587
|
|
|
561
588
|
If IMAP is **not** configured at all, every read behaves exactly as before
|
|
@@ -563,21 +590,21 @@ If IMAP is **not** configured at all, every read behaves exactly as before
|
|
|
563
590
|
`delete-mailbox`, `rename-mailbox`) remain conservative — they route to IMAP only
|
|
564
591
|
for an explicitly-named IMAP account, never on an omitted account.
|
|
565
592
|
|
|
566
|
-
| Variable
|
|
567
|
-
|
|
568
|
-
| `APPLE_MAIL_MCP_IMAP_USER`
|
|
569
|
-
| `APPLE_MAIL_MCP_IMAP_ACCOUNT`
|
|
570
|
-
| `APPLE_MAIL_MCP_IMAP_HOST`
|
|
571
|
-
| `APPLE_MAIL_MCP_IMAP_PORT`
|
|
572
|
-
| `APPLE_MAIL_MCP_IMAP_ALLOW_PLAINTEXT`
|
|
573
|
-
| `APPLE_MAIL_MCP_IMAP_PASSWORD`
|
|
574
|
-
| `APPLE_MAIL_MCP_IMAP_KEYCHAIN_SERVICE` | No
|
|
575
|
-
| `APPLE_MAIL_MCP_IMAP_KEYCHAIN_ACCOUNT` | No
|
|
576
|
-
| `APPLE_MAIL_MCP_IMAP_ACCOUNTS`
|
|
577
|
-
| `APPLE_MAIL_MCP_IMAP_IDLE`
|
|
578
|
-
| `APPLE_MAIL_MCP_IMAP_IDLE_MS`
|
|
579
|
-
| `APPLE_MAIL_MCP_STATS_BUDGET_MS`
|
|
580
|
-
| `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 |
|
|
581
608
|
|
|
582
609
|
**Multiple IMAP accounts (C2):** set `APPLE_MAIL_MCP_IMAP_ACCOUNTS` to a JSON array, e.g.
|
|
583
610
|
`[{"account":"Work","user":"me@co.com","host":"imap.co.com","keychainService":"imap.co.com"}]`.
|
|
@@ -623,9 +650,9 @@ those slots. This server keeps its footprint small:
|
|
|
623
650
|
polling every 30s, so it can't linger holding sockets after its session is gone.
|
|
624
651
|
|
|
625
652
|
The catch is **multiple concurrent instances**. A host like the Claude desktop
|
|
626
|
-
app spawns a
|
|
653
|
+
app spawns a _separate_ set of MCP servers per open conversation (and respawns
|
|
627
654
|
them after a crash), so the footprint is **per instance × accounts**. With IDLE
|
|
628
|
-
off, an idle instance trends to 0 connections; with many
|
|
655
|
+
off, an idle instance trends to 0 connections; with many _active_ conversations
|
|
629
656
|
or IDLE on, the per-account total climbs toward Gmail's 15-connection cap and can
|
|
630
657
|
starve Apple Mail of slots (→ intermittent "cannot connect"). If you hit that,
|
|
631
658
|
close idle Claude conversations, keep `APPLE_MAIL_MCP_IMAP_IDLE` off unless you
|
|
@@ -702,22 +729,23 @@ Enable it in your MCP client config alongside the IMAP settings:
|
|
|
702
729
|
|
|
703
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.
|
|
704
731
|
|
|
705
|
-
| Parameter
|
|
706
|
-
|
|
707
|
-
| `recipients` | object[] | Yes
|
|
708
|
-
| `subject`
|
|
709
|
-
| `body`
|
|
710
|
-
| `account`
|
|
711
|
-
| `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) |
|
|
712
739
|
|
|
713
740
|
Each recipient object:
|
|
714
741
|
|
|
715
|
-
| Field
|
|
716
|
-
|
|
717
|
-
| `email`
|
|
718
|
-
| `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 |
|
|
719
746
|
|
|
720
747
|
**Example:**
|
|
748
|
+
|
|
721
749
|
```json
|
|
722
750
|
{
|
|
723
751
|
"recipients": [
|
|
@@ -739,15 +767,15 @@ Each recipient object:
|
|
|
739
767
|
|
|
740
768
|
Save an email to Drafts without sending.
|
|
741
769
|
|
|
742
|
-
| Parameter
|
|
743
|
-
|
|
744
|
-
| `to`
|
|
745
|
-
| `subject`
|
|
746
|
-
| `body`
|
|
747
|
-
| `cc`
|
|
748
|
-
| `bcc`
|
|
749
|
-
| `account`
|
|
750
|
-
| `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 |
|
|
751
779
|
|
|
752
780
|
**Returns:** Confirmation that draft was created.
|
|
753
781
|
|
|
@@ -755,12 +783,12 @@ Save an email to Drafts without sending.
|
|
|
755
783
|
|
|
756
784
|
Group a conversation by normalized subject (across the AppleScript or IMAP backend).
|
|
757
785
|
|
|
758
|
-
| Parameter | Type
|
|
759
|
-
|
|
760
|
-
| `id`
|
|
761
|
-
| `account` | string | No
|
|
762
|
-
| `mailbox` | string | No
|
|
763
|
-
| `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) |
|
|
764
792
|
|
|
765
793
|
**Returns:** The conversation's messages, oldest-first.
|
|
766
794
|
|
|
@@ -768,10 +796,10 @@ Group a conversation by normalized subject (across the AppleScript or IMAP backe
|
|
|
768
796
|
|
|
769
797
|
Return an attachment's bytes as base64 (the read counterpart to inline-base64 send).
|
|
770
798
|
|
|
771
|
-
| Parameter
|
|
772
|
-
|
|
773
|
-
| `id`
|
|
774
|
-
| `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`) |
|
|
775
803
|
|
|
776
804
|
**Returns:** The attachment bytes, base64-encoded (also in `structuredContent.contentBase64`).
|
|
777
805
|
|
|
@@ -781,9 +809,9 @@ Return an attachment's bytes as base64 (the read counterpart to inline-base64 se
|
|
|
781
809
|
|
|
782
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.
|
|
783
811
|
|
|
784
|
-
| Parameter | Type
|
|
785
|
-
|
|
786
|
-
| `ids`
|
|
812
|
+
| Parameter | Type | Required | Description |
|
|
813
|
+
| --------- | -------- | -------- | ------------------------------------------- |
|
|
814
|
+
| `ids` | string[] | Yes | 1–100 message IDs, each numeric or `imap:…` |
|
|
787
815
|
|
|
788
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.
|
|
789
817
|
|
|
@@ -795,15 +823,16 @@ Map `imap:` message IDs to their numeric Mail.app IDs, via each message's RFC 53
|
|
|
795
823
|
|
|
796
824
|
Reply to an existing message.
|
|
797
825
|
|
|
798
|
-
| Parameter
|
|
799
|
-
|
|
800
|
-
| `id`
|
|
801
|
-
| `body`
|
|
802
|
-
| `replyAll`
|
|
803
|
-
| `send`
|
|
804
|
-
| `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. |
|
|
805
833
|
|
|
806
834
|
**Example - Reply to sender only:**
|
|
835
|
+
|
|
807
836
|
```json
|
|
808
837
|
{
|
|
809
838
|
"id": "12345",
|
|
@@ -812,6 +841,7 @@ Reply to an existing message.
|
|
|
812
841
|
```
|
|
813
842
|
|
|
814
843
|
**Example - Reply all, save as draft:**
|
|
844
|
+
|
|
815
845
|
```json
|
|
816
846
|
{
|
|
817
847
|
"id": "12345",
|
|
@@ -837,13 +867,13 @@ Success includes `transport` and, for SMTP when returned by the server, the new
|
|
|
837
867
|
|
|
838
868
|
Forward a message to new recipients.
|
|
839
869
|
|
|
840
|
-
| Parameter
|
|
841
|
-
|
|
842
|
-
| `id`
|
|
843
|
-
| `to`
|
|
844
|
-
| `body`
|
|
845
|
-
| `send`
|
|
846
|
-
| `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. |
|
|
847
877
|
|
|
848
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).
|
|
849
879
|
|
|
@@ -857,9 +887,9 @@ SMTP forwarding requires a readable plain-text original. HTML-only IMAP messages
|
|
|
857
887
|
|
|
858
888
|
Change read status of a message.
|
|
859
889
|
|
|
860
|
-
| Parameter | Type
|
|
861
|
-
|
|
862
|
-
| `id`
|
|
890
|
+
| Parameter | Type | Required | Description |
|
|
891
|
+
| --------- | ------ | -------- | ----------- |
|
|
892
|
+
| `id` | string | Yes | Message ID |
|
|
863
893
|
|
|
864
894
|
---
|
|
865
895
|
|
|
@@ -867,10 +897,10 @@ Change read status of a message.
|
|
|
867
897
|
|
|
868
898
|
Flag or unflag a message. `flag-message` optionally takes a flag **color**; `unflag-message` removes the flag entirely (which also clears any color).
|
|
869
899
|
|
|
870
|
-
| Parameter | Type
|
|
871
|
-
|
|
872
|
-
| `id`
|
|
873
|
-
| `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. |
|
|
874
904
|
|
|
875
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.
|
|
876
906
|
|
|
@@ -882,9 +912,9 @@ To **read** a color, the IMAP read path returns `flagColorIndex` in `structuredC
|
|
|
882
912
|
|
|
883
913
|
Delete a message (move to trash).
|
|
884
914
|
|
|
885
|
-
| Parameter | Type
|
|
886
|
-
|
|
887
|
-
| `id`
|
|
915
|
+
| Parameter | Type | Required | Description |
|
|
916
|
+
| --------- | ------ | -------- | ----------- |
|
|
917
|
+
| `id` | string | Yes | Message ID |
|
|
888
918
|
|
|
889
919
|
`structuredContent` carries `countDelta` — what the delete actually did to the
|
|
890
920
|
source mailbox. See [Auditing destructive operations](#auditing-destructive-operations).
|
|
@@ -897,11 +927,11 @@ source mailbox. See [Auditing destructive operations](#auditing-destructive-oper
|
|
|
897
927
|
|
|
898
928
|
Move a message to a different mailbox.
|
|
899
929
|
|
|
900
|
-
| Parameter | Type
|
|
901
|
-
|
|
902
|
-
| `id`
|
|
903
|
-
| `mailbox` | string | Yes
|
|
904
|
-
| `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 |
|
|
905
935
|
|
|
906
936
|
A destination is matched first as a full path, then as a leaf name. If a leaf
|
|
907
937
|
name matches **more than one** mailbox (e.g. `Archive` under both `Work` and
|
|
@@ -918,9 +948,9 @@ the full path. The same applies to `batch-move-messages`, `delete-mailbox` and
|
|
|
918
948
|
|
|
919
949
|
List attachments on a message.
|
|
920
950
|
|
|
921
|
-
| Parameter | Type
|
|
922
|
-
|
|
923
|
-
| `id`
|
|
951
|
+
| Parameter | Type | Required | Description |
|
|
952
|
+
| --------- | ------ | -------- | ----------- |
|
|
953
|
+
| `id` | string | Yes | Message ID |
|
|
924
954
|
|
|
925
955
|
**Returns:** List of attachments with name, MIME type, and size.
|
|
926
956
|
|
|
@@ -935,11 +965,11 @@ of overwriting an existing file. AppleScript and MIME fallback paths stage the
|
|
|
935
965
|
bytes privately, commit with an exclusive create, and leave the saved file
|
|
936
966
|
owner-readable/writable (`0600`).
|
|
937
967
|
|
|
938
|
-
| Parameter
|
|
939
|
-
|
|
940
|
-
| `id`
|
|
941
|
-
| `attachmentName` | string | Yes
|
|
942
|
-
| `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 |
|
|
943
973
|
|
|
944
974
|
---
|
|
945
975
|
|
|
@@ -976,11 +1006,11 @@ therefore a count of messages, not of list positions.
|
|
|
976
1006
|
|
|
977
1007
|
#### `batch-delete-messages`
|
|
978
1008
|
|
|
979
|
-
| Parameter
|
|
980
|
-
|
|
981
|
-
| `ids`
|
|
982
|
-
| `sourceMailbox` | string
|
|
983
|
-
| `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. |
|
|
984
1014
|
|
|
985
1015
|
`structuredContent` carries `countDelta` — what the batch actually did to each
|
|
986
1016
|
source mailbox. See [Auditing destructive operations](#auditing-destructive-operations).
|
|
@@ -989,33 +1019,33 @@ source mailbox. See [Auditing destructive operations](#auditing-destructive-oper
|
|
|
989
1019
|
|
|
990
1020
|
#### `batch-move-messages`
|
|
991
1021
|
|
|
992
|
-
| Parameter
|
|
993
|
-
|
|
994
|
-
| `ids`
|
|
995
|
-
| `mailbox`
|
|
996
|
-
| `account`
|
|
997
|
-
| `sourceMailbox` | string
|
|
998
|
-
| `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. |
|
|
999
1029
|
|
|
1000
1030
|
`structuredContent` carries `countDelta` — what the batch actually did to each
|
|
1001
1031
|
**source** mailbox. See [Auditing destructive operations](#auditing-destructive-operations).
|
|
1002
1032
|
|
|
1003
1033
|
#### `batch-mark-as-read` / `batch-mark-as-unread`
|
|
1004
1034
|
|
|
1005
|
-
| Parameter
|
|
1006
|
-
|
|
1007
|
-
| `ids`
|
|
1008
|
-
| `sourceMailbox` | string
|
|
1009
|
-
| `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. |
|
|
1010
1040
|
|
|
1011
1041
|
#### `batch-flag-messages` / `batch-unflag-messages`
|
|
1012
1042
|
|
|
1013
|
-
| Parameter
|
|
1014
|
-
|
|
1015
|
-
| `ids`
|
|
1016
|
-
| `color`
|
|
1017
|
-
| `sourceMailbox` | string
|
|
1018
|
-
| `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. |
|
|
1019
1049
|
|
|
1020
1050
|
---
|
|
1021
1051
|
|
|
@@ -1025,9 +1055,9 @@ source mailbox. See [Auditing destructive operations](#auditing-destructive-oper
|
|
|
1025
1055
|
|
|
1026
1056
|
List all mailboxes for an account.
|
|
1027
1057
|
|
|
1028
|
-
| Parameter | Type
|
|
1029
|
-
|
|
1030
|
-
| `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 |
|
|
1031
1061
|
|
|
1032
1062
|
**Returns:** List of mailbox **paths** (account-relative, e.g. `Archive/Inbox` for
|
|
1033
1063
|
a nested mailbox — a top-level `Inbox` stays `Inbox`) with message and unread
|
|
@@ -1067,10 +1097,10 @@ reported as ambiguous rather than silently resolving to the account copy.
|
|
|
1067
1097
|
|
|
1068
1098
|
Get unread message count.
|
|
1069
1099
|
|
|
1070
|
-
| Parameter | Type
|
|
1071
|
-
|
|
1072
|
-
| `mailbox` | string | No
|
|
1073
|
-
| `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) |
|
|
1074
1104
|
|
|
1075
1105
|
**Returns:** The unread count for the requested scope.
|
|
1076
1106
|
|
|
@@ -1082,10 +1112,10 @@ Get unread message count.
|
|
|
1082
1112
|
|
|
1083
1113
|
Create a new mailbox.
|
|
1084
1114
|
|
|
1085
|
-
| Parameter | Type
|
|
1086
|
-
|
|
1087
|
-
| `name`
|
|
1088
|
-
| `account` | string | No
|
|
1115
|
+
| Parameter | Type | Required | Description |
|
|
1116
|
+
| --------- | ------ | -------- | -------------------- |
|
|
1117
|
+
| `name` | string | Yes | Mailbox name |
|
|
1118
|
+
| `account` | string | No | Account to create in |
|
|
1089
1119
|
|
|
1090
1120
|
---
|
|
1091
1121
|
|
|
@@ -1093,10 +1123,10 @@ Create a new mailbox.
|
|
|
1093
1123
|
|
|
1094
1124
|
Delete a mailbox.
|
|
1095
1125
|
|
|
1096
|
-
| Parameter | Type
|
|
1097
|
-
|
|
1098
|
-
| `name`
|
|
1099
|
-
| `account` | string | No
|
|
1126
|
+
| Parameter | Type | Required | Description |
|
|
1127
|
+
| --------- | ------ | -------- | -------------------------- |
|
|
1128
|
+
| `name` | string | Yes | Mailbox name |
|
|
1129
|
+
| `account` | string | No | Account containing mailbox |
|
|
1100
1130
|
|
|
1101
1131
|
**⚠️ Safety:** Destructive — deletes the mailbox and its contents. Requires explicit user confirmation; list mailboxes first to confirm the name.
|
|
1102
1132
|
|
|
@@ -1106,11 +1136,11 @@ Delete a mailbox.
|
|
|
1106
1136
|
|
|
1107
1137
|
Rename a mailbox (creates new, moves messages, deletes old).
|
|
1108
1138
|
|
|
1109
|
-
| Parameter | Type
|
|
1110
|
-
|
|
1111
|
-
| `oldName` | string | Yes
|
|
1112
|
-
| `newName` | string | Yes
|
|
1113
|
-
| `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 |
|
|
1114
1144
|
|
|
1115
1145
|
---
|
|
1116
1146
|
|
|
@@ -1136,12 +1166,12 @@ List existing smart mailboxes.
|
|
|
1136
1166
|
|
|
1137
1167
|
Create a smart mailbox with a simple contains rule.
|
|
1138
1168
|
|
|
1139
|
-
| Parameter
|
|
1140
|
-
|
|
1141
|
-
| `name`
|
|
1142
|
-
| `fromContains`
|
|
1143
|
-
| `subjectContains` | string | No
|
|
1144
|
-
| `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 |
|
|
1145
1175
|
|
|
1146
1176
|
Provide at least one of the three `*Contains` fields.
|
|
1147
1177
|
|
|
@@ -1153,9 +1183,9 @@ Provide at least one of the three `*Contains` fields.
|
|
|
1153
1183
|
|
|
1154
1184
|
Delete a smart mailbox by name.
|
|
1155
1185
|
|
|
1156
|
-
| Parameter | Type
|
|
1157
|
-
|
|
1158
|
-
| `name`
|
|
1186
|
+
| Parameter | Type | Required | Description |
|
|
1187
|
+
| --------- | ------ | -------- | ------------------ |
|
|
1188
|
+
| `name` | string | Yes | Smart mailbox name |
|
|
1159
1189
|
|
|
1160
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.
|
|
1161
1191
|
|
|
@@ -1165,11 +1195,11 @@ Delete a smart mailbox by name.
|
|
|
1165
1195
|
|
|
1166
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: ...").
|
|
1167
1197
|
|
|
1168
|
-
| Parameter
|
|
1169
|
-
|
|
1170
|
-
| `dryRun`
|
|
1171
|
-
| `minCount` | number
|
|
1172
|
-
| `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) |
|
|
1173
1203
|
|
|
1174
1204
|
Defaults to a **safe dry run** that only proposes. Pass `dryRun: false` to actually create the smart mailboxes for newsletters cluttering your Inbox.
|
|
1175
1205
|
|
|
@@ -1205,9 +1235,9 @@ List all mail rules.
|
|
|
1205
1235
|
|
|
1206
1236
|
Enable or disable a mail rule.
|
|
1207
1237
|
|
|
1208
|
-
| Parameter | Type
|
|
1209
|
-
|
|
1210
|
-
| `name`
|
|
1238
|
+
| Parameter | Type | Required | Description |
|
|
1239
|
+
| --------- | ------ | -------- | ----------- |
|
|
1240
|
+
| `name` | string | Yes | Rule name |
|
|
1211
1241
|
|
|
1212
1242
|
---
|
|
1213
1243
|
|
|
@@ -1215,13 +1245,13 @@ Enable or disable a mail rule.
|
|
|
1215
1245
|
|
|
1216
1246
|
Create a Mail rule with one or more conditions and actions.
|
|
1217
1247
|
|
|
1218
|
-
| Parameter
|
|
1219
|
-
|
|
1220
|
-
| `name`
|
|
1221
|
-
| `conditions` | object[] | Yes
|
|
1222
|
-
| `actions`
|
|
1223
|
-
| `matchAll`
|
|
1224
|
-
| `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`) |
|
|
1225
1255
|
|
|
1226
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`.
|
|
1227
1257
|
|
|
@@ -1231,6 +1261,7 @@ then call `enable-rule` explicitly when the rule is approved. Set
|
|
|
1231
1261
|
`enabled: true` only when immediate activation is deliberate.
|
|
1232
1262
|
|
|
1233
1263
|
**Example:**
|
|
1264
|
+
|
|
1234
1265
|
```json
|
|
1235
1266
|
{
|
|
1236
1267
|
"name": "Newsletters",
|
|
@@ -1245,9 +1276,9 @@ then call `enable-rule` explicitly when the rule is approved. Set
|
|
|
1245
1276
|
|
|
1246
1277
|
Delete a mail rule by name.
|
|
1247
1278
|
|
|
1248
|
-
| Parameter | Type
|
|
1249
|
-
|
|
1250
|
-
| `name`
|
|
1279
|
+
| Parameter | Type | Required | Description |
|
|
1280
|
+
| --------- | ------ | -------- | ----------- |
|
|
1281
|
+
| `name` | string | Yes | Rule name |
|
|
1251
1282
|
|
|
1252
1283
|
**⚠️ Safety:** Destructive. Requires explicit user confirmation; list rules first to confirm the name.
|
|
1253
1284
|
|
|
@@ -1261,9 +1292,9 @@ Search the macOS Contacts database by name, organization, nickname, or email sub
|
|
|
1261
1292
|
|
|
1262
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.
|
|
1263
1294
|
|
|
1264
|
-
| Parameter | Type
|
|
1265
|
-
|
|
1266
|
-
| `query`
|
|
1295
|
+
| Parameter | Type | Required | Description |
|
|
1296
|
+
| --------- | ------ | -------- | --------------------------------------------------------------------------------- |
|
|
1297
|
+
| `query` | string | Yes | Substring matched against full name, organization, nickname, or any email address |
|
|
1267
1298
|
|
|
1268
1299
|
**Returns:** List of contacts with name, email addresses, and phone numbers. Results are **not** truncated — a broad query returns every match.
|
|
1269
1300
|
|
|
@@ -1277,14 +1308,14 @@ Email templates are **persisted to disk** so they survive server restarts, store
|
|
|
1277
1308
|
|
|
1278
1309
|
Save or update an email template.
|
|
1279
1310
|
|
|
1280
|
-
| Parameter | Type
|
|
1281
|
-
|
|
1282
|
-
| `name`
|
|
1283
|
-
| `subject` | string
|
|
1284
|
-
| `body`
|
|
1285
|
-
| `to`
|
|
1286
|
-
| `cc`
|
|
1287
|
-
| `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) |
|
|
1288
1319
|
|
|
1289
1320
|
---
|
|
1290
1321
|
|
|
@@ -1300,9 +1331,9 @@ List all saved templates.
|
|
|
1300
1331
|
|
|
1301
1332
|
Get a template by ID.
|
|
1302
1333
|
|
|
1303
|
-
| Parameter | Type
|
|
1304
|
-
|
|
1305
|
-
| `id`
|
|
1334
|
+
| Parameter | Type | Required | Description |
|
|
1335
|
+
| --------- | ------ | -------- | ----------- |
|
|
1336
|
+
| `id` | string | Yes | Template ID |
|
|
1306
1337
|
|
|
1307
1338
|
---
|
|
1308
1339
|
|
|
@@ -1310,9 +1341,9 @@ Get a template by ID.
|
|
|
1310
1341
|
|
|
1311
1342
|
Delete a template.
|
|
1312
1343
|
|
|
1313
|
-
| Parameter | Type
|
|
1314
|
-
|
|
1315
|
-
| `id`
|
|
1344
|
+
| Parameter | Type | Required | Description |
|
|
1345
|
+
| --------- | ------ | -------- | ----------- |
|
|
1346
|
+
| `id` | string | Yes | Template ID |
|
|
1316
1347
|
|
|
1317
1348
|
**⚠️ Safety:** Destructive — removes the template from the on-disk store. Requires explicit user confirmation; list templates first to confirm the id.
|
|
1318
1349
|
|
|
@@ -1322,13 +1353,13 @@ Delete a template.
|
|
|
1322
1353
|
|
|
1323
1354
|
Create a draft from a template, with optional overrides.
|
|
1324
1355
|
|
|
1325
|
-
| Parameter | Type
|
|
1326
|
-
|
|
1327
|
-
| `id`
|
|
1328
|
-
| `to`
|
|
1329
|
-
| `cc`
|
|
1330
|
-
| `subject` | string
|
|
1331
|
-
| `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 |
|
|
1332
1363
|
|
|
1333
1364
|
---
|
|
1334
1365
|
|
|
@@ -1358,9 +1389,9 @@ Run a full setup diagnostic: Mail.app automation permission, account state (flag
|
|
|
1358
1389
|
|
|
1359
1390
|
Get mail statistics.
|
|
1360
1391
|
|
|
1361
|
-
| Parameter | Type
|
|
1362
|
-
|
|
1363
|
-
| `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. |
|
|
1364
1395
|
|
|
1365
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.
|
|
1366
1397
|
|
|
@@ -1443,20 +1474,20 @@ returns the comparison in `structuredContent`:
|
|
|
1443
1474
|
|
|
1444
1475
|
`status` is deliberately not a pass/fail flag:
|
|
1445
1476
|
|
|
1446
|
-
| `status`
|
|
1447
|
-
|
|
1448
|
-
| `match`
|
|
1449
|
-
| `over`
|
|
1450
|
-
| `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 |
|
|
1451
1482
|
|
|
1452
|
-
`unknownReason` distinguishes four cases that are
|
|
1483
|
+
`unknownReason` distinguishes four cases that are _not_ interchangeable:
|
|
1453
1484
|
|
|
1454
|
-
| `unknownReason`
|
|
1455
|
-
|
|
1456
|
-
| `count-unreadable`
|
|
1457
|
-
| `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. |
|
|
1458
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. |
|
|
1459
|
-
| `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. |
|
|
1460
1491
|
|
|
1461
1492
|
> **`under` was removed in 2.11.0.** It used to mean "fewer left than expected"
|
|
1462
1493
|
> and was documented as routine. Field evidence retired it — see
|
|
@@ -1479,8 +1510,8 @@ trusted:
|
|
|
1479
1510
|
|
|
1480
1511
|
**Concurrent departure is the benign cause to rule out first**, and the warning
|
|
1481
1512
|
text says so. What the asymmetry argument actually buys is the other half:
|
|
1482
|
-
concurrent
|
|
1483
|
-
mid-operation
|
|
1513
|
+
concurrent _arrivals_ cannot produce `over`, because a message arriving
|
|
1514
|
+
mid-operation _raises_ the after-count and biases the reading short.
|
|
1484
1515
|
That is why `over` is the interesting direction — a strong signal, not a proof.
|
|
1485
1516
|
|
|
1486
1517
|
Setting `APPLE_MAIL_MCP_AUDIT_LOG` is what settles which one you have: the
|
|
@@ -1497,7 +1528,7 @@ On iCloud, @scottstern0325 ran the check that settled this: for two batches
|
|
|
1497
1528
|
reporting `observed: 0`, the messages were located **in Trash**, matched by
|
|
1498
1529
|
`date received` + sender against the audit log's pre-image. The deletes had
|
|
1499
1530
|
happened. Mail's count had not caught up. Across four readings — 0 of 4, 0 of 1
|
|
1500
|
-
(a
|
|
1531
|
+
(a _single-id_ delete), 15 of 16, and 14 of 15 — the shortfall bore no relation
|
|
1501
1532
|
to batch size, which is what a lagging count looks like and not what a
|
|
1502
1533
|
store-behaviour rule looks like.
|
|
1503
1534
|
|
|
@@ -1514,14 +1545,14 @@ Two things follow:
|
|
|
1514
1545
|
— a warning that fires on every ordinary Gmail delete would be ignored exactly
|
|
1515
1546
|
when it matters.
|
|
1516
1547
|
- **To confirm where messages went, match them at the destination by `date
|
|
1517
|
-
|
|
1548
|
+
received` plus sender — not by the numeric ids you passed.** Ids are renumbered
|
|
1518
1549
|
by the move and do not survive it.
|
|
1519
1550
|
|
|
1520
1551
|
**Removed in 2.11.0: the "reported success with no observed effect" warning.**
|
|
1521
1552
|
Shipped in 2.10.30, it fired when the count was flat, the snapshot read cleanly
|
|
1522
1553
|
and nothing had disappeared. Its premise was that the snapshot corroborated the
|
|
1523
1554
|
count — but both are read back-to-back in the same script, and the record that
|
|
1524
|
-
prompted it turns out to have had
|
|
1555
|
+
prompted it turns out to have had _both_ instruments stale at once. It therefore
|
|
1525
1556
|
fired on stores that had done exactly what they were asked. It is gone rather
|
|
1526
1557
|
than narrowed; `over` is the only surviving assertion.
|
|
1527
1558
|
|
|
@@ -1547,6 +1578,7 @@ Three more honesty rules:
|
|
|
1547
1578
|
clean — but it does not flag it either. The collateral diff still names anything
|
|
1548
1579
|
that vanished, so **enable `APPLE_MAIL_MCP_AUDIT_LOG` if you need coverage for
|
|
1549
1580
|
same-mailbox moves.**
|
|
1581
|
+
|
|
1550
1582
|
- A **repeated id is one message**. The batch tools operate on each distinct id
|
|
1551
1583
|
once and return one result per distinct id, so `success` counts messages rather
|
|
1552
1584
|
than list positions — and `expected` stays comparable with the mailbox instead
|
|
@@ -1581,10 +1613,10 @@ Single-message tools carry a post-condition check instead. `delete-message` and
|
|
|
1581
1613
|
}
|
|
1582
1614
|
```
|
|
1583
1615
|
|
|
1584
|
-
| `verdict`
|
|
1585
|
-
|
|
1586
|
-
| `verified`
|
|
1587
|
-
| `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`. |
|
|
1588
1620
|
|
|
1589
1621
|
`unverified` is **not a failure** and must not be rendered as one — it means
|
|
1590
1622
|
"accepted, no observation either way". It is reported rather than hidden because
|
|
@@ -1595,12 +1627,12 @@ store can legitimately keep a message visible in an all-mail view after a move.
|
|
|
1595
1627
|
|
|
1596
1628
|
### `APPLE_MAIL_MCP_AUDIT_LOG` — opt-in forensic log
|
|
1597
1629
|
|
|
1598
|
-
| Variable
|
|
1599
|
-
|
|
1600
|
-
| `APPLE_MAIL_MCP_AUDIT_LOG`
|
|
1601
|
-
| `APPLE_MAIL_MCP_AUDIT_SUBJECTS`
|
|
1602
|
-
| `APPLE_MAIL_MCP_AUDIT_SNAPSHOT_MAX`
|
|
1603
|
-
| `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 |
|
|
1604
1636
|
|
|
1605
1637
|
When set, each destructive operation appends **one JSON object per line**
|
|
1606
1638
|
containing: timestamp, tool name, server version, the arguments it was called
|
|
@@ -1682,12 +1714,12 @@ When a slice still will not read, the snapshot is reported as `partial` and it
|
|
|
1682
1714
|
Each half of the diff is withheld when the snapshot that could **refute** it has
|
|
1683
1715
|
a hole, because a wrong name here is worse than a missing one:
|
|
1684
1716
|
|
|
1685
|
-
| Hole in
|
|
1686
|
-
|
|
1687
|
-
| neither (`"ok"`) | reported
|
|
1688
|
-
| `before`
|
|
1689
|
-
| `after`
|
|
1690
|
-
| 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 |
|
|
1691
1723
|
|
|
1692
1724
|
**An absent field means "not computable", never "empty".** Check
|
|
1693
1725
|
`"snapshot": "ok"` before reading `disappeared` as a clean bill of health.
|
|
@@ -1703,7 +1735,7 @@ a hole, because a wrong name here is worse than a missing one:
|
|
|
1703
1735
|
### Privacy, and what the file costs you
|
|
1704
1736
|
|
|
1705
1737
|
- **Default:** identifying metadata only — Message-ID, date, mailbox, account,
|
|
1706
|
-
numeric id. Enough to say
|
|
1738
|
+
numeric id. Enough to say _which_ message, nothing about what it says.
|
|
1707
1739
|
- **Subjects are behind their own opt-in** (`APPLE_MAIL_MCP_AUDIT_SUBJECTS=1`)
|
|
1708
1740
|
because a subject line is frequently the entire sensitive payload, and it is
|
|
1709
1741
|
not needed to diagnose #155.
|
|
@@ -1818,6 +1850,7 @@ The repo ships prebuilt, dependency-free `build/index.js` and `build/cli.js` bun
|
|
|
1818
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.
|
|
1819
1851
|
|
|
1820
1852
|
If installed from source, use this configuration:
|
|
1853
|
+
|
|
1821
1854
|
```json
|
|
1822
1855
|
{
|
|
1823
1856
|
"mcpServers": {
|
|
@@ -1839,11 +1872,11 @@ The entrypoint is written as:
|
|
|
1839
1872
|
"args": ["${CLAUDE_PROJECT_DIR:-.}/build/index.js"]
|
|
1840
1873
|
```
|
|
1841
1874
|
|
|
1842
|
-
`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.
|
|
1843
1876
|
|
|
1844
|
-
> **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.
|
|
1845
1878
|
|
|
1846
|
-
> **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.
|
|
1847
1880
|
|
|
1848
1881
|
---
|
|
1849
1882
|
|
|
@@ -1868,20 +1901,20 @@ The entrypoint is written as:
|
|
|
1868
1901
|
|
|
1869
1902
|
## Known Limitations
|
|
1870
1903
|
|
|
1871
|
-
| Limitation
|
|
1872
|
-
|
|
1873
|
-
| macOS only
|
|
1874
|
-
| MCP `send-email` is plain-text
|
|
1875
|
-
| Attachment read path restrictions
|
|
1876
|
-
| Smart mailboxes need Mail quit
|
|
1877
|
-
| Very large mailboxes not searchable
|
|
1878
|
-
| Can't delete/rename server-side mailboxes or mutate drafts
|
|
1879
|
-
| Message ID format
|
|
1880
|
-
| Batch size cap
|
|
1881
|
-
| Date filter format
|
|
1882
|
-
| Attachment save path restrictions
|
|
1883
|
-
| Attachment count limit
|
|
1884
|
-
| 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 |
|
|
1885
1918
|
|
|
1886
1919
|
### Mail.app `<blockquote>` wrapping on macOS 15+ (workaround in v1.6.0)
|
|
1887
1920
|
|
|
@@ -1947,11 +1980,13 @@ In a JSON string literal, `\\` — two characters — denotes **one** literal ba
|
|
|
1947
1980
|
## Troubleshooting
|
|
1948
1981
|
|
|
1949
1982
|
### "Mail.app not responding"
|
|
1983
|
+
|
|
1950
1984
|
- Ensure Mail.app is not frozen
|
|
1951
1985
|
- Try opening Mail.app manually
|
|
1952
1986
|
- Restart the MCP server
|
|
1953
1987
|
|
|
1954
1988
|
### "Permission denied"
|
|
1989
|
+
|
|
1955
1990
|
- macOS needs automation permission
|
|
1956
1991
|
- Go to System Settings > Privacy & Security > Automation
|
|
1957
1992
|
- Ensure your terminal/Claude has permission to control Mail
|
|
@@ -1959,17 +1994,19 @@ In a JSON string literal, `\\` — two characters — denotes **one** literal ba
|
|
|
1959
1994
|
language — `Not authorised to send Apple events to Mail. (-1743)` on en-GB/en-AU/en-IE,
|
|
1960
1995
|
and fully translated on fr/de/es. Before **v2.17.10** the server didn't recognise
|
|
1961
1996
|
those spellings, so `health-check`/`doctor` reported `permissions: ok` and then blamed
|
|
1962
|
-
missing accounts (
|
|
1997
|
+
missing accounts (_"No Mail accounts found. Set up an account in Mail.app first."_).
|
|
1963
1998
|
If you see that on a Mac whose Mail accounts are already configured, upgrade and
|
|
1964
1999
|
re-run `doctor`.
|
|
1965
2000
|
|
|
1966
2001
|
### "Message not found"
|
|
2002
|
+
|
|
1967
2003
|
- Message may have been deleted or moved
|
|
1968
2004
|
- Message IDs change if the message is moved between mailboxes
|
|
1969
2005
|
- Use `search-messages` to find the current message ID
|
|
1970
2006
|
|
|
1971
2007
|
### "... is present in more than one mailbox"
|
|
1972
|
-
|
|
2008
|
+
|
|
2009
|
+
- A bare numeric ID identifies a message only _within a mailbox_, and a label store (Gmail, iCloud)
|
|
1973
2010
|
reports the same message under the same ID in `INBOX`, `Important` and `All Mail` at once. The
|
|
1974
2011
|
server refuses rather than guessing which copy you meant.
|
|
1975
2012
|
- Fix it by running `list-messages`/`search-messages` on the mailbox you actually want to act on,
|
|
@@ -1978,29 +2015,34 @@ In a JSON string literal, `\\` — two characters — denotes **one** literal ba
|
|
|
1978
2015
|
by hand). `imap:…` IDs encode their own mailbox and never hit this.
|
|
1979
2016
|
|
|
1980
2017
|
### `search-messages` says "Partial results" or skips a mailbox
|
|
2018
|
+
|
|
1981
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.
|
|
1982
2020
|
- To search inside one, scope the call with `mailbox` **and** a `dateFrom`/`dateTo` window.
|
|
1983
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.
|
|
1984
2022
|
- A `Partial results` warning means coverage was incomplete; it is **not** a confirmed "no such mail."
|
|
1985
2023
|
|
|
1986
2024
|
### "Account not found"
|
|
2025
|
+
|
|
1987
2026
|
- Account names must match exactly (case-sensitive)
|
|
1988
2027
|
- Use `list-accounts` to see exact account names
|
|
1989
2028
|
|
|
1990
2029
|
### "Failed to send email"
|
|
2030
|
+
|
|
1991
2031
|
- Check your network connection
|
|
1992
2032
|
- Verify Mail.app can send emails manually
|
|
1993
2033
|
- Check if the account is configured correctly in Mail.app
|
|
1994
2034
|
|
|
1995
2035
|
### "invalid outputSchema … unsupported dialect" — every tool is refused
|
|
2036
|
+
|
|
1996
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**.
|
|
1997
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).
|
|
1998
2039
|
- Nothing to configure — restart your host app after upgrading so it re-reads the tool list.
|
|
1999
2040
|
|
|
2000
2041
|
### `apple-mail` server fails to connect when run from a clone
|
|
2042
|
+
|
|
2001
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.
|
|
2002
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.
|
|
2003
|
-
- 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.
|
|
2004
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).
|
|
2005
2047
|
|
|
2006
2048
|
---
|