appleMailManager.d.ts 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387
  1. /**
  2. * Apple Mail Manager
  3. *
  4. * Handles all interactions with Apple Mail via AppleScript.
  5. * This is the core service layer for the MCP server.
  6. *
  7. * Architecture:
  8. * - Text escaping is handled by dedicated helper functions
  9. * - AppleScript generation uses template builders for consistency
  10. * - All public methods return typed results (no raw strings)
  11. * - Error handling is consistent across all operations
  12. *
  13. * @module services/appleMailManager
  14. */
  15. import type { Message, MessageContent, Mailbox, Account, Attachment, HealthCheckResult, MailStats, BatchOperationResult, SyncStatus, RecentlyReceivedStats, MailRule, Contact, EmailTemplate, SerialEmailRecipient, SerialEmailResult } from "../types.js";
  16. /**
  17. * Manager class for Apple Mail operations.
  18. *
  19. * Provides methods for:
  20. * - Reading and searching messages
  21. * - Sending emails
  22. * - Managing mailboxes
  23. * - Listing accounts
  24. *
  25. * All operations are synchronous since they rely on AppleScript
  26. * execution via osascript. Error handling is consistent: methods
  27. * return null/false/empty-array on failure rather than throwing.
  28. */
  29. export interface SearchConditionFilters {
  30. query?: string;
  31. from?: string;
  32. subject?: string;
  33. isRead?: boolean;
  34. isFlagged?: boolean;
  35. }
  36. /**
  37. * Build the AppleScript `whose` clause for searchMessages from a filter set.
  38. *
  39. * - `query` is a subject-OR-sender substring match, parenthesized so it groups
  40. * correctly when ANDed with other filters.
  41. * - `from` and `subject` are substring matches (`sender`/`subject` contains).
  42. * - `isRead` / `isFlagged` are boolean status checks.
  43. * - Returns "" when no filters are set. Every interpolated value is escaped.
  44. *
  45. * Exported for unit testing: the bug this addresses (filters declared in the
  46. * tool schema but silently dropped) lived in this logic, so it gets direct
  47. * coverage independent of Mail.app.
  48. */
  49. export declare function buildSearchCondition(filters: SearchConditionFilters): string;
  50. export declare class AppleMailManager {
  51. /**
  52. * Default account used when no account is specified.
  53. */
  54. private defaultAccount;
  55. /**
  56. * TTL cache for expensive AppleScript queries that rarely change.
  57. * Caches account list and per-account mailbox names to avoid
  58. * redundant AppleScript roundtrips on every tool call.
  59. */
  60. private cache;
  61. /** Cache TTL in milliseconds (60 seconds). */
  62. private readonly CACHE_TTL_MS;
  63. /**
  64. * Returns cached accounts or fetches fresh data if cache is expired/empty.
  65. */
  66. private getCachedAccounts;
  67. /**
  68. * Returns cached mailbox names for an account, or fetches fresh.
  69. * This caches only the name list used by resolveMailbox(), not the
  70. * full Mailbox objects with counts (which change frequently).
  71. */
  72. private getCachedMailboxNames;
  73. /**
  74. * Invalidate all caches. Call after operations that change
  75. * mailbox structure (create/delete/rename mailbox).
  76. */
  77. private invalidateCache;
  78. /**
  79. * Resolves the account to use for an operation.
  80. * Queries Mail.app's configured default send account, then falls back
  81. * to the first available account.
  82. */
  83. private resolveAccount;
  84. /**
  85. * Resolves a mailbox name to its actual name in the account.
  86. *
  87. * Different account types (IMAP, Exchange, iCloud) use different
  88. * mailbox naming conventions:
  89. * - IMAP/Gmail: "INBOX", "Sent", "Drafts"
  90. * - Exchange: "Inbox", "Sent Items", "Deleted Items"
  91. * - iCloud: "INBOX", "Sent", "Trash"
  92. *
  93. * This method tries to find a matching mailbox by:
  94. * 1. Exact match
  95. * 2. Case-insensitive match
  96. * 3. Known aliases (e.g., "Sent" -> "Sent Items")
  97. *
  98. * @param mailbox - Requested mailbox name
  99. * @param account - Account to search in
  100. * @returns Actual mailbox name, or original if not found
  101. */
  102. private resolveMailbox;
  103. /**
  104. * Search for messages matching criteria.
  105. *
  106. * @param query - Text to search for in subject or sender
  107. * @param mailbox - Mailbox to search in (e.g., "INBOX")
  108. * @param account - Account to search in
  109. * @param limit - Maximum number of results
  110. * @returns Array of matching messages
  111. */
  112. searchMessages(query?: string, mailbox?: string, account?: string, limit?: number, dateFrom?: string, dateTo?: string, from?: string, subject?: string, isRead?: boolean, isFlagged?: boolean): Message[];
  113. /**
  114. * Get a message by ID.
  115. *
  116. * Note: Mail.app message IDs are unique per mailbox. This method searches
  117. * all mailboxes in all accounts to find the message.
  118. */
  119. getMessageById(id: string): Message | null;
  120. /**
  121. * Get the content of a message.
  122. */
  123. getMessageContent(id: string): MessageContent | null;
  124. /**
  125. * Get the raw MIME source of a message.
  126. * Used as fallback for attachment extraction when AppleScript
  127. * mail attachments returns empty.
  128. *
  129. * Timeout is 2x the default (120s) because `source of msg` returns
  130. * the entire raw message including base64-encoded attachments —
  131. * a 20MB attachment can take several seconds over Exchange/IMAP.
  132. */
  133. getRawSource(id: string): string | null;
  134. /**
  135. * List messages in a mailbox.
  136. *
  137. * @param mailbox - Mailbox to list from (default: INBOX)
  138. * @param account - Account to list from
  139. * @param limit - Maximum number of messages
  140. * @returns Array of messages
  141. */
  142. listMessages(mailbox?: string, account?: string, limit?: number, from?: string, offset?: number): Message[];
  143. /**
  144. * Parse message list output from AppleScript.
  145. *
  146. * Two emission schemas, disambiguated by length:
  147. * 7 fields: single-mailbox — ...|hasAtt (mailbox from caller)
  148. * 8 fields: all-mailboxes — ...|mailbox|hasAtt
  149. *
  150. * `hasAttachments` here is the fast-path AppleScript count only; it will
  151. * false-negative for MIME-embedded attachments (a known AppleScript
  152. * limitation). Use getMessage or list-attachments for authoritative info.
  153. */
  154. private parseMessageList;
  155. /**
  156. * Send an email.
  157. *
  158. * @param to - Recipient email addresses
  159. * @param subject - Email subject
  160. * @param body - Email body (plain text)
  161. * @param cc - CC recipients
  162. * @param bcc - BCC recipients
  163. * @param account - Account to send from
  164. * @returns true if sent successfully
  165. */
  166. sendEmail(to: string[], subject: string, body: string, cc?: string[], bcc?: string[], account?: string, attachments?: string[]): boolean;
  167. /**
  168. * Send individual personalized emails to a list of recipients (mail merge).
  169. *
  170. * Replaces {{placeholder}} tokens in subject and body with per-recipient values.
  171. * Each recipient receives their own individual email.
  172. *
  173. * @param recipients - List of recipient objects with email and variable values
  174. * @param subject - Email subject (may contain {{placeholders}})
  175. * @param body - Email body (may contain {{placeholders}})
  176. * @param account - Account to send from
  177. * @param delayMs - Delay between sends in milliseconds (default: 500, max: 10000)
  178. * @returns Array of per-recipient results
  179. */
  180. sendSerialEmail(recipients: SerialEmailRecipient[], subject: string, body: string, account?: string, delayMs?: number): SerialEmailResult[];
  181. /**
  182. * Create a draft email (saved to Drafts folder, not sent).
  183. *
  184. * @param to - Recipient email addresses
  185. * @param subject - Email subject
  186. * @param body - Email body (plain text)
  187. * @param cc - CC recipients
  188. * @param bcc - BCC recipients
  189. * @param account - Account to create draft in
  190. * @returns true if draft created successfully
  191. */
  192. createDraft(to: string[], subject: string, body: string, cc?: string[], bcc?: string[], account?: string, attachments?: string[]): boolean;
  193. /**
  194. * Reply to a message.
  195. *
  196. * @param id - Message ID to reply to
  197. * @param body - Reply body
  198. * @param replyAll - If true, reply to all recipients
  199. * @param send - If true, send immediately; if false, save as draft
  200. * @returns true if reply created/sent successfully
  201. */
  202. replyToMessage(id: string, body: string, replyAll?: boolean, send?: boolean): boolean;
  203. /**
  204. * Forward a message.
  205. *
  206. * @param id - Message ID to forward
  207. * @param to - Recipients to forward to
  208. * @param body - Optional body to prepend
  209. * @param send - If true, send immediately; if false, save as draft
  210. * @returns true if forward created/sent successfully
  211. */
  212. forwardMessage(id: string, to: string[], body?: string, send?: boolean): boolean;
  213. /**
  214. * Helper to find and operate on a message by ID.
  215. */
  216. private findMessageScript;
  217. /**
  218. * Mark a message as read.
  219. */
  220. markAsRead(id: string): boolean;
  221. /**
  222. * Mark a message as unread.
  223. */
  224. markAsUnread(id: string): boolean;
  225. /**
  226. * Flag a message.
  227. */
  228. flagMessage(id: string): boolean;
  229. /**
  230. * Unflag a message.
  231. */
  232. unflagMessage(id: string): boolean;
  233. /**
  234. * Delete a message.
  235. */
  236. deleteMessage(id: string): boolean;
  237. /**
  238. * Move a message to a different mailbox.
  239. */
  240. moveMessage(id: string, mailbox: string, account?: string): boolean;
  241. /**
  242. * Delete multiple messages at once.
  243. *
  244. * @param ids - Array of message IDs to delete
  245. * @returns Array of results for each message
  246. */
  247. batchDeleteMessages(ids: string[]): BatchOperationResult[];
  248. /**
  249. * Move multiple messages to a mailbox at once.
  250. *
  251. * @param ids - Array of message IDs to move
  252. * @param mailbox - Destination mailbox name
  253. * @param account - Account containing the destination mailbox
  254. * @returns Array of results for each message
  255. */
  256. batchMoveMessages(ids: string[], mailbox: string, account?: string): BatchOperationResult[];
  257. /**
  258. * Mark multiple messages as read at once.
  259. */
  260. batchMarkAsRead(ids: string[]): BatchOperationResult[];
  261. /**
  262. * Mark multiple messages as unread at once.
  263. */
  264. batchMarkAsUnread(ids: string[]): BatchOperationResult[];
  265. /**
  266. * Flag multiple messages at once.
  267. */
  268. batchFlagMessages(ids: string[]): BatchOperationResult[];
  269. /**
  270. * Unflag multiple messages at once.
  271. */
  272. batchUnflagMessages(ids: string[]): BatchOperationResult[];
  273. /**
  274. * List attachments for a message.
  275. * Tries AppleScript first, falls back to MIME source parsing
  276. * when AppleScript returns empty (known issue across all account types).
  277. */
  278. listAttachments(id: string): Attachment[];
  279. /**
  280. * Save an attachment from a message to disk.
  281. * Tries AppleScript first, falls back to MIME source extraction
  282. * when AppleScript can't find the attachment.
  283. */
  284. saveAttachment(id: string, attachmentName: string, savePath: string): boolean;
  285. /**
  286. * List all mailboxes for an account.
  287. */
  288. listMailboxes(account?: string): Mailbox[];
  289. /**
  290. * Get unread count for a mailbox.
  291. */
  292. getUnreadCount(mailbox?: string, account?: string): number;
  293. /**
  294. * Create a new mailbox.
  295. */
  296. createMailbox(name: string, account?: string): boolean;
  297. /**
  298. * Delete a mailbox.
  299. */
  300. deleteMailbox(name: string, account?: string): boolean;
  301. /**
  302. * Rename a mailbox by creating a new one, moving messages, and deleting the old one.
  303. */
  304. renameMailbox(oldName: string, newName: string, account?: string): boolean;
  305. /**
  306. * List all mail accounts (uses cache).
  307. */
  308. listAccounts(): Account[];
  309. /**
  310. * Fetches account list directly from Mail.app via AppleScript.
  311. * Used internally by the cache; prefer getCachedAccounts() or listAccounts().
  312. */
  313. private fetchAccounts;
  314. /**
  315. * Fetches mailbox names for an account directly from Mail.app.
  316. * Used internally by the cache; prefer getCachedMailboxNames().
  317. */
  318. private fetchMailboxNames;
  319. /**
  320. * List all mail rules.
  321. */
  322. listRules(): MailRule[];
  323. /**
  324. * Enable or disable a mail rule.
  325. */
  326. setRuleEnabled(ruleName: string, enabled: boolean): boolean;
  327. /**
  328. * Search contacts by name or email.
  329. */
  330. searchContacts(query: string): Contact[];
  331. private templates;
  332. private nextTemplateId;
  333. /**
  334. * List all stored templates.
  335. */
  336. listTemplates(): EmailTemplate[];
  337. /**
  338. * Get a template by ID.
  339. */
  340. getTemplate(id: string): EmailTemplate | null;
  341. /**
  342. * Create or update a template.
  343. */
  344. saveTemplate(name: string, subject: string, body: string, to?: string[], cc?: string[], id?: string): EmailTemplate;
  345. /**
  346. * Delete a template.
  347. */
  348. deleteTemplate(id: string): boolean;
  349. /**
  350. * Use a template to create a draft.
  351. */
  352. useTemplate(id: string, overrides?: {
  353. to?: string[];
  354. cc?: string[];
  355. subject?: string;
  356. body?: string;
  357. }): boolean;
  358. /**
  359. * Run health check on Mail.app connectivity.
  360. */
  361. healthCheck(): HealthCheckResult;
  362. /**
  363. * Get mail statistics.
  364. */
  365. getMailStats(): MailStats;
  366. /**
  367. * Get counts of recently received messages.
  368. *
  369. * Only counts messages in INBOX for performance (scanning all mailboxes
  370. * is too slow for large accounts).
  371. *
  372. * @returns Counts of messages received in last 24h, 7d, and 30d
  373. */
  374. getRecentlyReceivedStats(): RecentlyReceivedStats;
  375. /**
  376. * Get sync status for Mail.app.
  377. *
  378. * Checks for sync activity indicators like:
  379. * - Activity monitor status
  380. * - Network activity status
  381. * - Background refresh indicators
  382. *
  383. * @returns Sync status information
  384. */
  385. getSyncStatus(): SyncStatus;
  386. }
  387. //# sourceMappingURL=appleMailManager.d.ts.map