apple-mail-mcp 1.5.7 → 1.6.1
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 +83 -1
- package/build/index.js +44 -7
- package/build/services/appleMailManager.d.ts +47 -1
- package/build/services/appleMailManager.d.ts.map +1 -1
- package/build/services/appleMailManager.js +230 -59
- package/build/services/smtpMailer.d.ts +85 -0
- package/build/services/smtpMailer.d.ts.map +1 -0
- package/build/services/smtpMailer.js +172 -0
- package/build/types.d.ts +33 -0
- package/build/types.d.ts.map +1 -1
- package/package.json +3 -1
package/README.md
CHANGED
|
@@ -149,6 +149,27 @@ Search for messages matching criteria. Searches all accounts by default.
|
|
|
149
149
|
| `dateTo` | string | No | End date filter (e.g., "March 1, 2026") |
|
|
150
150
|
| `limit` | number | No | Max results (default: 50) |
|
|
151
151
|
|
|
152
|
+
**Large mailboxes & partial results.** Apple Mail's AppleScript bridge cannot
|
|
153
|
+
search very large IMAP/Gmail mailboxes (tens of thousands of messages) before
|
|
154
|
+
the Apple Event times out — empirically even reading the newest 20 messages of
|
|
155
|
+
a 44k-message mailbox takes ~45s. To avoid burning minutes only to return a
|
|
156
|
+
misleading empty result, an unscoped (all-mailboxes) search **skips** mailboxes
|
|
157
|
+
whose message count exceeds a threshold (default **5000**), enforces a
|
|
158
|
+
per-account time budget, and **reports** anything it skipped or that timed out
|
|
159
|
+
rather than silently returning nothing. When coverage is incomplete the result
|
|
160
|
+
includes an explicit warning, e.g.:
|
|
161
|
+
|
|
162
|
+
```
|
|
163
|
+
⚠️ Partial results — this is NOT a confirmed "no such mail":
|
|
164
|
+
- skipped mailbox(es) too large to search via AppleScript: Gmail / All Mail (44287) — scope the search with `mailbox` + a `dateFrom`/`dateTo` window to target them
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
To search inside a large mailbox, scope the call with `mailbox` (and ideally a
|
|
168
|
+
`dateFrom`/`dateTo` window). Tune or disable the skip threshold with the
|
|
169
|
+
`APPLE_MAIL_MAX_SEARCH_MAILBOX` environment variable (default `5000`; set to `0`
|
|
170
|
+
to disable the guard and attempt every mailbox regardless of size).
|
|
171
|
+
([#24](https://github.com/sweetrb/apple-mail-mcp/issues/24))
|
|
172
|
+
|
|
152
173
|
---
|
|
153
174
|
|
|
154
175
|
#### `get-message`
|
|
@@ -192,8 +213,9 @@ Send a new email immediately.
|
|
|
192
213
|
| `body` | string | Yes | Email body (plain text) |
|
|
193
214
|
| `cc` | string[] | No | CC recipients |
|
|
194
215
|
| `bcc` | string[] | No | BCC recipients |
|
|
195
|
-
| `account` | string | No | Send from specific account |
|
|
216
|
+
| `account` | string | No | Send from specific account (with `transport: "smtp"`, overrides the From address) |
|
|
196
217
|
| `attachments` | string[] | No | Absolute file paths to attach, max 20 files (e.g., `["/Users/me/report.pdf"]`) |
|
|
218
|
+
| `transport` | `"applescript"` \| `"smtp"` | No | Send transport (default `"applescript"`). Use `"smtp"` to send clean MIME directly, avoiding the macOS 15+ Mail.app `<blockquote>` wrapping — see [SMTP transport](#smtp-transport) |
|
|
197
219
|
|
|
198
220
|
**Example:**
|
|
199
221
|
```json
|
|
@@ -206,6 +228,48 @@ Send a new email immediately.
|
|
|
206
228
|
}
|
|
207
229
|
```
|
|
208
230
|
|
|
231
|
+
##### SMTP transport
|
|
232
|
+
|
|
233
|
+
On macOS 15+ (Sequoia/Tahoe), Mail.app wraps any AppleScript-injected body in
|
|
234
|
+
`<blockquote type="cite">` under the `Apple-Mail-URLShareWrapperClass` template,
|
|
235
|
+
so emails sent through the default `applescript` transport render to recipients
|
|
236
|
+
as if they were quoted/forwarded (Apple radar **FB11734014**, open since
|
|
237
|
+
Ventura). Passing `transport: "smtp"` bypasses Mail.app entirely and submits
|
|
238
|
+
clean MIME via SMTP.
|
|
239
|
+
|
|
240
|
+
Configure SMTP via environment variables on the MCP server. The password is
|
|
241
|
+
read from the macOS **Keychain** by default, so no secret goes in config:
|
|
242
|
+
|
|
243
|
+
| Variable | Required | Default | Description |
|
|
244
|
+
|----------|----------|---------|-------------|
|
|
245
|
+
| `APPLE_MAIL_MCP_SMTP_HOST` | Yes | — | SMTP server hostname (e.g. `smtp.fastmail.com`) |
|
|
246
|
+
| `APPLE_MAIL_MCP_SMTP_USER` | Yes | — | SMTP username |
|
|
247
|
+
| `APPLE_MAIL_MCP_SMTP_PORT` | No | `465` if secure, else `587` | SMTP port |
|
|
248
|
+
| `APPLE_MAIL_MCP_SMTP_SECURE` | No | `false` | `true` for implicit TLS (port 465); otherwise STARTTLS |
|
|
249
|
+
| `APPLE_MAIL_MCP_SMTP_FROM` | No | = user | From address |
|
|
250
|
+
| `APPLE_MAIL_MCP_SMTP_PASSWORD` | No | — | Password (if set, used instead of the Keychain) |
|
|
251
|
+
| `APPLE_MAIL_MCP_SMTP_KEYCHAIN_SERVICE` | No | = host | Keychain item service/server name |
|
|
252
|
+
| `APPLE_MAIL_MCP_SMTP_KEYCHAIN_ACCOUNT` | No | = user | Keychain item account |
|
|
253
|
+
|
|
254
|
+
Store the password in the Keychain once (an app-specific password for Gmail/
|
|
255
|
+
iCloud), e.g.:
|
|
256
|
+
|
|
257
|
+
```bash
|
|
258
|
+
security add-internet-password -s smtp.fastmail.com -a you@example.com -w
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
Then send:
|
|
262
|
+
```json
|
|
263
|
+
{
|
|
264
|
+
"to": ["colleague@company.com"],
|
|
265
|
+
"subject": "Standings",
|
|
266
|
+
"body": "Plain body — no blockquote wrapping.",
|
|
267
|
+
"transport": "smtp"
|
|
268
|
+
}
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
The default `applescript` transport is unchanged; SMTP is opt-in per call.
|
|
272
|
+
|
|
209
273
|
---
|
|
210
274
|
|
|
211
275
|
#### `send-serial-email`
|
|
@@ -735,6 +799,7 @@ The entrypoint is written as:
|
|
|
735
799
|
| No sending HTML email | Emails are sent as plain text; reading HTML content is supported |
|
|
736
800
|
| Attachments require absolute paths | File attachments must use full absolute paths (e.g., `/Users/me/file.pdf`) |
|
|
737
801
|
| No smart mailboxes | Cannot access Smart Mailboxes via AppleScript |
|
|
802
|
+
| Very large mailboxes not searchable | 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 to search inside one. ([#24](https://github.com/sweetrb/apple-mail-mcp/issues/24)) |
|
|
738
803
|
| In-memory templates | Email templates are not persisted across server restarts |
|
|
739
804
|
| Numeric-only message IDs | Message IDs must contain only digits (validated by schema) |
|
|
740
805
|
| Batch size cap | Batch operations are limited to 100 messages per request |
|
|
@@ -742,6 +807,17 @@ The entrypoint is written as:
|
|
|
742
807
|
| Attachment save path restrictions | `save-attachment` only allows saving to home directory, `/tmp`, `/private/tmp`, and `/Volumes`; path traversal is blocked |
|
|
743
808
|
| Attachment count limit | `send-email` and `create-draft` accept a maximum of 20 file attachments |
|
|
744
809
|
|
|
810
|
+
### Mail.app `<blockquote>` wrapping on macOS 15+ (workaround in v1.6.0)
|
|
811
|
+
|
|
812
|
+
On macOS 15+ Mail.app wraps AppleScript-injected message bodies in
|
|
813
|
+
`<blockquote type="cite">` under the `Apple-Mail-URLShareWrapperClass` template,
|
|
814
|
+
so mail sent via the default `applescript` transport renders to recipients as
|
|
815
|
+
quoted/forwarded content (Apple radar **FB11734014**, open since Ventura, no
|
|
816
|
+
fix). Since v1.6.0, `send-email` accepts `transport: "smtp"` to bypass Mail.app
|
|
817
|
+
and send clean MIME directly — see [SMTP transport](#smtp-transport). The
|
|
818
|
+
AppleScript path is still the default and still exhibits Apple's wrapping.
|
|
819
|
+
([#12](https://github.com/sweetrb/apple-mail-mcp/issues/12))
|
|
820
|
+
|
|
745
821
|
### Reply / Forward from Background Processes (Fixed in v1.4.0)
|
|
746
822
|
|
|
747
823
|
Prior to v1.4.0, `reply-to-message` and `forward-message` would send messages with **empty body text** when the MCP server ran as a background process (e.g., spawned via `execSync` from Node.js, which is how Claude Code invokes it).
|
|
@@ -795,6 +871,12 @@ The `\\\\` in JSON becomes `\\` in the actual string, which represents a single
|
|
|
795
871
|
- Message IDs change if the message is moved between mailboxes
|
|
796
872
|
- Use `search-messages` to find the current message ID
|
|
797
873
|
|
|
874
|
+
### `search-messages` says "Partial results" or skips a mailbox
|
|
875
|
+
- 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.
|
|
876
|
+
- To search inside one, scope the call with `mailbox` **and** a `dateFrom`/`dateTo` window.
|
|
877
|
+
- 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.
|
|
878
|
+
- A `Partial results` warning means coverage was incomplete; it is **not** a confirmed "no such mail."
|
|
879
|
+
|
|
798
880
|
### "Account not found"
|
|
799
881
|
- Account names must match exactly (case-sensitive)
|
|
800
882
|
- Use `list-accounts` to see exact account names
|
package/build/index.js
CHANGED
|
@@ -24,6 +24,7 @@ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
|
24
24
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
25
25
|
import { z } from "zod";
|
|
26
26
|
import { AppleMailManager } from "./services/appleMailManager.js";
|
|
27
|
+
import { sendViaSmtp } from "./services/smtpMailer.js";
|
|
27
28
|
import { createSerialGate } from "./utils/serialize.js";
|
|
28
29
|
// =============================================================================
|
|
29
30
|
// Shared Validation Schemas
|
|
@@ -97,12 +98,14 @@ const serializeAppleScript = createSerialGate();
|
|
|
97
98
|
/**
|
|
98
99
|
* Wraps a tool handler with consistent error handling, serialized through the
|
|
99
100
|
* AppleScript gate so concurrent MCP tool calls don't race into Mail.app (#11).
|
|
101
|
+
* Handlers may be synchronous or async (the SMTP send path in send-email is
|
|
102
|
+
* async), so the handler result is awaited inside the gate.
|
|
100
103
|
*/
|
|
101
104
|
function withErrorHandling(handler, errorPrefix) {
|
|
102
105
|
return async (params) => {
|
|
103
|
-
return serializeAppleScript(() => {
|
|
106
|
+
return serializeAppleScript(async () => {
|
|
104
107
|
try {
|
|
105
|
-
return handler(params);
|
|
108
|
+
return await handler(params);
|
|
106
109
|
}
|
|
107
110
|
catch (error) {
|
|
108
111
|
const message = error instanceof Error ? error.message : "Unknown error";
|
|
@@ -133,14 +136,35 @@ server.tool("search-messages", {
|
|
|
133
136
|
dateTo: DATE_FILTER_SCHEMA.describe("End date filter (e.g., 'March 1, 2026')"),
|
|
134
137
|
limit: z.number().optional().describe("Maximum number of results (default: 50)"),
|
|
135
138
|
}, withErrorHandling(({ query, mailbox, account, limit = 50, dateFrom, dateTo, from, subject, isRead, isFlagged, }) => {
|
|
136
|
-
const messages = mailManager.
|
|
139
|
+
const { messages, diagnostics } = mailManager.searchMessagesWithDiagnostics(query, mailbox, account, limit, dateFrom, dateTo, from, subject, isRead, isFlagged);
|
|
140
|
+
// Build a coverage note when the search couldn't reach everything, so a
|
|
141
|
+
// caller never mistakes an incomplete search for a confirmed "no matches"
|
|
142
|
+
// (issue #24).
|
|
143
|
+
const coverageNotes = [];
|
|
144
|
+
if (diagnostics.timedOutAccounts.length > 0) {
|
|
145
|
+
coverageNotes.push(`timed out (no results) for account(s): ${diagnostics.timedOutAccounts.join(", ")}`);
|
|
146
|
+
}
|
|
147
|
+
if (diagnostics.skippedLargeMailboxes.length > 0) {
|
|
148
|
+
coverageNotes.push(`skipped mailbox(es) too large to search via AppleScript: ${diagnostics.skippedLargeMailboxes.join(", ")} — scope the search with \`mailbox\` + a \`dateFrom\`/\`dateTo\` window to target them`);
|
|
149
|
+
}
|
|
150
|
+
if (diagnostics.notSearchedMailboxes.length > 0) {
|
|
151
|
+
coverageNotes.push(`could not finish searching mailbox(es): ${diagnostics.notSearchedMailboxes.join(", ")}`);
|
|
152
|
+
}
|
|
153
|
+
const coverageBlock = coverageNotes.length > 0
|
|
154
|
+
? `\n\n⚠️ Partial results — this is NOT a confirmed "no such mail":\n${coverageNotes
|
|
155
|
+
.map((n) => ` - ${n}`)
|
|
156
|
+
.join("\n")}`
|
|
157
|
+
: "";
|
|
137
158
|
if (messages.length === 0) {
|
|
138
|
-
|
|
159
|
+
const base = diagnostics.partial
|
|
160
|
+
? "No messages found in the portions that were searched."
|
|
161
|
+
: "No messages found matching criteria";
|
|
162
|
+
return successResponse(`${base}${coverageBlock}`);
|
|
139
163
|
}
|
|
140
164
|
const messageList = messages
|
|
141
165
|
.map((m) => ` - ID: ${m.id} | ${m.dateReceived.toLocaleDateString()} | ${m.subject} (from: ${m.sender}) [${m.isRead ? "read" : "unread"}]`)
|
|
142
166
|
.join("\n");
|
|
143
|
-
return successResponse(`Found ${messages.length} message(s):\n${messageList}`);
|
|
167
|
+
return successResponse(`Found ${messages.length} message(s):\n${messageList}${coverageBlock}`);
|
|
144
168
|
}, "Error searching messages"));
|
|
145
169
|
// --- get-message ---
|
|
146
170
|
server.tool("get-message", {
|
|
@@ -190,12 +214,25 @@ server.tool("send-email", {
|
|
|
190
214
|
.max(20, "Cannot attach more than 20 files")
|
|
191
215
|
.optional()
|
|
192
216
|
.describe("Absolute file paths to attach (e.g., ['/Users/me/report.pdf'])"),
|
|
193
|
-
|
|
217
|
+
transport: z
|
|
218
|
+
.enum(["applescript", "smtp"])
|
|
219
|
+
.optional()
|
|
220
|
+
.describe("Send transport. 'applescript' (default) sends through Mail.app. " +
|
|
221
|
+
"'smtp' submits clean MIME directly via SMTP, avoiding the macOS 15+ " +
|
|
222
|
+
"Mail.app <blockquote> wrapping (issue #12); requires APPLE_MAIL_MCP_SMTP_* env config."),
|
|
223
|
+
}, withErrorHandling(async ({ to, subject, body, cc, bcc, account, attachments, transport }) => {
|
|
224
|
+
const attachInfo = attachments?.length ? ` with ${attachments.length} attachment(s)` : "";
|
|
225
|
+
if (transport === "smtp") {
|
|
226
|
+
const result = await sendViaSmtp({ to, subject, body, cc, bcc, from: account, attachments });
|
|
227
|
+
if (!result.success) {
|
|
228
|
+
return errorResponse(result.error ?? "Failed to send email via SMTP.");
|
|
229
|
+
}
|
|
230
|
+
return successResponse(`Email sent via SMTP to ${to.join(", ")}${attachInfo}`);
|
|
231
|
+
}
|
|
194
232
|
const success = mailManager.sendEmail(to, subject, body, cc, bcc, account, attachments);
|
|
195
233
|
if (!success) {
|
|
196
234
|
return errorResponse("Failed to send email. Check Mail.app configuration.");
|
|
197
235
|
}
|
|
198
|
-
const attachInfo = attachments?.length ? ` with ${attachments.length} attachment(s)` : "";
|
|
199
236
|
return successResponse(`Email sent to ${to.join(", ")}${attachInfo}`);
|
|
200
237
|
}, "Error sending email"));
|
|
201
238
|
// --- send-serial-email ---
|
|
@@ -12,7 +12,29 @@
|
|
|
12
12
|
*
|
|
13
13
|
* @module services/appleMailManager
|
|
14
14
|
*/
|
|
15
|
-
import type { Message, MessageContent, Mailbox, Account, Attachment, HealthCheckResult, MailStats, BatchOperationResult, SyncStatus, RecentlyReceivedStats, MailRule, Contact, EmailTemplate, SerialEmailRecipient, SerialEmailResult } from "../types.js";
|
|
15
|
+
import type { Message, MessageContent, Mailbox, Account, Attachment, HealthCheckResult, MailStats, BatchOperationResult, SyncStatus, RecentlyReceivedStats, MailRule, Contact, EmailTemplate, SerialEmailRecipient, SerialEmailResult, SearchDiagnostics, SearchResult } from "../types.js";
|
|
16
|
+
/**
|
|
17
|
+
* Merge a per-account SearchDiagnostics into an aggregate (all-accounts) one.
|
|
18
|
+
*
|
|
19
|
+
* Exported for unit testing.
|
|
20
|
+
*/
|
|
21
|
+
export declare function mergeSearchDiagnostics(into: SearchDiagnostics, from: SearchDiagnostics): void;
|
|
22
|
+
/**
|
|
23
|
+
* Split a per-account search payload into its message-list portion and parsed
|
|
24
|
+
* diagnostics. The AppleScript appends a trailer of the form:
|
|
25
|
+
*
|
|
26
|
+
* <messages>|||DIAG|||timedOut=true|||F|||skipped=Foo (9000)|||M||||||F|||notSearched=Bar|||M|||
|
|
27
|
+
*
|
|
28
|
+
* `skipped`/`notSearched` are `|||M|||`-separated mailbox names, each prefixed
|
|
29
|
+
* with the account name on the way out so the aggregate result is unambiguous.
|
|
30
|
+
*
|
|
31
|
+
* Exported (pure, no Mail.app dependency) for unit testing — this is the logic
|
|
32
|
+
* that turns a swallowed timeout into a visible partial result (issue #24).
|
|
33
|
+
*/
|
|
34
|
+
export declare function splitSearchDiagnostics(output: string, account: string): {
|
|
35
|
+
payload: string;
|
|
36
|
+
diagnostics: SearchDiagnostics;
|
|
37
|
+
};
|
|
16
38
|
/**
|
|
17
39
|
* Emits AppleScript that builds a date into the variable `varName` from numeric
|
|
18
40
|
* components.
|
|
@@ -127,6 +149,30 @@ export declare class AppleMailManager {
|
|
|
127
149
|
* @returns Array of matching messages
|
|
128
150
|
*/
|
|
129
151
|
searchMessages(query?: string, mailbox?: string, account?: string, limit?: number, dateFrom?: string, dateTo?: string, from?: string, subject?: string, isRead?: boolean, isFlagged?: boolean): Message[];
|
|
152
|
+
/**
|
|
153
|
+
* Search for messages, returning both the matches and diagnostics describing
|
|
154
|
+
* how complete the search was.
|
|
155
|
+
*
|
|
156
|
+
* This is the correctness fix for issue #24. The previous implementation ran
|
|
157
|
+
* an unbounded `messages of mb whose <predicate>` over every mailbox in an
|
|
158
|
+
* account; on large IMAP/Gmail mailboxes (tens of thousands of messages) that
|
|
159
|
+
* single Apple Event exceeded the timeout, the error was swallowed by a `try`,
|
|
160
|
+
* and the function returned a clean — but wrong — empty result. Callers/agents
|
|
161
|
+
* then confidently reported "no such mail."
|
|
162
|
+
*
|
|
163
|
+
* Two changes fix that:
|
|
164
|
+
* 1. Cheap count-guard: mailboxes larger than the scan threshold are skipped
|
|
165
|
+
* (Apple Mail can't search them before timing out anyway) and reported.
|
|
166
|
+
* 2. Honest diagnostics: per-account/per-mailbox timeouts are surfaced as a
|
|
167
|
+
* `partial` result with the affected scopes named, instead of an empty
|
|
168
|
+
* "success."
|
|
169
|
+
*/
|
|
170
|
+
searchMessagesWithDiagnostics(query?: string, mailbox?: string, account?: string, limit?: number, dateFrom?: string, dateTo?: string, from?: string, subject?: string, isRead?: boolean, isFlagged?: boolean): SearchResult;
|
|
171
|
+
/**
|
|
172
|
+
* Split a per-account search payload into its message list and the DIAG
|
|
173
|
+
* trailer, parse both, and return a SearchResult. See searchMessagesWithDiagnostics.
|
|
174
|
+
*/
|
|
175
|
+
private parseSearchResult;
|
|
130
176
|
/**
|
|
131
177
|
* Get a message by ID.
|
|
132
178
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"appleMailManager.d.ts","sourceRoot":"","sources":["../../src/services/appleMailManager.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAQH,OAAO,KAAK,EACV,OAAO,EACP,cAAc,EACd,OAAO,EACP,OAAO,EACP,UAAU,EACV,iBAAiB,EACjB,SAAS,EAET,oBAAoB,EACpB,UAAU,EACV,qBAAqB,EACrB,QAAQ,EACR,OAAO,EACP,aAAa,EACb,oBAAoB,EACpB,iBAAiB,
|
|
1
|
+
{"version":3,"file":"appleMailManager.d.ts","sourceRoot":"","sources":["../../src/services/appleMailManager.ts"],"names":[],"mappings":"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"}
|
|
@@ -19,6 +19,94 @@ import { homedir } from "os";
|
|
|
19
19
|
import { executeAppleScript } from "../utils/applescript.js";
|
|
20
20
|
import { parseMimeAttachments, extractMimeAttachment } from "../utils/mimeParse.js";
|
|
21
21
|
// =============================================================================
|
|
22
|
+
// Search Tuning (issue #24)
|
|
23
|
+
// =============================================================================
|
|
24
|
+
/**
|
|
25
|
+
* Mailboxes larger than this are skipped during an unscoped (all-mailboxes)
|
|
26
|
+
* search rather than scanned. Apple Mail's AppleScript bridge cannot search a
|
|
27
|
+
* mailbox of this size before the Apple Event timeout fires — empirically even
|
|
28
|
+
* reading the newest 20 messages of a 44k-message Gmail mailbox took ~47s — so
|
|
29
|
+
* attempting it only burns the time budget and yields a misleading empty
|
|
30
|
+
* result. `count of messages` is cheap (Mail keeps it cached), so the guard is
|
|
31
|
+
* effectively free. The skipped mailboxes are reported back to the caller.
|
|
32
|
+
*
|
|
33
|
+
* Override with APPLE_MAIL_MAX_SEARCH_MAILBOX (set to 0 to disable the guard).
|
|
34
|
+
*/
|
|
35
|
+
function getMailboxScanThreshold() {
|
|
36
|
+
const raw = process.env.APPLE_MAIL_MAX_SEARCH_MAILBOX;
|
|
37
|
+
if (raw !== undefined) {
|
|
38
|
+
const n = Number(raw);
|
|
39
|
+
if (Number.isFinite(n) && n >= 0)
|
|
40
|
+
return n;
|
|
41
|
+
}
|
|
42
|
+
return 5000;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Per-account wall-clock budget (seconds) enforced *inside* the AppleScript so
|
|
46
|
+
* a single account can't consume minutes. Kept comfortably below the osascript
|
|
47
|
+
* process timeout (see searchMessages) so the script exits and reports a
|
|
48
|
+
* partial result rather than being SIGKILLed with no diagnostics.
|
|
49
|
+
*/
|
|
50
|
+
const SEARCH_ACCOUNT_BUDGET_SECONDS = 30;
|
|
51
|
+
/** osascript process timeout for a per-account search (ms). */
|
|
52
|
+
const SEARCH_ACCOUNT_TIMEOUT_MS = 45000;
|
|
53
|
+
/** Separator between the message payload and the diagnostics trailer. */
|
|
54
|
+
const DIAG_MARKER = "|||DIAG|||";
|
|
55
|
+
/**
|
|
56
|
+
* Merge a per-account SearchDiagnostics into an aggregate (all-accounts) one.
|
|
57
|
+
*
|
|
58
|
+
* Exported for unit testing.
|
|
59
|
+
*/
|
|
60
|
+
export function mergeSearchDiagnostics(into, from) {
|
|
61
|
+
into.timedOutAccounts.push(...from.timedOutAccounts);
|
|
62
|
+
into.skippedLargeMailboxes.push(...from.skippedLargeMailboxes);
|
|
63
|
+
into.notSearchedMailboxes.push(...from.notSearchedMailboxes);
|
|
64
|
+
if (from.partial)
|
|
65
|
+
into.partial = true;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Split a per-account search payload into its message-list portion and parsed
|
|
69
|
+
* diagnostics. The AppleScript appends a trailer of the form:
|
|
70
|
+
*
|
|
71
|
+
* <messages>|||DIAG|||timedOut=true|||F|||skipped=Foo (9000)|||M||||||F|||notSearched=Bar|||M|||
|
|
72
|
+
*
|
|
73
|
+
* `skipped`/`notSearched` are `|||M|||`-separated mailbox names, each prefixed
|
|
74
|
+
* with the account name on the way out so the aggregate result is unambiguous.
|
|
75
|
+
*
|
|
76
|
+
* Exported (pure, no Mail.app dependency) for unit testing — this is the logic
|
|
77
|
+
* that turns a swallowed timeout into a visible partial result (issue #24).
|
|
78
|
+
*/
|
|
79
|
+
export function splitSearchDiagnostics(output, account) {
|
|
80
|
+
const markerIdx = output.lastIndexOf(DIAG_MARKER);
|
|
81
|
+
const payload = markerIdx >= 0 ? output.slice(0, markerIdx) : output;
|
|
82
|
+
const trailer = markerIdx >= 0 ? output.slice(markerIdx + DIAG_MARKER.length) : "";
|
|
83
|
+
const diagnostics = {
|
|
84
|
+
partial: false,
|
|
85
|
+
timedOutAccounts: [],
|
|
86
|
+
skippedLargeMailboxes: [],
|
|
87
|
+
notSearchedMailboxes: [],
|
|
88
|
+
};
|
|
89
|
+
if (trailer) {
|
|
90
|
+
const fields = trailer.split("|||F|||");
|
|
91
|
+
const getField = (key) => {
|
|
92
|
+
const f = fields.find((x) => x.startsWith(`${key}=`));
|
|
93
|
+
return f ? f.slice(key.length + 1) : "";
|
|
94
|
+
};
|
|
95
|
+
const splitList = (raw) => raw
|
|
96
|
+
.split("|||M|||")
|
|
97
|
+
.map((s) => s.trim())
|
|
98
|
+
.filter((s) => s.length > 0);
|
|
99
|
+
diagnostics.skippedLargeMailboxes = splitList(getField("skipped")).map((mb) => `${account} / ${mb}`);
|
|
100
|
+
diagnostics.notSearchedMailboxes = splitList(getField("notSearched")).map((mb) => `${account} / ${mb}`);
|
|
101
|
+
if (getField("timedOut") === "true")
|
|
102
|
+
diagnostics.partial = true;
|
|
103
|
+
}
|
|
104
|
+
if (diagnostics.skippedLargeMailboxes.length > 0 || diagnostics.notSearchedMailboxes.length > 0) {
|
|
105
|
+
diagnostics.partial = true;
|
|
106
|
+
}
|
|
107
|
+
return { payload, diagnostics };
|
|
108
|
+
}
|
|
109
|
+
// =============================================================================
|
|
22
110
|
// Text Processing Utilities
|
|
23
111
|
// =============================================================================
|
|
24
112
|
/**
|
|
@@ -336,18 +424,46 @@ export class AppleMailManager {
|
|
|
336
424
|
* @returns Array of matching messages
|
|
337
425
|
*/
|
|
338
426
|
searchMessages(query, mailbox, account, limit = 50, dateFrom, dateTo, from, subject, isRead, isFlagged) {
|
|
339
|
-
|
|
427
|
+
return this.searchMessagesWithDiagnostics(query, mailbox, account, limit, dateFrom, dateTo, from, subject, isRead, isFlagged).messages;
|
|
428
|
+
}
|
|
429
|
+
/**
|
|
430
|
+
* Search for messages, returning both the matches and diagnostics describing
|
|
431
|
+
* how complete the search was.
|
|
432
|
+
*
|
|
433
|
+
* This is the correctness fix for issue #24. The previous implementation ran
|
|
434
|
+
* an unbounded `messages of mb whose <predicate>` over every mailbox in an
|
|
435
|
+
* account; on large IMAP/Gmail mailboxes (tens of thousands of messages) that
|
|
436
|
+
* single Apple Event exceeded the timeout, the error was swallowed by a `try`,
|
|
437
|
+
* and the function returned a clean — but wrong — empty result. Callers/agents
|
|
438
|
+
* then confidently reported "no such mail."
|
|
439
|
+
*
|
|
440
|
+
* Two changes fix that:
|
|
441
|
+
* 1. Cheap count-guard: mailboxes larger than the scan threshold are skipped
|
|
442
|
+
* (Apple Mail can't search them before timing out anyway) and reported.
|
|
443
|
+
* 2. Honest diagnostics: per-account/per-mailbox timeouts are surfaced as a
|
|
444
|
+
* `partial` result with the affected scopes named, instead of an empty
|
|
445
|
+
* "success."
|
|
446
|
+
*/
|
|
447
|
+
searchMessagesWithDiagnostics(query, mailbox, account, limit = 50, dateFrom, dateTo, from, subject, isRead, isFlagged) {
|
|
448
|
+
// If no account specified, search across all accounts and merge diagnostics.
|
|
340
449
|
if (!account) {
|
|
341
450
|
const accounts = this.listAccounts();
|
|
342
451
|
const allMessages = [];
|
|
452
|
+
const diagnostics = {
|
|
453
|
+
partial: false,
|
|
454
|
+
timedOutAccounts: [],
|
|
455
|
+
skippedLargeMailboxes: [],
|
|
456
|
+
notSearchedMailboxes: [],
|
|
457
|
+
};
|
|
343
458
|
for (const acct of accounts) {
|
|
344
459
|
if (allMessages.length >= limit)
|
|
345
460
|
break;
|
|
346
461
|
const remaining = limit - allMessages.length;
|
|
347
|
-
const
|
|
348
|
-
allMessages.push(...
|
|
462
|
+
const res = this.searchMessagesWithDiagnostics(query, mailbox, acct.name, remaining, dateFrom, dateTo, from, subject, isRead, isFlagged);
|
|
463
|
+
allMessages.push(...res.messages);
|
|
464
|
+
mergeSearchDiagnostics(diagnostics, res.diagnostics);
|
|
349
465
|
}
|
|
350
|
-
return allMessages.slice(0, limit);
|
|
466
|
+
return { messages: allMessages.slice(0, limit), diagnostics };
|
|
351
467
|
}
|
|
352
468
|
const targetAccount = this.resolveAccount(account);
|
|
353
469
|
// `query` is a subject-OR-sender substring match; from/subject/isRead/isFlagged
|
|
@@ -379,79 +495,136 @@ export class AppleMailManager {
|
|
|
379
495
|
}
|
|
380
496
|
dateFilter = dateChecks.join(" and ");
|
|
381
497
|
}
|
|
498
|
+
const scanThreshold = getMailboxScanThreshold();
|
|
382
499
|
let searchCommand;
|
|
383
500
|
if (mailbox) {
|
|
384
|
-
// Search a specific mailbox
|
|
501
|
+
// Search a specific mailbox. The caller explicitly chose this mailbox, so
|
|
502
|
+
// we don't apply the count-guard skip — but we still wrap the scan so a
|
|
503
|
+
// timeout is reported as a partial result rather than a false empty.
|
|
385
504
|
const targetMailbox = this.resolveMailbox(mailbox, targetAccount);
|
|
386
505
|
searchCommand = `
|
|
387
506
|
${dateSetup}set outputText to ""
|
|
507
|
+
set _timedOut to false
|
|
508
|
+
set _notSearched to ""
|
|
388
509
|
set theMailbox to mailbox "${escapeForAppleScript(targetMailbox)}"
|
|
389
|
-
set allMessages to messages of theMailbox ${searchCondition}
|
|
390
510
|
set msgCount to 0
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
511
|
+
try
|
|
512
|
+
set allMessages to messages of theMailbox ${searchCondition}
|
|
513
|
+
repeat with msg in allMessages
|
|
514
|
+
if msgCount >= ${limit} then exit repeat
|
|
515
|
+
try
|
|
516
|
+
${dateFilter ? `set msgDate to date received of msg\n if not (${dateFilter}) then\n -- skip message outside date range\n else` : ""}
|
|
517
|
+
set msgId to id of msg as string
|
|
518
|
+
set msgSubject to subject of msg
|
|
519
|
+
set msgSender to sender of msg
|
|
520
|
+
set d to date received of msg
|
|
521
|
+
set msgDateStr to ${AS_DATE_TO_STRING}
|
|
522
|
+
set msgRead to read status of msg as string
|
|
523
|
+
set msgFlagged to flagged status of msg as string
|
|
524
|
+
if msgCount > 0 then set outputText to outputText & "|||ITEM|||"
|
|
525
|
+
set outputText to outputText & msgId & "|||" & msgSubject & "|||" & msgSender & "|||" & msgDateStr & "|||" & msgRead & "|||" & msgFlagged
|
|
526
|
+
set msgCount to msgCount + 1
|
|
527
|
+
${dateFilter ? "end if" : ""}
|
|
528
|
+
end try
|
|
529
|
+
end repeat
|
|
530
|
+
on error _errMsg number _errNum
|
|
531
|
+
set _timedOut to true
|
|
532
|
+
set _notSearched to "${escapeForAppleScript(targetMailbox)}|||M|||"
|
|
533
|
+
end try
|
|
534
|
+
return outputText & "${DIAG_MARKER}timedOut=" & (_timedOut as string) & "|||F|||skipped=|||F|||notSearched=" & _notSearched
|
|
409
535
|
`;
|
|
410
536
|
}
|
|
411
537
|
else {
|
|
412
|
-
// Search ALL mailboxes — iterate every mailbox in the account, dedup by
|
|
538
|
+
// Search ALL mailboxes — iterate every mailbox in the account, dedup by
|
|
539
|
+
// message ID. Skip mailboxes that exceed the scan threshold (they can't be
|
|
540
|
+
// searched before timing out), enforce a per-account wall-clock budget, and
|
|
541
|
+
// capture per-mailbox timeouts. All three are reported via the DIAG trailer.
|
|
542
|
+
const scanGuard = scanThreshold > 0 ? `mbCount > ${scanThreshold}` : "false";
|
|
413
543
|
searchCommand = `
|
|
414
544
|
${dateSetup}set outputText to ""
|
|
415
545
|
set msgCount to 0
|
|
416
546
|
set seenIds to {}
|
|
547
|
+
set _timedOut to false
|
|
548
|
+
set _skipped to ""
|
|
549
|
+
set _notSearched to ""
|
|
550
|
+
set _startedAt to current date
|
|
417
551
|
repeat with mb in mailboxes
|
|
418
552
|
if msgCount >= ${limit} then exit repeat
|
|
553
|
+
set mbName to ""
|
|
419
554
|
try
|
|
420
|
-
set
|
|
421
|
-
|
|
422
|
-
|
|
555
|
+
set mbName to name of mb
|
|
556
|
+
end try
|
|
557
|
+
if ((current date) - _startedAt) > ${SEARCH_ACCOUNT_BUDGET_SECONDS} then
|
|
558
|
+
set _timedOut to true
|
|
559
|
+
set _notSearched to _notSearched & mbName & "|||M|||"
|
|
560
|
+
else
|
|
561
|
+
set mbCount to 0
|
|
562
|
+
try
|
|
563
|
+
set mbCount to count of messages of mb
|
|
564
|
+
end try
|
|
565
|
+
if (${scanGuard}) then
|
|
566
|
+
set _timedOut to true
|
|
567
|
+
set _skipped to _skipped & mbName & " (" & (mbCount as string) & ")|||M|||"
|
|
568
|
+
else
|
|
423
569
|
try
|
|
424
|
-
set
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
570
|
+
set allMessages to messages of mb ${searchCondition}
|
|
571
|
+
repeat with msg in allMessages
|
|
572
|
+
if msgCount >= ${limit} then exit repeat
|
|
573
|
+
try
|
|
574
|
+
set msgId to id of msg as string
|
|
575
|
+
if seenIds does not contain msgId then
|
|
576
|
+
set end of seenIds to msgId
|
|
577
|
+
${dateFilter ? `set msgDate to date received of msg\n if not (${dateFilter}) then\n -- skip message outside date range\n else` : ""}
|
|
578
|
+
set msgSubject to subject of msg
|
|
579
|
+
set msgSender to sender of msg
|
|
580
|
+
set d to date received of msg
|
|
581
|
+
set msgDateStr to ${AS_DATE_TO_STRING}
|
|
582
|
+
set msgRead to read status of msg as string
|
|
583
|
+
set msgFlagged to flagged status of msg as string
|
|
584
|
+
if msgCount > 0 then set outputText to outputText & "|||ITEM|||"
|
|
585
|
+
set outputText to outputText & msgId & "|||" & msgSubject & "|||" & msgSender & "|||" & msgDateStr & "|||" & msgRead & "|||" & msgFlagged & "|||" & mbName
|
|
586
|
+
set msgCount to msgCount + 1
|
|
587
|
+
${dateFilter ? "end if" : ""}
|
|
588
|
+
end if
|
|
589
|
+
end try
|
|
590
|
+
end repeat
|
|
591
|
+
on error _errMsg number _errNum
|
|
592
|
+
set _timedOut to true
|
|
593
|
+
set _notSearched to _notSearched & mbName & "|||M|||"
|
|
439
594
|
end try
|
|
440
|
-
end
|
|
441
|
-
end
|
|
595
|
+
end if
|
|
596
|
+
end if
|
|
442
597
|
end repeat
|
|
443
|
-
return outputText
|
|
598
|
+
return outputText & "${DIAG_MARKER}timedOut=" & (_timedOut as string) & "|||F|||skipped=" & _skipped & "|||F|||notSearched=" & _notSearched
|
|
444
599
|
`;
|
|
445
600
|
}
|
|
446
601
|
const script = buildAccountScopedScript(targetAccount, searchCommand);
|
|
447
|
-
const result = executeAppleScript(script, { timeoutMs:
|
|
602
|
+
const result = executeAppleScript(script, { timeoutMs: SEARCH_ACCOUNT_TIMEOUT_MS });
|
|
448
603
|
if (!result.success) {
|
|
449
|
-
|
|
450
|
-
|
|
604
|
+
// Whole-account script failed (most often the osascript process timeout /
|
|
605
|
+
// SIGKILL on an unresponsive account). Surface it as a timeout rather than
|
|
606
|
+
// a false empty result — that confusion is the heart of issue #24.
|
|
607
|
+
console.error(`Failed to search messages in "${targetAccount}": ${result.error}`);
|
|
608
|
+
return {
|
|
609
|
+
messages: [],
|
|
610
|
+
diagnostics: {
|
|
611
|
+
partial: true,
|
|
612
|
+
timedOutAccounts: [targetAccount],
|
|
613
|
+
skippedLargeMailboxes: [],
|
|
614
|
+
notSearchedMailboxes: [],
|
|
615
|
+
},
|
|
616
|
+
};
|
|
451
617
|
}
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
618
|
+
return this.parseSearchResult(result.output, mailbox || "INBOX", targetAccount);
|
|
619
|
+
}
|
|
620
|
+
/**
|
|
621
|
+
* Split a per-account search payload into its message list and the DIAG
|
|
622
|
+
* trailer, parse both, and return a SearchResult. See searchMessagesWithDiagnostics.
|
|
623
|
+
*/
|
|
624
|
+
parseSearchResult(output, mailbox, account) {
|
|
625
|
+
const { payload, diagnostics } = splitSearchDiagnostics(output, account);
|
|
626
|
+
const messages = payload.trim() ? this.parseMessageList(payload, mailbox, account) : [];
|
|
627
|
+
return { messages, diagnostics };
|
|
455
628
|
}
|
|
456
629
|
/**
|
|
457
630
|
* Get a message by ID.
|
|
@@ -789,13 +962,11 @@ export class AppleMailManager {
|
|
|
789
962
|
// Discussion: https://forums.macrumors.com/threads/applescript-creating-a-
|
|
790
963
|
// new-message-in-mail-app-is-causing-weird-formatting-issues.2385052/
|
|
791
964
|
//
|
|
792
|
-
//
|
|
793
|
-
//
|
|
794
|
-
//
|
|
795
|
-
//
|
|
796
|
-
//
|
|
797
|
-
// 2. Switch to smtplib-style direct send with Keychain-stored creds.
|
|
798
|
-
// 3. Use Mail.app's NSSharingService rather than AppleScript.
|
|
965
|
+
// FIX (v1.6.0): send-email now accepts `transport: "smtp"`, which bypasses
|
|
966
|
+
// Mail.app and submits clean MIME directly via nodemailer (creds from the
|
|
967
|
+
// Keychain). See src/services/smtpMailer.ts. This AppleScript path remains
|
|
968
|
+
// the default for back-compat and for users who don't configure SMTP, so the
|
|
969
|
+
// wrapping behavior below is unchanged for them.
|
|
799
970
|
// Tracking issue: https://github.com/sweetrb/apple-mail-mcp/issues/12
|
|
800
971
|
// ───────────────────────────────────────────────────────────────────
|
|
801
972
|
sendEmail(to, subject, body, cc, bcc, account, attachments) {
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SMTP transport for sending mail (issue #12).
|
|
3
|
+
*
|
|
4
|
+
* Mail.app's AppleScript send path wraps any injected body in
|
|
5
|
+
* `<blockquote type="cite">` under the Apple-Mail-URLShareWrapperClass template
|
|
6
|
+
* on macOS 15+, so messages render to recipients as quoted/forwarded content
|
|
7
|
+
* (Apple radar FB11734014, open since Ventura). This module bypasses Mail.app
|
|
8
|
+
* entirely and submits clean MIME directly over SMTP via nodemailer.
|
|
9
|
+
*
|
|
10
|
+
* Connection settings come from environment variables; the password is read
|
|
11
|
+
* from the macOS Keychain via the `security` CLI by default so no secret is
|
|
12
|
+
* ever placed in config. AppleScript remains the default transport — SMTP is
|
|
13
|
+
* opt-in per call (`transport: "smtp"`).
|
|
14
|
+
*
|
|
15
|
+
* @module services/smtpMailer
|
|
16
|
+
*/
|
|
17
|
+
import nodemailer from "nodemailer";
|
|
18
|
+
/** Options for an SMTP send, mirroring the AppleScript send-email surface. */
|
|
19
|
+
export interface SmtpSendOptions {
|
|
20
|
+
to: string[];
|
|
21
|
+
subject: string;
|
|
22
|
+
body: string;
|
|
23
|
+
cc?: string[];
|
|
24
|
+
bcc?: string[];
|
|
25
|
+
/** Overrides the configured From address (must be allowed by the SMTP server). */
|
|
26
|
+
from?: string;
|
|
27
|
+
/** Absolute paths to files to attach. */
|
|
28
|
+
attachments?: string[];
|
|
29
|
+
}
|
|
30
|
+
/** Resolved SMTP connection configuration. */
|
|
31
|
+
export interface SmtpConfig {
|
|
32
|
+
host: string;
|
|
33
|
+
port: number;
|
|
34
|
+
secure: boolean;
|
|
35
|
+
user: string;
|
|
36
|
+
pass: string;
|
|
37
|
+
from: string;
|
|
38
|
+
}
|
|
39
|
+
/** Result of an SMTP send. */
|
|
40
|
+
export interface SmtpSendResult {
|
|
41
|
+
success: boolean;
|
|
42
|
+
messageId?: string;
|
|
43
|
+
error?: string;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Environment variables consumed by {@link resolveSmtpConfig}. Documented here
|
|
47
|
+
* (and in the README) so the error path can point users at exactly what to set.
|
|
48
|
+
*/
|
|
49
|
+
export declare const SMTP_ENV: {
|
|
50
|
+
readonly host: "APPLE_MAIL_MCP_SMTP_HOST";
|
|
51
|
+
readonly port: "APPLE_MAIL_MCP_SMTP_PORT";
|
|
52
|
+
readonly secure: "APPLE_MAIL_MCP_SMTP_SECURE";
|
|
53
|
+
readonly user: "APPLE_MAIL_MCP_SMTP_USER";
|
|
54
|
+
readonly from: "APPLE_MAIL_MCP_SMTP_FROM";
|
|
55
|
+
readonly password: "APPLE_MAIL_MCP_SMTP_PASSWORD";
|
|
56
|
+
readonly keychainService: "APPLE_MAIL_MCP_SMTP_KEYCHAIN_SERVICE";
|
|
57
|
+
readonly keychainAccount: "APPLE_MAIL_MCP_SMTP_KEYCHAIN_ACCOUNT";
|
|
58
|
+
};
|
|
59
|
+
/**
|
|
60
|
+
* Reads a password from the macOS login Keychain via the `security` CLI.
|
|
61
|
+
*
|
|
62
|
+
* Tries `find-internet-password` first (where Mail.app stores account
|
|
63
|
+
* passwords) and falls back to `find-generic-password`. Returns null if no
|
|
64
|
+
* matching item exists or the lookup fails for any reason — callers fall back
|
|
65
|
+
* to the password env var and ultimately surface a clear configuration error.
|
|
66
|
+
*
|
|
67
|
+
* @param service - Keychain service / server name (typically the SMTP host)
|
|
68
|
+
* @param account - Keychain account (typically the SMTP username)
|
|
69
|
+
*/
|
|
70
|
+
export declare function readKeychainPassword(service: string, account: string): string | null;
|
|
71
|
+
/**
|
|
72
|
+
* Resolves SMTP connection configuration from environment + Keychain.
|
|
73
|
+
*
|
|
74
|
+
* @throws Error with an actionable message listing the missing settings.
|
|
75
|
+
*/
|
|
76
|
+
export declare function resolveSmtpConfig(env?: NodeJS.ProcessEnv): SmtpConfig;
|
|
77
|
+
/**
|
|
78
|
+
* Sends an email over SMTP, producing clean MIME with no blockquote wrapping.
|
|
79
|
+
*
|
|
80
|
+
* Config is resolved via {@link resolveSmtpConfig} unless one is injected (the
|
|
81
|
+
* `config` parameter exists for testing). The body is sent as plain text; pass
|
|
82
|
+
* a transporter factory only in tests.
|
|
83
|
+
*/
|
|
84
|
+
export declare function sendViaSmtp(opts: SmtpSendOptions, config?: SmtpConfig, createTransport?: typeof nodemailer.createTransport): Promise<SmtpSendResult>;
|
|
85
|
+
//# sourceMappingURL=smtpMailer.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"smtpMailer.d.ts","sourceRoot":"","sources":["../../src/services/smtpMailer.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,UAAU,MAAM,YAAY,CAAC;AAKpC,8EAA8E;AAC9E,MAAM,WAAW,eAAe;IAC9B,EAAE,EAAE,MAAM,EAAE,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC;IACd,GAAG,CAAC,EAAE,MAAM,EAAE,CAAC;IACf,kFAAkF;IAClF,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,yCAAyC;IACzC,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;CACxB;AAED,8CAA8C;AAC9C,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,OAAO,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;CACd;AAED,8BAA8B;AAC9B,MAAM,WAAW,cAAc;IAC7B,OAAO,EAAE,OAAO,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED;;;GAGG;AACH,eAAO,MAAM,QAAQ;;;;;;;;;CASX,CAAC;AAEX;;;;;;;;;;GAUG;AACH,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAcpF;AAED;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,GAAG,GAAE,MAAM,CAAC,UAAwB,GAAG,UAAU,CA8ClF;AAmBD;;;;;;GAMG;AACH,wBAAsB,WAAW,CAC/B,IAAI,EAAE,eAAe,EACrB,MAAM,CAAC,EAAE,UAAU,EACnB,eAAe,GAAE,OAAO,UAAU,CAAC,eAA4C,GAC9E,OAAO,CAAC,cAAc,CAAC,CAyCzB"}
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SMTP transport for sending mail (issue #12).
|
|
3
|
+
*
|
|
4
|
+
* Mail.app's AppleScript send path wraps any injected body in
|
|
5
|
+
* `<blockquote type="cite">` under the Apple-Mail-URLShareWrapperClass template
|
|
6
|
+
* on macOS 15+, so messages render to recipients as quoted/forwarded content
|
|
7
|
+
* (Apple radar FB11734014, open since Ventura). This module bypasses Mail.app
|
|
8
|
+
* entirely and submits clean MIME directly over SMTP via nodemailer.
|
|
9
|
+
*
|
|
10
|
+
* Connection settings come from environment variables; the password is read
|
|
11
|
+
* from the macOS Keychain via the `security` CLI by default so no secret is
|
|
12
|
+
* ever placed in config. AppleScript remains the default transport — SMTP is
|
|
13
|
+
* opt-in per call (`transport: "smtp"`).
|
|
14
|
+
*
|
|
15
|
+
* @module services/smtpMailer
|
|
16
|
+
*/
|
|
17
|
+
import nodemailer from "nodemailer";
|
|
18
|
+
import { execFileSync } from "child_process";
|
|
19
|
+
import { isAbsolute } from "path";
|
|
20
|
+
import { existsSync } from "fs";
|
|
21
|
+
/**
|
|
22
|
+
* Environment variables consumed by {@link resolveSmtpConfig}. Documented here
|
|
23
|
+
* (and in the README) so the error path can point users at exactly what to set.
|
|
24
|
+
*/
|
|
25
|
+
export const SMTP_ENV = {
|
|
26
|
+
host: "APPLE_MAIL_MCP_SMTP_HOST",
|
|
27
|
+
port: "APPLE_MAIL_MCP_SMTP_PORT",
|
|
28
|
+
secure: "APPLE_MAIL_MCP_SMTP_SECURE",
|
|
29
|
+
user: "APPLE_MAIL_MCP_SMTP_USER",
|
|
30
|
+
from: "APPLE_MAIL_MCP_SMTP_FROM",
|
|
31
|
+
password: "APPLE_MAIL_MCP_SMTP_PASSWORD",
|
|
32
|
+
keychainService: "APPLE_MAIL_MCP_SMTP_KEYCHAIN_SERVICE",
|
|
33
|
+
keychainAccount: "APPLE_MAIL_MCP_SMTP_KEYCHAIN_ACCOUNT",
|
|
34
|
+
};
|
|
35
|
+
/**
|
|
36
|
+
* Reads a password from the macOS login Keychain via the `security` CLI.
|
|
37
|
+
*
|
|
38
|
+
* Tries `find-internet-password` first (where Mail.app stores account
|
|
39
|
+
* passwords) and falls back to `find-generic-password`. Returns null if no
|
|
40
|
+
* matching item exists or the lookup fails for any reason — callers fall back
|
|
41
|
+
* to the password env var and ultimately surface a clear configuration error.
|
|
42
|
+
*
|
|
43
|
+
* @param service - Keychain service / server name (typically the SMTP host)
|
|
44
|
+
* @param account - Keychain account (typically the SMTP username)
|
|
45
|
+
*/
|
|
46
|
+
export function readKeychainPassword(service, account) {
|
|
47
|
+
for (const kind of ["find-internet-password", "find-generic-password"]) {
|
|
48
|
+
try {
|
|
49
|
+
const out = execFileSync("security", [kind, "-s", service, "-a", account, "-w"], {
|
|
50
|
+
encoding: "utf8",
|
|
51
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
52
|
+
});
|
|
53
|
+
const pass = out.replace(/\n$/, "");
|
|
54
|
+
if (pass)
|
|
55
|
+
return pass;
|
|
56
|
+
}
|
|
57
|
+
catch {
|
|
58
|
+
// Not found via this kind; try the next.
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
return null;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Resolves SMTP connection configuration from environment + Keychain.
|
|
65
|
+
*
|
|
66
|
+
* @throws Error with an actionable message listing the missing settings.
|
|
67
|
+
*/
|
|
68
|
+
export function resolveSmtpConfig(env = process.env) {
|
|
69
|
+
const host = env[SMTP_ENV.host]?.trim();
|
|
70
|
+
const user = env[SMTP_ENV.user]?.trim();
|
|
71
|
+
const missing = [];
|
|
72
|
+
if (!host)
|
|
73
|
+
missing.push(SMTP_ENV.host);
|
|
74
|
+
if (!user)
|
|
75
|
+
missing.push(SMTP_ENV.user);
|
|
76
|
+
if (missing.length > 0) {
|
|
77
|
+
throw new Error(`SMTP transport is not configured. Set ${missing.join(" and ")} ` +
|
|
78
|
+
`(plus a password via ${SMTP_ENV.password} or the Keychain). ` +
|
|
79
|
+
`See the README "SMTP transport" section.`);
|
|
80
|
+
}
|
|
81
|
+
// secure=true => implicit TLS (port 465); otherwise STARTTLS (port 587).
|
|
82
|
+
const secure = /^(1|true|yes)$/i.test(env[SMTP_ENV.secure]?.trim() ?? "");
|
|
83
|
+
const port = env[SMTP_ENV.port]
|
|
84
|
+
? Number.parseInt(env[SMTP_ENV.port], 10)
|
|
85
|
+
: secure
|
|
86
|
+
? 465
|
|
87
|
+
: 587;
|
|
88
|
+
if (!Number.isInteger(port) || port <= 0) {
|
|
89
|
+
throw new Error(`Invalid ${SMTP_ENV.port}: "${env[SMTP_ENV.port]}" is not a valid port.`);
|
|
90
|
+
}
|
|
91
|
+
const from = env[SMTP_ENV.from]?.trim() || user;
|
|
92
|
+
// Password: explicit env var wins, otherwise Keychain (service/account
|
|
93
|
+
// default to the host/user but can be overridden).
|
|
94
|
+
let pass = env[SMTP_ENV.password];
|
|
95
|
+
if (!pass) {
|
|
96
|
+
const service = env[SMTP_ENV.keychainService]?.trim() || host;
|
|
97
|
+
const account = env[SMTP_ENV.keychainAccount]?.trim() || user;
|
|
98
|
+
pass = readKeychainPassword(service, account) ?? undefined;
|
|
99
|
+
}
|
|
100
|
+
if (!pass) {
|
|
101
|
+
throw new Error(`No SMTP password found. Set ${SMTP_ENV.password}, or store an internet ` +
|
|
102
|
+
`password in the Keychain for service "${env[SMTP_ENV.keychainService]?.trim() || host}" / account "${env[SMTP_ENV.keychainAccount]?.trim() || user}".`);
|
|
103
|
+
}
|
|
104
|
+
return { host: host, port, secure, user: user, pass, from };
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Validates attachment paths the same way the AppleScript path does: absolute
|
|
108
|
+
* and existing. Returns nodemailer attachment descriptors.
|
|
109
|
+
*/
|
|
110
|
+
function buildAttachments(attachments) {
|
|
111
|
+
if (!attachments || attachments.length === 0)
|
|
112
|
+
return undefined;
|
|
113
|
+
for (const filePath of attachments) {
|
|
114
|
+
if (!isAbsolute(filePath)) {
|
|
115
|
+
throw new Error(`Attachment path must be absolute: "${filePath}"`);
|
|
116
|
+
}
|
|
117
|
+
if (!existsSync(filePath)) {
|
|
118
|
+
throw new Error(`Attachment file not found: "${filePath}"`);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
return attachments.map((path) => ({ path }));
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Sends an email over SMTP, producing clean MIME with no blockquote wrapping.
|
|
125
|
+
*
|
|
126
|
+
* Config is resolved via {@link resolveSmtpConfig} unless one is injected (the
|
|
127
|
+
* `config` parameter exists for testing). The body is sent as plain text; pass
|
|
128
|
+
* a transporter factory only in tests.
|
|
129
|
+
*/
|
|
130
|
+
export async function sendViaSmtp(opts, config, createTransport = nodemailer.createTransport) {
|
|
131
|
+
let cfg;
|
|
132
|
+
try {
|
|
133
|
+
cfg = config ?? resolveSmtpConfig();
|
|
134
|
+
}
|
|
135
|
+
catch (error) {
|
|
136
|
+
return { success: false, error: error instanceof Error ? error.message : String(error) };
|
|
137
|
+
}
|
|
138
|
+
let attachments;
|
|
139
|
+
try {
|
|
140
|
+
attachments = buildAttachments(opts.attachments);
|
|
141
|
+
}
|
|
142
|
+
catch (error) {
|
|
143
|
+
return { success: false, error: error instanceof Error ? error.message : String(error) };
|
|
144
|
+
}
|
|
145
|
+
const transporter = createTransport({
|
|
146
|
+
host: cfg.host,
|
|
147
|
+
port: cfg.port,
|
|
148
|
+
secure: cfg.secure,
|
|
149
|
+
auth: { user: cfg.user, pass: cfg.pass },
|
|
150
|
+
});
|
|
151
|
+
try {
|
|
152
|
+
const info = await transporter.sendMail({
|
|
153
|
+
from: opts.from?.trim() || cfg.from,
|
|
154
|
+
to: opts.to,
|
|
155
|
+
cc: opts.cc,
|
|
156
|
+
bcc: opts.bcc,
|
|
157
|
+
subject: opts.subject,
|
|
158
|
+
text: opts.body,
|
|
159
|
+
attachments,
|
|
160
|
+
});
|
|
161
|
+
return { success: true, messageId: info.messageId };
|
|
162
|
+
}
|
|
163
|
+
catch (error) {
|
|
164
|
+
return {
|
|
165
|
+
success: false,
|
|
166
|
+
error: `SMTP send failed: ${error instanceof Error ? error.message : String(error)}`,
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
finally {
|
|
170
|
+
transporter.close();
|
|
171
|
+
}
|
|
172
|
+
}
|
package/build/types.d.ts
CHANGED
|
@@ -47,6 +47,39 @@ export interface Message {
|
|
|
47
47
|
/** Whether the message has attachments */
|
|
48
48
|
hasAttachments: boolean;
|
|
49
49
|
}
|
|
50
|
+
/**
|
|
51
|
+
* Per-account / per-mailbox diagnostics for a search, so callers can tell a
|
|
52
|
+
* genuine "no matches" apart from "we couldn't finish searching."
|
|
53
|
+
*
|
|
54
|
+
* Apple Mail's AppleScript bridge is pathologically slow on large IMAP/Gmail
|
|
55
|
+
* mailboxes (tens of thousands of messages): a single `whose` predicate — or
|
|
56
|
+
* even reading the newest 20 messages by index — can blow past the Apple Event
|
|
57
|
+
* timeout. Before this was tracked, those timeouts were swallowed and the
|
|
58
|
+
* search returned a clean (but wrong) empty result. See issue #24.
|
|
59
|
+
*/
|
|
60
|
+
export interface SearchDiagnostics {
|
|
61
|
+
/** True if any account/mailbox could not be fully searched (timeout or skip). */
|
|
62
|
+
partial: boolean;
|
|
63
|
+
/** Accounts that timed out entirely (whole-account AppleScript was killed). */
|
|
64
|
+
timedOutAccounts: string[];
|
|
65
|
+
/**
|
|
66
|
+
* Mailboxes skipped because their message count exceeded the scan threshold,
|
|
67
|
+
* formatted as "Account / Mailbox (count)".
|
|
68
|
+
*/
|
|
69
|
+
skippedLargeMailboxes: string[];
|
|
70
|
+
/**
|
|
71
|
+
* Mailboxes that were reached but timed out or errored mid-scan, formatted as
|
|
72
|
+
* "Account / Mailbox".
|
|
73
|
+
*/
|
|
74
|
+
notSearchedMailboxes: string[];
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* A search result paired with diagnostics about coverage.
|
|
78
|
+
*/
|
|
79
|
+
export interface SearchResult {
|
|
80
|
+
messages: Message[];
|
|
81
|
+
diagnostics: SearchDiagnostics;
|
|
82
|
+
}
|
|
50
83
|
/**
|
|
51
84
|
* Represents the content of an email message.
|
|
52
85
|
*/
|
package/build/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "apple-mail-mcp",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.6.1",
|
|
4
4
|
"description": "MCP server for Apple Mail - read, search, send, and manage emails via Claude",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "build/index.js",
|
|
@@ -59,10 +59,12 @@
|
|
|
59
59
|
],
|
|
60
60
|
"dependencies": {
|
|
61
61
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
62
|
+
"nodemailer": "^9.0.0",
|
|
62
63
|
"zod": "^3.22.4"
|
|
63
64
|
},
|
|
64
65
|
"devDependencies": {
|
|
65
66
|
"@types/node": "^20.0.0",
|
|
67
|
+
"@types/nodemailer": "^8.0.1",
|
|
66
68
|
"@typescript-eslint/eslint-plugin": "^8.0.0",
|
|
67
69
|
"@typescript-eslint/parser": "^8.0.0",
|
|
68
70
|
"@vitest/coverage-v8": "^4.1.9",
|