apple-mail-mcp 1.1.1 → 1.2.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 CHANGED
@@ -84,8 +84,9 @@ On first use, macOS will ask for permission to automate Mail.app. Click "OK" to
84
84
  | **List Messages** | List messages with pagination, sender filter, date display |
85
85
  | **Search Messages** | Search by sender, subject, content, date range, read/flagged status — across all accounts |
86
86
  | **Read Messages** | Get full email content (plain text or HTML) |
87
- | **Send Email** | Compose and send new emails |
88
- | **Create Draft** | Save emails to Drafts folder |
87
+ | **Send Email** | Compose and send new emails (with optional file attachments) |
88
+ | **Send Serial Email** | Mail merge — send personalized emails to a list of recipients with {{placeholder}} support |
89
+ | **Create Draft** | Save emails to Drafts folder (with optional file attachments) |
89
90
  | **Reply** | Reply to messages (with reply-all support) |
90
91
  | **Forward** | Forward messages to new recipients |
91
92
  | **Mark Read/Unread** | Change read status (single or batch) |
@@ -190,6 +191,7 @@ Send a new email immediately.
190
191
  | `cc` | string[] | No | CC recipients |
191
192
  | `bcc` | string[] | No | BCC recipients |
192
193
  | `account` | string | No | Send from specific account |
194
+ | `attachments` | string[] | No | Absolute file paths to attach, max 20 files (e.g., `["/Users/me/report.pdf"]`) |
193
195
 
194
196
  **Example:**
195
197
  ```json
@@ -197,12 +199,48 @@ Send a new email immediately.
197
199
  "to": ["colleague@company.com"],
198
200
  "subject": "Meeting Tomorrow",
199
201
  "body": "Hi, just confirming our meeting at 2pm tomorrow.",
200
- "account": "Work"
202
+ "account": "Work",
203
+ "attachments": ["/Users/me/Documents/agenda.pdf"]
201
204
  }
202
205
  ```
203
206
 
204
207
  ---
205
208
 
209
+ #### `send-serial-email`
210
+
211
+ 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.
212
+
213
+ | Parameter | Type | Required | Description |
214
+ |-----------|------|----------|-------------|
215
+ | `recipients` | object[] | Yes | List of recipients, max 100 (see below) |
216
+ | `subject` | string | Yes | Email subject — use `{{Key}}` for placeholders |
217
+ | `body` | string | Yes | Email body — use `{{Key}}` for placeholders |
218
+ | `account` | string | No | Send from specific account |
219
+ | `delayMs` | number | No | Delay between sends in ms (default: 500, max 10000) |
220
+
221
+ Each recipient object:
222
+
223
+ | Field | Type | Required | Description |
224
+ |-------|------|----------|-------------|
225
+ | `email` | string | Yes | Recipient email address |
226
+ | `variables` | object | Yes | Key-value pairs for placeholder replacement |
227
+
228
+ **Example:**
229
+ ```json
230
+ {
231
+ "recipients": [
232
+ { "email": "alice@example.com", "variables": { "Name": "Alice", "Company": "Acme" } },
233
+ { "email": "bob@example.com", "variables": { "Name": "Bob", "Company": "Globex" } }
234
+ ],
235
+ "subject": "Hello {{Name}}!",
236
+ "body": "Dear {{Name}},\n\nGreat to connect about {{Company}}.\n\nBest regards"
237
+ }
238
+ ```
239
+
240
+ **Returns:** Per-recipient success/failure results with a summary count.
241
+
242
+ ---
243
+
206
244
  #### `create-draft`
207
245
 
208
246
  Save an email to Drafts without sending.
@@ -215,6 +253,7 @@ Save an email to Drafts without sending.
215
253
  | `cc` | string[] | No | CC recipients |
216
254
  | `bcc` | string[] | No | BCC recipients |
217
255
  | `account` | string | No | Account for draft |
256
+ | `attachments` | string[] | No | Absolute file paths to attach, max 20 files |
218
257
 
219
258
  **Returns:** Confirmation that draft was created.
220
259
 
@@ -332,19 +371,19 @@ Save a message attachment to disk.
332
371
 
333
372
  ### Batch Operations
334
373
 
335
- All batch operations accept an array of message IDs and return per-item success/failure results.
374
+ All batch operations accept an array of message IDs (max 100 per batch) and return per-item success/failure results.
336
375
 
337
376
  #### `batch-delete-messages`
338
377
 
339
378
  | Parameter | Type | Required | Description |
340
379
  |-----------|------|----------|-------------|
341
- | `ids` | string[] | Yes | Message IDs to delete |
380
+ | `ids` | string[] | Yes | Message IDs to delete (max 100) |
342
381
 
343
382
  #### `batch-move-messages`
344
383
 
345
384
  | Parameter | Type | Required | Description |
346
385
  |-----------|------|----------|-------------|
347
- | `ids` | string[] | Yes | Message IDs to move |
386
+ | `ids` | string[] | Yes | Message IDs to move (max 100) |
348
387
  | `mailbox` | string | Yes | Destination mailbox |
349
388
  | `account` | string | No | Account containing mailbox |
350
389
 
@@ -352,13 +391,13 @@ All batch operations accept an array of message IDs and return per-item success/
352
391
 
353
392
  | Parameter | Type | Required | Description |
354
393
  |-----------|------|----------|-------------|
355
- | `ids` | string[] | Yes | Message IDs |
394
+ | `ids` | string[] | Yes | Message IDs (max 100) |
356
395
 
357
396
  #### `batch-flag-messages` / `batch-unflag-messages`
358
397
 
359
398
  | Parameter | Type | Required | Description |
360
399
  |-----------|------|----------|-------------|
361
- | `ids` | string[] | Yes | Message IDs |
400
+ | `ids` | string[] | Yes | Message IDs (max 100) |
362
401
 
363
402
  ---
364
403
 
@@ -606,6 +645,19 @@ User: "Send it"
606
645
  AI: [User opens Mail.app and sends manually, or AI calls send-email]
607
646
  ```
608
647
 
648
+ ### Sending Personalized Emails (Mail Merge)
649
+
650
+ ```
651
+ User: "Send a personalized email to Alice (alice@acme.com), Bob (bob@globex.com),
652
+ and Carol (carol@initech.com). Subject: 'Project Update for {{Company}}',
653
+ Body: 'Hi {{Name}}, here is the latest update for {{Company}}.'"
654
+ AI: [calls send-serial-email with recipients, subject template, and body template]
655
+ "Successfully sent 3 email(s):
656
+ - alice@acme.com: sent
657
+ - bob@globex.com: sent
658
+ - carol@initech.com: sent"
659
+ ```
660
+
609
661
  ### Organizing Messages
610
662
 
611
663
  ```
@@ -663,9 +715,14 @@ If installed from source, use this configuration:
663
715
  |------------|--------|
664
716
  | macOS only | Apple Mail and AppleScript are macOS-specific |
665
717
  | No sending HTML email | Emails are sent as plain text; reading HTML content is supported |
666
- | No adding attachments | Can list and save existing attachments, but cannot attach files to outgoing emails |
718
+ | Attachments require absolute paths | File attachments must use full absolute paths (e.g., `/Users/me/file.pdf`) |
667
719
  | No smart mailboxes | Cannot access Smart Mailboxes via AppleScript |
668
720
  | In-memory templates | Email templates are not persisted across server restarts |
721
+ | Numeric-only message IDs | Message IDs must contain only digits (validated by schema) |
722
+ | Batch size cap | Batch operations are limited to 100 messages per request |
723
+ | Date filter format | Date filters accept alphanumeric characters and safe punctuation only |
724
+ | Attachment save path restrictions | `save-attachment` only allows saving to home directory, `/tmp`, `/private/tmp`, and `/Volumes`; path traversal is blocked |
725
+ | Attachment count limit | `send-email` and `create-draft` accept a maximum of 20 file attachments |
669
726
 
670
727
  ### Backslash Escaping (Important for AI Agents)
671
728
 
@@ -724,11 +781,13 @@ The `\\\\` in JSON becomes `\\` in the actual string, which represents a single
724
781
  ## Development
725
782
 
726
783
  ```bash
727
- npm install # Install dependencies
728
- npm run build # Compile TypeScript
729
- npm test # Run test suite (28 tests)
730
- npm run lint # Check code style
731
- npm run format # Format code
784
+ npm install # Install dependencies
785
+ npm run build # Compile TypeScript
786
+ npm test # Run unit tests
787
+ npm run test:integration # Run integration tests (requires Mail.app)
788
+ npm run test:all # Run all tests (unit + integration)
789
+ npm run lint # Check code style
790
+ npm run format # Format code
732
791
  ```
733
792
 
734
793
  ---
package/build/index.js CHANGED
@@ -24,6 +24,23 @@ 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
+ // =============================================================================
28
+ // Shared Validation Schemas
29
+ // =============================================================================
30
+ /** Message IDs in Apple Mail are always numeric. Enforce this at the schema level
31
+ * to prevent AppleScript injection via the `whose id is ${id}` interpolation. */
32
+ const MESSAGE_ID_SCHEMA = z.string().regex(/^\d+$/, "Message ID must be numeric");
33
+ /** Batch operations are capped to prevent unbounded loops / DoS. */
34
+ const BATCH_IDS_SCHEMA = z
35
+ .array(MESSAGE_ID_SCHEMA)
36
+ .min(1, "At least one message ID is required")
37
+ .max(100, "Cannot process more than 100 messages in a single batch");
38
+ /** Date filter strings must look like natural-language dates (e.g. "March 1, 2026").
39
+ * Block characters that could escape an AppleScript `date "..."` literal. */
40
+ const DATE_FILTER_SCHEMA = z
41
+ .string()
42
+ .regex(/^[a-zA-Z0-9 ,/\-:]+$/, "Date must contain only alphanumeric characters, spaces, commas, slashes, hyphens, and colons")
43
+ .optional();
27
44
  // Read version from package.json to keep it in sync
28
45
  const require = createRequire(import.meta.url);
29
46
  const { version } = require("../package.json");
@@ -89,8 +106,8 @@ server.tool("search-messages", {
89
106
  account: z.string().optional().describe("Account to search in (omit to search all accounts)"),
90
107
  isRead: z.boolean().optional().describe("Filter by read status"),
91
108
  isFlagged: z.boolean().optional().describe("Filter by flagged status"),
92
- dateFrom: z.string().optional().describe("Start date filter (e.g., 'January 1, 2026')"),
93
- dateTo: z.string().optional().describe("End date filter (e.g., 'March 1, 2026')"),
109
+ dateFrom: DATE_FILTER_SCHEMA.describe("Start date filter (e.g., 'January 1, 2026')"),
110
+ dateTo: DATE_FILTER_SCHEMA.describe("End date filter (e.g., 'March 1, 2026')"),
94
111
  limit: z.number().optional().describe("Maximum number of results (default: 50)"),
95
112
  }, withErrorHandling(({ query, mailbox, account, limit = 50, dateFrom, dateTo }) => {
96
113
  const messages = mailManager.searchMessages(query, mailbox, account, limit, dateFrom, dateTo);
@@ -104,7 +121,7 @@ server.tool("search-messages", {
104
121
  }, "Error searching messages"));
105
122
  // --- get-message ---
106
123
  server.tool("get-message", {
107
- id: z.string().min(1, "Message ID is required"),
124
+ id: MESSAGE_ID_SCHEMA,
108
125
  preferHtml: z.boolean().optional().describe("Return HTML source instead of plain text"),
109
126
  }, withErrorHandling(({ id, preferHtml }) => {
110
127
  const content = mailManager.getMessageContent(id);
@@ -142,13 +159,63 @@ server.tool("send-email", {
142
159
  cc: z.array(z.string()).optional().describe("CC recipients"),
143
160
  bcc: z.array(z.string()).optional().describe("BCC recipients"),
144
161
  account: z.string().optional().describe("Account to send from"),
145
- }, withErrorHandling(({ to, subject, body, cc, bcc, account }) => {
146
- const success = mailManager.sendEmail(to, subject, body, cc, bcc, account);
162
+ attachments: z
163
+ .array(z.string())
164
+ .max(20, "Cannot attach more than 20 files")
165
+ .optional()
166
+ .describe("Absolute file paths to attach (e.g., ['/Users/me/report.pdf'])"),
167
+ }, withErrorHandling(({ to, subject, body, cc, bcc, account, attachments }) => {
168
+ const success = mailManager.sendEmail(to, subject, body, cc, bcc, account, attachments);
147
169
  if (!success) {
148
170
  return errorResponse("Failed to send email. Check Mail.app configuration.");
149
171
  }
150
- return successResponse(`Email sent to ${to.join(", ")}`);
172
+ const attachInfo = attachments?.length ? ` with ${attachments.length} attachment(s)` : "";
173
+ return successResponse(`Email sent to ${to.join(", ")}${attachInfo}`);
151
174
  }, "Error sending email"));
175
+ // --- send-serial-email ---
176
+ server.tool("send-serial-email", {
177
+ recipients: z
178
+ .array(z.object({
179
+ email: z.string().min(1, "Recipient email is required"),
180
+ variables: z
181
+ .record(z.string())
182
+ .describe("Placeholder values, e.g. { Name: 'Alice', Company: 'Acme' }"),
183
+ }))
184
+ .min(1, "At least one recipient is required")
185
+ .max(100, "Cannot send to more than 100 recipients in a single batch")
186
+ .describe("List of recipients with personalization variables (max 100)"),
187
+ subject: z
188
+ .string()
189
+ .min(1, "Subject is required")
190
+ .describe("Subject line — use {{Key}} for placeholders"),
191
+ body: z
192
+ .string()
193
+ .min(1, "Body is required")
194
+ .describe("Email body — use {{Key}} for placeholders"),
195
+ account: z.string().optional().describe("Account to send from"),
196
+ delayMs: z
197
+ .number()
198
+ .min(0)
199
+ .max(10000)
200
+ .optional()
201
+ .describe("Delay between sends in ms (default: 500, max: 10000)"),
202
+ }, withErrorHandling(({ recipients, subject, body, account, delayMs }) => {
203
+ const results = mailManager.sendSerialEmail(recipients, subject, body, account, delayMs);
204
+ const successCount = results.filter((r) => r.success).length;
205
+ const failCount = results.length - successCount;
206
+ const details = results
207
+ .map((r) => ` - ${r.email}: ${r.success ? "sent" : `FAILED (${r.error})`}`)
208
+ .join("\n");
209
+ if (failCount === 0) {
210
+ return successResponse(`Successfully sent ${successCount} email(s):\n${details}`);
211
+ }
212
+ else if (successCount === 0) {
213
+ return errorResponse(`Failed to send all ${failCount} email(s):\n${details}`);
214
+ }
215
+ else {
216
+ return successResponse(`Sent ${successCount} of ${results.length} email(s), ${failCount} failed:\n${details}`);
217
+ }
218
+ }, "Error sending serial emails"));
152
219
  // --- create-draft ---
153
220
  server.tool("create-draft", {
154
221
  to: z.array(z.string()).min(1, "At least one recipient is required"),
@@ -157,16 +224,22 @@ server.tool("create-draft", {
157
224
  cc: z.array(z.string()).optional().describe("CC recipients"),
158
225
  bcc: z.array(z.string()).optional().describe("BCC recipients"),
159
226
  account: z.string().optional().describe("Account to create draft in"),
160
- }, withErrorHandling(({ to, subject, body, cc, bcc, account }) => {
161
- const success = mailManager.createDraft(to, subject, body, cc, bcc, account);
227
+ attachments: z
228
+ .array(z.string())
229
+ .max(20, "Cannot attach more than 20 files")
230
+ .optional()
231
+ .describe("Absolute file paths to attach (e.g., ['/Users/me/report.pdf'])"),
232
+ }, withErrorHandling(({ to, subject, body, cc, bcc, account, attachments }) => {
233
+ const success = mailManager.createDraft(to, subject, body, cc, bcc, account, attachments);
162
234
  if (!success) {
163
235
  return errorResponse("Failed to create draft. Check Mail.app configuration.");
164
236
  }
165
- return successResponse(`Draft created for ${to.join(", ")}`);
237
+ const attachInfo = attachments?.length ? ` with ${attachments.length} attachment(s)` : "";
238
+ return successResponse(`Draft created for ${to.join(", ")}${attachInfo}`);
166
239
  }, "Error creating draft"));
167
240
  // --- reply-to-message ---
168
241
  server.tool("reply-to-message", {
169
- id: z.string().min(1, "Message ID is required"),
242
+ id: MESSAGE_ID_SCHEMA,
170
243
  body: z.string().min(1, "Reply body is required"),
171
244
  replyAll: z.boolean().optional().default(false).describe("Reply to all recipients"),
172
245
  send: z.boolean().optional().default(true).describe("Send immediately (false = save as draft)"),
@@ -179,7 +252,7 @@ server.tool("reply-to-message", {
179
252
  }, "Error replying to message"));
180
253
  // --- forward-message ---
181
254
  server.tool("forward-message", {
182
- id: z.string().min(1, "Message ID is required"),
255
+ id: MESSAGE_ID_SCHEMA,
183
256
  to: z.array(z.string()).min(1, "At least one recipient is required"),
184
257
  body: z.string().optional().describe("Optional message to prepend"),
185
258
  send: z.boolean().optional().default(true).describe("Send immediately (false = save as draft)"),
@@ -192,7 +265,7 @@ server.tool("forward-message", {
192
265
  }, "Error forwarding message"));
193
266
  // --- mark-as-read ---
194
267
  server.tool("mark-as-read", {
195
- id: z.string().min(1, "Message ID is required"),
268
+ id: MESSAGE_ID_SCHEMA,
196
269
  }, withErrorHandling(({ id }) => {
197
270
  const success = mailManager.markAsRead(id);
198
271
  if (!success) {
@@ -202,7 +275,7 @@ server.tool("mark-as-read", {
202
275
  }, "Error marking message as read"));
203
276
  // --- mark-as-unread ---
204
277
  server.tool("mark-as-unread", {
205
- id: z.string().min(1, "Message ID is required"),
278
+ id: MESSAGE_ID_SCHEMA,
206
279
  }, withErrorHandling(({ id }) => {
207
280
  const success = mailManager.markAsUnread(id);
208
281
  if (!success) {
@@ -212,7 +285,7 @@ server.tool("mark-as-unread", {
212
285
  }, "Error marking message as unread"));
213
286
  // --- flag-message ---
214
287
  server.tool("flag-message", {
215
- id: z.string().min(1, "Message ID is required"),
288
+ id: MESSAGE_ID_SCHEMA,
216
289
  }, withErrorHandling(({ id }) => {
217
290
  const success = mailManager.flagMessage(id);
218
291
  if (!success) {
@@ -222,7 +295,7 @@ server.tool("flag-message", {
222
295
  }, "Error flagging message"));
223
296
  // --- unflag-message ---
224
297
  server.tool("unflag-message", {
225
- id: z.string().min(1, "Message ID is required"),
298
+ id: MESSAGE_ID_SCHEMA,
226
299
  }, withErrorHandling(({ id }) => {
227
300
  const success = mailManager.unflagMessage(id);
228
301
  if (!success) {
@@ -232,7 +305,7 @@ server.tool("unflag-message", {
232
305
  }, "Error unflagging message"));
233
306
  // --- delete-message ---
234
307
  server.tool("delete-message", {
235
- id: z.string().min(1, "Message ID is required"),
308
+ id: MESSAGE_ID_SCHEMA,
236
309
  }, withErrorHandling(({ id }) => {
237
310
  const success = mailManager.deleteMessage(id);
238
311
  if (!success) {
@@ -242,7 +315,7 @@ server.tool("delete-message", {
242
315
  }, "Error deleting message"));
243
316
  // --- move-message ---
244
317
  server.tool("move-message", {
245
- id: z.string().min(1, "Message ID is required"),
318
+ id: MESSAGE_ID_SCHEMA,
246
319
  mailbox: z.string().min(1, "Destination mailbox is required"),
247
320
  account: z.string().optional().describe("Account containing the destination mailbox"),
248
321
  }, withErrorHandling(({ id, mailbox, account }) => {
@@ -254,7 +327,7 @@ server.tool("move-message", {
254
327
  }, "Error moving message"));
255
328
  // --- batch-delete-messages ---
256
329
  server.tool("batch-delete-messages", {
257
- ids: z.array(z.string()).min(1, "At least one message ID is required"),
330
+ ids: BATCH_IDS_SCHEMA,
258
331
  }, withErrorHandling(({ ids }) => {
259
332
  const results = mailManager.batchDeleteMessages(ids);
260
333
  const successCount = results.filter((r) => r.success).length;
@@ -271,7 +344,7 @@ server.tool("batch-delete-messages", {
271
344
  }, "Error batch deleting messages"));
272
345
  // --- batch-move-messages ---
273
346
  server.tool("batch-move-messages", {
274
- ids: z.array(z.string()).min(1, "At least one message ID is required"),
347
+ ids: BATCH_IDS_SCHEMA,
275
348
  mailbox: z.string().min(1, "Destination mailbox is required"),
276
349
  account: z.string().optional().describe("Account containing the destination mailbox"),
277
350
  }, withErrorHandling(({ ids, mailbox, account }) => {
@@ -290,7 +363,7 @@ server.tool("batch-move-messages", {
290
363
  }, "Error batch moving messages"));
291
364
  // --- batch-mark-as-read ---
292
365
  server.tool("batch-mark-as-read", {
293
- ids: z.array(z.string()).min(1, "At least one message ID is required"),
366
+ ids: BATCH_IDS_SCHEMA,
294
367
  }, withErrorHandling(({ ids }) => {
295
368
  const results = mailManager.batchMarkAsRead(ids);
296
369
  const successCount = results.filter((r) => r.success).length;
@@ -307,7 +380,7 @@ server.tool("batch-mark-as-read", {
307
380
  }, "Error batch marking messages as read"));
308
381
  // --- batch-mark-as-unread ---
309
382
  server.tool("batch-mark-as-unread", {
310
- ids: z.array(z.string()).min(1, "At least one message ID is required"),
383
+ ids: BATCH_IDS_SCHEMA,
311
384
  }, withErrorHandling(({ ids }) => {
312
385
  const results = mailManager.batchMarkAsUnread(ids);
313
386
  const successCount = results.filter((r) => r.success).length;
@@ -324,7 +397,7 @@ server.tool("batch-mark-as-unread", {
324
397
  }, "Error batch marking messages as unread"));
325
398
  // --- batch-flag-messages ---
326
399
  server.tool("batch-flag-messages", {
327
- ids: z.array(z.string()).min(1, "At least one message ID is required"),
400
+ ids: BATCH_IDS_SCHEMA,
328
401
  }, withErrorHandling(({ ids }) => {
329
402
  const results = mailManager.batchFlagMessages(ids);
330
403
  const successCount = results.filter((r) => r.success).length;
@@ -341,7 +414,7 @@ server.tool("batch-flag-messages", {
341
414
  }, "Error batch flagging messages"));
342
415
  // --- batch-unflag-messages ---
343
416
  server.tool("batch-unflag-messages", {
344
- ids: z.array(z.string()).min(1, "At least one message ID is required"),
417
+ ids: BATCH_IDS_SCHEMA,
345
418
  }, withErrorHandling(({ ids }) => {
346
419
  const results = mailManager.batchUnflagMessages(ids);
347
420
  const successCount = results.filter((r) => r.success).length;
@@ -358,7 +431,7 @@ server.tool("batch-unflag-messages", {
358
431
  }, "Error batch unflagging messages"));
359
432
  // --- list-attachments ---
360
433
  server.tool("list-attachments", {
361
- id: z.string().min(1, "Message ID is required"),
434
+ id: MESSAGE_ID_SCHEMA,
362
435
  }, withErrorHandling(({ id }) => {
363
436
  const attachments = mailManager.listAttachments(id);
364
437
  if (attachments.length === 0) {
@@ -374,7 +447,7 @@ server.tool("list-attachments", {
374
447
  }, "Error listing attachments"));
375
448
  // --- save-attachment ---
376
449
  server.tool("save-attachment", {
377
- id: z.string().min(1, "Message ID is required"),
450
+ id: MESSAGE_ID_SCHEMA,
378
451
  attachmentName: z.string().min(1, "Attachment name is required"),
379
452
  savePath: z.string().min(1, "Save directory path is required"),
380
453
  }, withErrorHandling(({ id, attachmentName, savePath }) => {
@@ -12,7 +12,7 @@
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 } from "../types.js";
15
+ import type { Message, MessageContent, Mailbox, Account, Attachment, HealthCheckResult, MailStats, BatchOperationResult, SyncStatus, RecentlyReceivedStats, MailRule, Contact, EmailTemplate, SerialEmailRecipient, SerialEmailResult } from "../types.js";
16
16
  /**
17
17
  * Manager class for Apple Mail operations.
18
18
  *
@@ -124,7 +124,21 @@ export declare class AppleMailManager {
124
124
  * @param account - Account to send from
125
125
  * @returns true if sent successfully
126
126
  */
127
- sendEmail(to: string[], subject: string, body: string, cc?: string[], bcc?: string[], account?: string): boolean;
127
+ sendEmail(to: string[], subject: string, body: string, cc?: string[], bcc?: string[], account?: string, attachments?: string[]): boolean;
128
+ /**
129
+ * Send individual personalized emails to a list of recipients (mail merge).
130
+ *
131
+ * Replaces {{placeholder}} tokens in subject and body with per-recipient values.
132
+ * Each recipient receives their own individual email.
133
+ *
134
+ * @param recipients - List of recipient objects with email and variable values
135
+ * @param subject - Email subject (may contain {{placeholders}})
136
+ * @param body - Email body (may contain {{placeholders}})
137
+ * @param account - Account to send from
138
+ * @param delayMs - Delay between sends in milliseconds (default: 500, max: 10000)
139
+ * @returns Array of per-recipient results
140
+ */
141
+ sendSerialEmail(recipients: SerialEmailRecipient[], subject: string, body: string, account?: string, delayMs?: number): SerialEmailResult[];
128
142
  /**
129
143
  * Create a draft email (saved to Drafts folder, not sent).
130
144
  *
@@ -136,7 +150,7 @@ export declare class AppleMailManager {
136
150
  * @param account - Account to create draft in
137
151
  * @returns true if draft created successfully
138
152
  */
139
- createDraft(to: string[], subject: string, body: string, cc?: string[], bcc?: string[], account?: string): boolean;
153
+ createDraft(to: string[], subject: string, body: string, cc?: string[], bcc?: string[], account?: string, attachments?: string[]): boolean;
140
154
  /**
141
155
  * Reply to a message.
142
156
  *
@@ -1 +1 @@
1
- {"version":3,"file":"appleMailManager.d.ts","sourceRoot":"","sources":["../../src/services/appleMailManager.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"appleMailManager.d.ts","sourceRoot":"","sources":["../../src/services/appleMailManager.ts"],"names":[],"mappings":"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"}
@@ -12,6 +12,10 @@
12
12
  *
13
13
  * @module services/appleMailManager
14
14
  */
15
+ import { spawnSync } from "child_process";
16
+ import { existsSync } from "fs";
17
+ import { isAbsolute, resolve } from "path";
18
+ import { homedir } from "os";
15
19
  import { executeAppleScript } from "../utils/applescript.js";
16
20
  // =============================================================================
17
21
  // Text Processing Utilities
@@ -32,16 +36,54 @@ function escapeForAppleScript(text) {
32
36
  return text.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
33
37
  }
34
38
  /**
35
- * Parses AppleScript date representation to JavaScript Date.
39
+ * Validates attachment file paths and builds AppleScript commands to attach them.
36
40
  *
37
- * AppleScript returns dates in a verbose format like:
38
- * "date Saturday, December 27, 2025 at 3:44:02 PM"
41
+ * @param attachments - Absolute file paths to attach
42
+ * @returns AppleScript commands to add attachments, or empty string if none
43
+ * @throws Error if any path is not absolute or does not exist
44
+ */
45
+ function buildAttachmentCommands(attachments) {
46
+ if (!attachments || attachments.length === 0)
47
+ return "";
48
+ for (const filePath of attachments) {
49
+ if (!isAbsolute(filePath)) {
50
+ throw new Error(`Attachment path must be absolute: "${filePath}"`);
51
+ }
52
+ if (!existsSync(filePath)) {
53
+ throw new Error(`Attachment file not found: "${filePath}"`);
54
+ }
55
+ }
56
+ let commands = "";
57
+ for (const filePath of attachments) {
58
+ const safePath = escapeForAppleScript(filePath);
59
+ commands += `make new attachment with properties {file name:POSIX file "${safePath}"} at after the last paragraph\n`;
60
+ }
61
+ return commands;
62
+ }
63
+ /**
64
+ * AppleScript snippet that converts a date variable `d` into a
65
+ * locale-independent numeric string: "YYYY-M-D-H-m-s".
66
+ * Use: set d to date received of msg, then inline this snippet.
67
+ */
68
+ const AS_DATE_TO_STRING = `((year of d) as string) & "-" & ((month of d as integer) as string) & "-" & ((day of d) as string) & "-" & ((hours of d) as string) & "-" & ((minutes of d) as string) & "-" & ((seconds of d) as string)`;
69
+ /**
70
+ * Parses a locale-independent date string "YYYY-M-D-H-m-s"
71
+ * produced by the AppleScript snippet above.
39
72
  *
40
- * @param appleScriptDate - Date string from AppleScript
73
+ * Falls back to the locale-dependent `as string` format for
74
+ * backwards compatibility, and finally to current date.
75
+ *
76
+ * @param dateStr - Date string from AppleScript
41
77
  * @returns Parsed Date, or current date if parsing fails
42
78
  */
43
- function parseAppleScriptDate(appleScriptDate) {
44
- const withoutPrefix = appleScriptDate.replace(/^date\s+/, "");
79
+ function parseAppleScriptDate(dateStr) {
80
+ // Try locale-independent numeric format first: "YYYY-M-D-H-m-s"
81
+ const numParts = dateStr.split("-").map(Number);
82
+ if (numParts.length === 6 && numParts.every((n) => !isNaN(n))) {
83
+ return new Date(numParts[0], numParts[1] - 1, numParts[2], numParts[3], numParts[4], numParts[5]);
84
+ }
85
+ // Fallback: try legacy locale-dependent format
86
+ const withoutPrefix = dateStr.replace(/^date\s+/, "");
45
87
  const normalized = withoutPrefix.replace(" at ", " ");
46
88
  const parsed = new Date(normalized);
47
89
  return isNaN(parsed.getTime()) ? new Date() : parsed;
@@ -269,15 +311,18 @@ export class AppleMailManager {
269
311
  const safeQuery = escapeForAppleScript(query);
270
312
  searchCondition = `whose subject contains "${safeQuery}" or sender contains "${safeQuery}"`;
271
313
  }
272
- // Build date filter AppleScript
314
+ // Build date filter AppleScript.
315
+ // Note: dateFrom/dateTo are already validated by DATE_FILTER_SCHEMA (alphanumeric + safe
316
+ // punctuation only), so escapeForAppleScript() below is belt-and-suspenders — it won't
317
+ // alter valid date strings but guards against future schema changes.
273
318
  let dateFilter = "";
274
319
  if (dateFrom || dateTo) {
275
320
  const dateChecks = [];
276
321
  if (dateFrom) {
277
- dateChecks.push(`date received of msg >= date "${dateFrom}"`);
322
+ dateChecks.push(`date received of msg >= date "${escapeForAppleScript(dateFrom)}"`);
278
323
  }
279
324
  if (dateTo) {
280
- dateChecks.push(`date received of msg <= date "${dateTo}"`);
325
+ dateChecks.push(`date received of msg <= date "${escapeForAppleScript(dateTo)}"`);
281
326
  }
282
327
  dateFilter = dateChecks.join(" and ");
283
328
  }
@@ -293,7 +338,8 @@ export class AppleMailManager {
293
338
  set msgId to id of msg as string
294
339
  set msgSubject to subject of msg
295
340
  set msgSender to sender of msg
296
- set msgDateStr to date received of msg as string
341
+ set d to date received of msg
342
+ set msgDateStr to ${AS_DATE_TO_STRING}
297
343
  set msgRead to read status of msg as string
298
344
  set msgFlagged to flagged status of msg as string
299
345
  if msgCount > 0 then set outputText to outputText & "|||ITEM|||"
@@ -326,12 +372,13 @@ export class AppleMailManager {
326
372
  repeat with acct in accounts
327
373
  repeat with mb in mailboxes of acct
328
374
  try
329
- set matchingMsgs to (messages of mb whose id is ${id})
375
+ set matchingMsgs to (messages of mb whose id is ${Number(id)})
330
376
  if (count of matchingMsgs) > 0 then
331
377
  set msg to item 1 of matchingMsgs
332
378
  set msgSubject to subject of msg
333
379
  set msgSender to sender of msg
334
- set msgDate to date received of msg as string
380
+ set d to date received of msg
381
+ set msgDate to ${AS_DATE_TO_STRING}
335
382
  set msgRead to read status of msg as string
336
383
  set msgFlagged to flagged status of msg as string
337
384
  set msgJunk to junk mail status of msg as string
@@ -380,7 +427,7 @@ export class AppleMailManager {
380
427
  repeat with acct in accounts
381
428
  repeat with mb in mailboxes of acct
382
429
  try
383
- set matchingMsgs to (messages of mb whose id is ${id})
430
+ set matchingMsgs to (messages of mb whose id is ${Number(id)})
384
431
  if (count of matchingMsgs) > 0 then
385
432
  set msg to item 1 of matchingMsgs
386
433
  set msgSubject to subject of msg
@@ -445,7 +492,8 @@ export class AppleMailManager {
445
492
  set msgId to id of msg as string
446
493
  set msgSubject to subject of msg
447
494
  set msgSender to sender of msg
448
- set msgDate to date received of msg as string
495
+ set d to date received of msg
496
+ set msgDate to ${AS_DATE_TO_STRING}
449
497
  set msgRead to read status of msg as string
450
498
  set msgFlagged to flagged status of msg as string
451
499
  if msgCount > 0 then set outputText to outputText & "|||ITEM|||"
@@ -504,7 +552,7 @@ export class AppleMailManager {
504
552
  * @param account - Account to send from
505
553
  * @returns true if sent successfully
506
554
  */
507
- sendEmail(to, subject, body, cc, bcc, account) {
555
+ sendEmail(to, subject, body, cc, bcc, account, attachments) {
508
556
  const safeSubject = escapeForAppleScript(subject);
509
557
  const safeBody = escapeForAppleScript(body);
510
558
  // Build recipient additions
@@ -522,6 +570,7 @@ export class AppleMailManager {
522
570
  recipientCommands += `make new bcc recipient at end of bcc recipients with properties {address:"${escapeForAppleScript(addr)}"}\n`;
523
571
  }
524
572
  }
573
+ const attachmentCommands = buildAttachmentCommands(attachments);
525
574
  let sendCommand;
526
575
  if (account) {
527
576
  const safeAccount = escapeForAppleScript(account);
@@ -530,6 +579,7 @@ export class AppleMailManager {
530
579
  tell newMessage
531
580
  ${recipientCommands}
532
581
  set sender to "${safeAccount}"
582
+ ${attachmentCommands}
533
583
  end tell
534
584
  send newMessage
535
585
  return "sent"
@@ -540,19 +590,69 @@ export class AppleMailManager {
540
590
  set newMessage to make new outgoing message with properties {subject:"${safeSubject}", content:"${safeBody}", visible:true}
541
591
  tell newMessage
542
592
  ${recipientCommands}
593
+ ${attachmentCommands}
543
594
  end tell
544
595
  send newMessage
545
596
  return "sent"
546
597
  `;
547
598
  }
548
599
  const script = buildAppLevelScript(sendCommand);
549
- const result = executeAppleScript(script);
600
+ const result = executeAppleScript(script, { timeoutMs: 60000, maxRetries: 2 });
550
601
  if (!result.success) {
551
602
  console.error(`Failed to send email: ${result.error}`);
552
603
  return false;
553
604
  }
554
605
  return result.output.includes("sent");
555
606
  }
607
+ /**
608
+ * Send individual personalized emails to a list of recipients (mail merge).
609
+ *
610
+ * Replaces {{placeholder}} tokens in subject and body with per-recipient values.
611
+ * Each recipient receives their own individual email.
612
+ *
613
+ * @param recipients - List of recipient objects with email and variable values
614
+ * @param subject - Email subject (may contain {{placeholders}})
615
+ * @param body - Email body (may contain {{placeholders}})
616
+ * @param account - Account to send from
617
+ * @param delayMs - Delay between sends in milliseconds (default: 500, max: 10000)
618
+ * @returns Array of per-recipient results
619
+ */
620
+ sendSerialEmail(recipients, subject, body, account, delayMs = 500) {
621
+ const effectiveDelay = Math.min(Math.max(delayMs, 0), 10000);
622
+ const results = [];
623
+ for (let i = 0; i < recipients.length; i++) {
624
+ const recipient = recipients[i];
625
+ try {
626
+ // Replace all {{Key}} placeholders with recipient's values
627
+ let personalizedSubject = subject;
628
+ let personalizedBody = body;
629
+ for (const [key, value] of Object.entries(recipient.variables)) {
630
+ const safeKey = key.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
631
+ const placeholder = new RegExp(`\\{\\{${safeKey}\\}\\}`, "g");
632
+ personalizedSubject = personalizedSubject.replace(placeholder, value);
633
+ personalizedBody = personalizedBody.replace(placeholder, value);
634
+ }
635
+ const success = this.sendEmail([recipient.email], personalizedSubject, personalizedBody, undefined, undefined, account);
636
+ results.push({
637
+ email: recipient.email,
638
+ success,
639
+ error: success ? undefined : "Failed to send email",
640
+ });
641
+ }
642
+ catch (error) {
643
+ results.push({
644
+ email: recipient.email,
645
+ success: false,
646
+ error: error instanceof Error ? error.message : "Unknown error",
647
+ });
648
+ }
649
+ // Brief delay between sends to avoid overwhelming Mail.app
650
+ if (effectiveDelay > 0 && i < recipients.length - 1) {
651
+ spawnSync("sleep", [(effectiveDelay / 1000).toString()], { stdio: "ignore" });
652
+ }
653
+ }
654
+ return results;
655
+ }
556
656
  /**
557
657
  * Create a draft email (saved to Drafts folder, not sent).
558
658
  *
@@ -564,7 +664,7 @@ export class AppleMailManager {
564
664
  * @param account - Account to create draft in
565
665
  * @returns true if draft created successfully
566
666
  */
567
- createDraft(to, subject, body, cc, bcc, account) {
667
+ createDraft(to, subject, body, cc, bcc, account, attachments) {
568
668
  const safeSubject = escapeForAppleScript(subject);
569
669
  const safeBody = escapeForAppleScript(body);
570
670
  // Build recipient additions
@@ -582,6 +682,7 @@ export class AppleMailManager {
582
682
  recipientCommands += `make new bcc recipient at end of bcc recipients with properties {address:"${escapeForAppleScript(addr)}"}\n`;
583
683
  }
584
684
  }
685
+ const attachmentCommands = buildAttachmentCommands(attachments);
585
686
  let draftCommand;
586
687
  if (account) {
587
688
  const safeAccount = escapeForAppleScript(account);
@@ -590,6 +691,7 @@ export class AppleMailManager {
590
691
  tell newMessage
591
692
  ${recipientCommands}
592
693
  set sender to "${safeAccount}"
694
+ ${attachmentCommands}
593
695
  end tell
594
696
  return "draft created"
595
697
  `;
@@ -599,12 +701,13 @@ export class AppleMailManager {
599
701
  set newMessage to make new outgoing message with properties {subject:"${safeSubject}", content:"${safeBody}", visible:false}
600
702
  tell newMessage
601
703
  ${recipientCommands}
704
+ ${attachmentCommands}
602
705
  end tell
603
706
  return "draft created"
604
707
  `;
605
708
  }
606
709
  const script = buildAppLevelScript(draftCommand);
607
- const result = executeAppleScript(script);
710
+ const result = executeAppleScript(script, { timeoutMs: 60000, maxRetries: 2 });
608
711
  if (!result.success) {
609
712
  console.error(`Failed to create draft: ${result.error}`);
610
713
  return false;
@@ -629,7 +732,7 @@ export class AppleMailManager {
629
732
  repeat with acct in accounts
630
733
  repeat with mb in mailboxes of acct
631
734
  try
632
- set matchingMsgs to (messages of mb whose id is ${id})
735
+ set matchingMsgs to (messages of mb whose id is ${Number(id)})
633
736
  if (count of matchingMsgs) > 0 then
634
737
  set msg to item 1 of matchingMsgs
635
738
  set theReply to reply msg with opening window${replyAllClause}
@@ -674,7 +777,7 @@ export class AppleMailManager {
674
777
  repeat with acct in accounts
675
778
  repeat with mb in mailboxes of acct
676
779
  try
677
- set matchingMsgs to (messages of mb whose id is ${id})
780
+ set matchingMsgs to (messages of mb whose id is ${Number(id)})
678
781
  if (count of matchingMsgs) > 0 then
679
782
  set msg to item 1 of matchingMsgs
680
783
  set theForward to forward msg with opening window
@@ -707,7 +810,7 @@ export class AppleMailManager {
707
810
  repeat with acct in accounts
708
811
  repeat with mb in mailboxes of acct
709
812
  try
710
- set matchingMsgs to (messages of mb whose id is ${id})
813
+ set matchingMsgs to (messages of mb whose id is ${Number(id)})
711
814
  if (count of matchingMsgs) > 0 then
712
815
  set msg to item 1 of matchingMsgs
713
816
  ${operation}
@@ -795,7 +898,7 @@ export class AppleMailManager {
795
898
  repeat with acct in accounts
796
899
  repeat with mb in mailboxes of acct
797
900
  try
798
- set matchingMsgs to (messages of mb whose id is ${id})
901
+ set matchingMsgs to (messages of mb whose id is ${Number(id)})
799
902
  if (count of matchingMsgs) > 0 then
800
903
  set msg to item 1 of matchingMsgs
801
904
  set destMailbox to mailbox "${safeMailbox}" of account "${safeAccount}"
@@ -915,7 +1018,7 @@ export class AppleMailManager {
915
1018
  repeat with acct in accounts
916
1019
  repeat with mb in mailboxes of acct
917
1020
  try
918
- set matchingMsgs to (messages of mb whose id is ${id})
1021
+ set matchingMsgs to (messages of mb whose id is ${Number(id)})
919
1022
  if (count of matchingMsgs) > 0 then
920
1023
  set msg to item 1 of matchingMsgs
921
1024
  set outputText to ""
@@ -961,14 +1064,30 @@ export class AppleMailManager {
961
1064
  * Save an attachment from a message to disk.
962
1065
  */
963
1066
  saveAttachment(id, attachmentName, savePath) {
1067
+ // Validate attachment name: block path separators, traversal, null bytes, and backslashes
1068
+ if (/[/\\\0]/.test(attachmentName) || attachmentName.includes("..")) {
1069
+ console.error(`Invalid attachment name: "${attachmentName}"`);
1070
+ return false;
1071
+ }
1072
+ // Resolve the save path to prevent symlink / ".." traversal bypass
1073
+ const resolvedPath = resolve(savePath);
1074
+ const allowedPrefixes = [homedir(), "/tmp", "/private/tmp", "/Volumes"];
1075
+ const isAllowed = allowedPrefixes.some((prefix) => resolvedPath.startsWith(prefix));
1076
+ if (!isAllowed) {
1077
+ console.error(`Save path "${savePath}" is outside allowed directories`);
1078
+ return false;
1079
+ }
964
1080
  const safeName = escapeForAppleScript(attachmentName);
965
- const safePath = escapeForAppleScript(savePath);
1081
+ const safePath = escapeForAppleScript(resolvedPath);
1082
+ // Use Number(id) as defense-in-depth — the Zod schema already enforces numeric IDs,
1083
+ // but this ensures raw interpolation into AppleScript is safe even if validation changes.
1084
+ const numericId = Number(id);
966
1085
  const script = buildAppLevelScript(`
967
1086
  try
968
1087
  repeat with acct in accounts
969
1088
  repeat with mb in mailboxes of acct
970
1089
  try
971
- set matchingMsgs to (messages of mb whose id is ${id})
1090
+ set matchingMsgs to (messages of mb whose id is ${numericId})
972
1091
  if (count of matchingMsgs) > 0 then
973
1092
  set msg to item 1 of matchingMsgs
974
1093
  repeat with att in mail attachments of msg
package/build/types.d.ts CHANGED
@@ -207,6 +207,26 @@ export interface MoveMessageParams {
207
207
  /** Account containing the destination mailbox */
208
208
  account?: string;
209
209
  }
210
+ /**
211
+ * A single recipient in a serial email (mail merge) operation.
212
+ */
213
+ export interface SerialEmailRecipient {
214
+ /** Recipient email address */
215
+ email: string;
216
+ /** Variable values for placeholder replacement (e.g., { Name: "Alice", Company: "Acme" }) */
217
+ variables: Record<string, string>;
218
+ }
219
+ /**
220
+ * Result of sending a serial email to a single recipient.
221
+ */
222
+ export interface SerialEmailResult {
223
+ /** Recipient email address */
224
+ email: string;
225
+ /** Whether the email was sent successfully */
226
+ success: boolean;
227
+ /** Error message if sending failed */
228
+ error?: string;
229
+ }
210
230
  /**
211
231
  * Individual check result in a health check.
212
232
  */
@@ -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.1.1",
3
+ "version": "1.2.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",
@@ -18,6 +18,8 @@
18
18
  "start": "node build/index.js",
19
19
  "dev": "tsc --watch",
20
20
  "test": "vitest run",
21
+ "test:integration": "vitest run --config vitest.integration.config.ts",
22
+ "test:all": "vitest run && vitest run --config vitest.integration.config.ts",
21
23
  "test:watch": "vitest",
22
24
  "test:coverage": "vitest run --coverage",
23
25
  "lint": "eslint src",