apple-mail-mcp 2.19.22 → 2.20.0
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 +94 -33
- package/build/cli.js +58 -17
- package/build/index.js +300 -68
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -164,11 +164,11 @@ tool, and troubleshooting. Verify any time by running the **`doctor`** tool.
|
|
|
164
164
|
| **Search Messages** | Search by sender, subject, content, date range, read/flagged status — across all accounts |
|
|
165
165
|
| **Read Messages** | Get full email content (plain text or HTML) |
|
|
166
166
|
| **Read Headers** | Get a message's raw RFC 5322 headers — the author's `Date:`, Message-ID, threading ids, `Received:` trace — without downloading the body |
|
|
167
|
-
| **Send Email** | Compose and send new emails (attach by file path or inline base64 content)
|
|
167
|
+
| **Send Email** | Compose and send new emails (attach by file path or inline base64 content; optional `inReplyTo`/`references` threading over SMTP) |
|
|
168
168
|
| **Send Serial Email** | Mail merge — send personalized emails to a list of recipients with {{placeholder}} support |
|
|
169
|
-
| **Create Draft** | Save emails to Drafts folder (attach by file path or inline base64 content)
|
|
170
|
-
| **Reply** | Reply to messages (with reply-all support)
|
|
171
|
-
| **Forward** | Forward messages to new recipients
|
|
169
|
+
| **Create Draft** | Save emails to Drafts folder (attach by file path or inline base64 content; threaded drafts filed over IMAP) |
|
|
170
|
+
| **Reply** | Reply to messages (with reply-all support and attachments) |
|
|
171
|
+
| **Forward** | Forward messages to new recipients (with extra attachments) |
|
|
172
172
|
| **Get Thread** | Group a conversation by normalized subject (across AppleScript or IMAP) |
|
|
173
173
|
| **Mark Read/Unread** | Change read status (single or batch) |
|
|
174
174
|
| **Flag/Unflag** | Flag or unflag messages (single or batch) |
|
|
@@ -402,16 +402,19 @@ Send a new email immediately.
|
|
|
402
402
|
|
|
403
403
|
**⚠️ Safety:** Sends real mail immediately and cannot be unsent. Confirm the recipients, subject, and body with the user before calling.
|
|
404
404
|
|
|
405
|
-
| Parameter | Type | Required | Description
|
|
406
|
-
| ------------- | --------------------------------------- | -------- |
|
|
407
|
-
| `to` | string[] | Yes | Recipient addresses
|
|
408
|
-
| `subject` | string | Yes | Email subject
|
|
409
|
-
| `body` | string | Yes | Email body (plain text)
|
|
410
|
-
| `cc` | string[] | No | CC recipients
|
|
411
|
-
| `bcc` | string[] | No | BCC recipients
|
|
412
|
-
| `account` | string | No | Mail.app account label, or an email-form SMTP From override. An SMTP override must match `APPLE_MAIL_MCP_SMTP_USER`, `APPLE_MAIL_MCP_SMTP_FROM`, or an address in `APPLE_MAIL_MCP_SMTP_ALLOWED_FROM`
|
|
413
|
-
| `attachments` | (string \| {filename, contentBase64})[] | No | Up to 20 attachments: absolute file paths inside the configured read roots (e.g., `"/Users/me/Documents/report.pdf"`) and/or inline `{filename, contentBase64}` objects up to 25 MiB decoded each
|
|
414
|
-
| `transport` | `"applescript"` \| `"smtp"` | No | Send transport. If omitted, **SMTP is used automatically when configured** (otherwise AppleScript). Pass `"smtp"` to require clean MIME, or `"applescript"` to force the Mail.app path — see [SMTP transport](#smtp-transport)
|
|
405
|
+
| Parameter | Type | Required | Description |
|
|
406
|
+
| ------------- | --------------------------------------- | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
407
|
+
| `to` | string[] | Yes | Recipient addresses |
|
|
408
|
+
| `subject` | string | Yes | Email subject |
|
|
409
|
+
| `body` | string | Yes | Email body (plain text) |
|
|
410
|
+
| `cc` | string[] | No | CC recipients |
|
|
411
|
+
| `bcc` | string[] | No | BCC recipients |
|
|
412
|
+
| `account` | string | No | Mail.app account label, or an email-form SMTP From override. An SMTP override must match `APPLE_MAIL_MCP_SMTP_USER`, `APPLE_MAIL_MCP_SMTP_FROM`, or an address in `APPLE_MAIL_MCP_SMTP_ALLOWED_FROM` |
|
|
413
|
+
| `attachments` | (string \| {filename, contentBase64})[] | No | Up to 20 attachments: absolute file paths inside the configured read roots (e.g., `"/Users/me/Documents/report.pdf"`) and/or inline `{filename, contentBase64}` objects up to 25 MiB decoded each |
|
|
414
|
+
| `transport` | `"applescript"` \| `"smtp"` | No | Send transport. If omitted, **SMTP is used automatically when configured** (otherwise AppleScript). Pass `"smtp"` to require clean MIME, or `"applescript"` to force the Mail.app path — see [SMTP transport](#smtp-transport) |
|
|
415
|
+
| `replyTo` | string | No | SMTP only: `Reply-To` header, when replies should go somewhere other than the From address. Ignored on the AppleScript transport |
|
|
416
|
+
| `inReplyTo` | string | No | SMTP only (2.20.0): thread the message as a reply — the parent's `Message-ID` token with angle brackets, e.g. `"<abc@mail.example.com>"`. Emitted verbatim, never RFC 2047-encoded. See [Threading headers](#threading-headers-inreplyto--references) |
|
|
417
|
+
| `references` | string[] | No | SMTP only (2.20.0): the `References` chain, oldest first (the parent's own `References` + its `Message-ID`). Defaults to `[inReplyTo]` when omitted |
|
|
415
418
|
|
|
416
419
|
**Example:**
|
|
417
420
|
|
|
@@ -425,6 +428,22 @@ Send a new email immediately.
|
|
|
425
428
|
}
|
|
426
429
|
```
|
|
427
430
|
|
|
431
|
+
##### Threading headers (`inReplyTo` / `references`)
|
|
432
|
+
|
|
433
|
+
`send-email` and `create-draft` accept caller-built threading headers (2.20.0, #267) for
|
|
434
|
+
callers that compose the recipients and quoted text themselves. **Prefer
|
|
435
|
+
`reply-to-message`** — it derives recipients, `In-Reply-To`/`References` and the quote
|
|
436
|
+
from the original, and (since 2.20.0) takes attachments too.
|
|
437
|
+
|
|
438
|
+
- Each value must be a single RFC 5322 msg-id token **with** angle brackets
|
|
439
|
+
(`<left@right>`, no whitespace); anything else is rejected by validation.
|
|
440
|
+
- The headers are always emitted as plain `<id>` tokens — never RFC 2047-encoded, even
|
|
441
|
+
for Outlook/Exchange IDs longer than 76 characters (a long header is folded at
|
|
442
|
+
whitespace between tokens, which is legal and does not alter the id).
|
|
443
|
+
- Mail.app's AppleScript cannot set these headers, so they are **never silently
|
|
444
|
+
dropped**: `send-email` refuses them unless the SMTP transport is in use, and
|
|
445
|
+
`create-draft` files a threaded draft over IMAP instead of through Mail.app (below).
|
|
446
|
+
|
|
428
447
|
##### SMTP transport
|
|
429
448
|
|
|
430
449
|
On macOS 15+ (Sequoia/Tahoe), Mail.app wraps any AppleScript-injected body in
|
|
@@ -783,10 +802,24 @@ Save an email to Drafts without sending.
|
|
|
783
802
|
| `body` | string | Yes | Email body (plain text) |
|
|
784
803
|
| `cc` | string[] | No | CC recipients |
|
|
785
804
|
| `bcc` | string[] | No | BCC recipients |
|
|
786
|
-
| `account` | string | No | Account for draft
|
|
805
|
+
| `account` | string | No | Account for draft. With `inReplyTo`/`references` it must name a configured IMAP account (label or login) |
|
|
787
806
|
| `attachments` | (string \| {filename, contentBase64})[] | No | Up to 20 attachments: absolute file paths inside the configured read roots and/or inline `{filename, contentBase64}` objects up to 25 MiB decoded each |
|
|
788
|
-
|
|
789
|
-
|
|
807
|
+
| `inReplyTo` | string | No | (2.20.0) Parent `Message-ID` token, e.g. `"<abc@mail.example.com>"` — makes this a **threaded draft** (see below) |
|
|
808
|
+
| `references` | string[] | No | (2.20.0) `References` chain, oldest first; defaults to `[inReplyTo]` |
|
|
809
|
+
|
|
810
|
+
**Returns:** Confirmation that draft was created, with `transport` (`applescript`, or `imap` for a threaded draft).
|
|
811
|
+
|
|
812
|
+
**Threaded drafts (2.20.0, #267):** Mail.app's `outgoing message` has no header property,
|
|
813
|
+
so a draft with `inReplyTo`/`references` cannot go through AppleScript. Instead the
|
|
814
|
+
server composes it with the same MIME builder as the SMTP send (attachments included) and
|
|
815
|
+
files it over IMAP into the account's Drafts mailbox (SPECIAL-USE `\Drafts`), flagged
|
|
816
|
+
`\Draft`. The account is `account` when given (it must be a configured IMAP account);
|
|
817
|
+
otherwise the IMAP account whose login is the SMTP identity, else the only configured
|
|
818
|
+
IMAP account. When none qualifies — IMAP unconfigured, an unknown `account`, or several
|
|
819
|
+
accounts and no way to choose — the call **fails and files nothing**; it never falls back
|
|
820
|
+
to an unthreaded Mail.app draft. The result names the Drafts `mailbox`, the `account` and
|
|
821
|
+
the draft's `messageId`. Without threading headers, `create-draft` is unchanged
|
|
822
|
+
(AppleScript).
|
|
790
823
|
|
|
791
824
|
#### `get-thread`
|
|
792
825
|
|
|
@@ -832,13 +865,14 @@ Map `imap:` message IDs to their numeric Mail.app IDs, via each message's RFC 53
|
|
|
832
865
|
|
|
833
866
|
Reply to an existing message.
|
|
834
867
|
|
|
835
|
-
| Parameter
|
|
836
|
-
|
|
|
837
|
-
| `id`
|
|
838
|
-
| `body`
|
|
839
|
-
| `replyAll`
|
|
840
|
-
| `send`
|
|
841
|
-
| `transport`
|
|
868
|
+
| Parameter | Type | Required | Description |
|
|
869
|
+
| ------------- | --------------------------------------- | -------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
870
|
+
| `id` | string | Yes | Message ID to reply to |
|
|
871
|
+
| `body` | string | Yes | Reply body (plain text; HTML tags such as `<br>` are not rendered) |
|
|
872
|
+
| `replyAll` | boolean | No | Reply to all recipients (default: false) |
|
|
873
|
+
| `send` | boolean | No | Send immediately (default: true, false = save as draft) |
|
|
874
|
+
| `transport` | string | No | `smtp` or `applescript`; omitted prefers configured SMTP when sending. Drafts use AppleScript. |
|
|
875
|
+
| `attachments` | (string \| {filename, contentBase64})[] | No | Up to 20 attachments, same rules as `send-email`: absolute file paths inside the configured read roots and/or inline `{filename, contentBase64}` objects up to 25 MiB decoded each |
|
|
842
876
|
|
|
843
877
|
**Example - Reply to sender only:**
|
|
844
878
|
|
|
@@ -860,6 +894,30 @@ Reply to an existing message.
|
|
|
860
894
|
}
|
|
861
895
|
```
|
|
862
896
|
|
|
897
|
+
**Example - Reply all with an attachment (2.20.0):**
|
|
898
|
+
|
|
899
|
+
```json
|
|
900
|
+
{
|
|
901
|
+
"id": "imap:eyJhIjoiV29yayIsInAiOiJJTkJPWCIsInUiOjQyfQ",
|
|
902
|
+
"body": "Here is the PDF you asked for.",
|
|
903
|
+
"replyAll": true,
|
|
904
|
+
"attachments": [
|
|
905
|
+
"/Users/me/Documents/quote.pdf",
|
|
906
|
+
{ "filename": "notes.txt", "contentBase64": "SGVsbG8K" }
|
|
907
|
+
]
|
|
908
|
+
}
|
|
909
|
+
```
|
|
910
|
+
|
|
911
|
+
**Attachments (2.20.0, #267):** same schema, limits (20 items, 25 MiB each) and read
|
|
912
|
+
roots as `send-email`, validated before the original is fetched — an out-of-roots path
|
|
913
|
+
fails with exactly `send-email`'s error. Recipients, threading headers and the quote are
|
|
914
|
+
identical to the same call without attachments. Over SMTP the files ride the same
|
|
915
|
+
nodemailer path `send-email` uses, so the Sent-folder copy (`sentCopy`) carries them too.
|
|
916
|
+
On the AppleScript transport they are added with `make new attachment` **after** the
|
|
917
|
+
reply content is set and before `save`/`send` (inline items are written to `0600` temp
|
|
918
|
+
files and removed afterwards), so a `send: false` draft contains both the attachments and
|
|
919
|
+
the quoted original. Results report `attachmentCount`.
|
|
920
|
+
|
|
863
921
|
**Delivery:** `imap:` IDs are read directly from their encoded account, mailbox, and UID. Numeric IDs are read through Mail.app. With SMTP configured, replies use clean MIME with the original `Message-ID` in `In-Reply-To` and the full `References` chain. Only the original plain-text body is quoted; the new text is not. Pass `transport: "smtp"` to require this path, or `transport: "applescript"` to use Mail.app explicitly.
|
|
864
922
|
|
|
865
923
|
**AppleScript transport quoting (v2.19.12):** every draft (`send: false`) and any send that falls back to AppleScript (SMTP not configured) also gets the original quoted — an "On \<date\>, \<sender\> wrote:" attribution line followed by the `> `-prefixed original body, built the same way as the SMTP path. Mail's own `reply`/`forward` AppleScript commands generate this quote internally but expose no way to read it back, so it's built here instead of relying on Mail's (unreadable) copy. Unlike SMTP, this path has no sending-identity restriction — it's read-only source lookup, not sending.
|
|
@@ -868,7 +926,7 @@ Reply to an existing message.
|
|
|
868
926
|
|
|
869
927
|
The direct IMAP source read is bounded to 25 MiB, including MIME attachments. Its account login must match the SMTP login, configured From, or an explicitly configured `APPLE_MAIL_MCP_SMTP_ALLOWED_FROM` identity; otherwise the call fails rather than sending from an unrelated account. The SMTP configuration still represents a single sending identity, not a per-account SMTP registry.
|
|
870
928
|
|
|
871
|
-
Success includes `transport` and, for SMTP when returned by the server, the new `messageId`. SMTP
|
|
929
|
+
Success includes `transport`, `attachmentCount` and, for SMTP when returned by the server, the new `messageId`. Over SMTP a best-effort Sent-folder copy is filed over IMAP exactly as for `send-email` (`sentCopy` / `sentCopyError`, see [SMTP transport](#smtp-transport)). `send-email` is for **new conversations**: adding `Re:` to its subject does not add threading headers. Use this reply tool with the original id instead.
|
|
872
930
|
|
|
873
931
|
**⚠️ Safety:** With the default `send: true`, sends real mail immediately and cannot be unsent. Confirm the recipients, subject, and body with the user before calling (or pass `send: false` to save a draft for review).
|
|
874
932
|
|
|
@@ -878,15 +936,18 @@ Success includes `transport` and, for SMTP when returned by the server, the new
|
|
|
878
936
|
|
|
879
937
|
Forward a message to new recipients.
|
|
880
938
|
|
|
881
|
-
| Parameter
|
|
882
|
-
|
|
|
883
|
-
| `id`
|
|
884
|
-
| `to`
|
|
885
|
-
| `body`
|
|
886
|
-
| `send`
|
|
887
|
-
| `transport`
|
|
939
|
+
| Parameter | Type | Required | Description |
|
|
940
|
+
| ------------- | --------------------------------------- | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
941
|
+
| `id` | string | Yes | Message ID to forward |
|
|
942
|
+
| `to` | string[] | Yes | Recipients to forward to |
|
|
943
|
+
| `body` | string | No | Message to prepend (plain text) |
|
|
944
|
+
| `send` | boolean | No | Send immediately (default: true, false = save as draft) |
|
|
945
|
+
| `transport` | string | No | `smtp` or `applescript`; omitted prefers configured SMTP when sending. Drafts use AppleScript. |
|
|
946
|
+
| `attachments` | (string \| {filename, contentBase64})[] | No | Up to 20 additional attachments, same rules as `send-email`: absolute file paths inside the configured read roots and/or inline `{filename, contentBase64}` objects up to 25 MiB decoded each |
|
|
947
|
+
|
|
948
|
+
**Delivery:** uses the same source lookup, transport selection, 25 MiB source limit, account-identity check, and failure behavior as `reply-to-message`. A forward deliberately starts a new conversation, so it has no `In-Reply-To` or `References` headers. The existing plain-text forwarding behavior is unchanged: original attachments are not reattached. See [SMTP transport](#smtp-transport). On the AppleScript transport, a `body` is merged with the forwarded-message header block and original text the same way the SMTP path builds it (see `reply-to-message`'s AppleScript transport quoting note); with no `body`, Mail's own forward content is left untouched — unless `attachments` are given, in which case the forwarded-message block is built the same way so Mail has a paragraph to anchor the new files after.
|
|
888
949
|
|
|
889
|
-
**
|
|
950
|
+
**Attachments (2.20.0, #267):** `attachments` adds new files to the forward, with or without a prepended `body`, under the same rules, validation and per-transport mechanics as `reply-to-message`'s attachments. They are independent of the original's attachments (which the SMTP path still does not reattach).
|
|
890
951
|
|
|
891
952
|
SMTP forwarding requires a readable plain-text original. HTML-only IMAP messages and failed Mail.app body reads return an error before sending instead of silently omitting the original content. Explicitly select `transport: "applescript"` to forward these with Mail.app; no automatic fallback occurs. An intentionally empty plain-text message is still valid.
|
|
892
953
|
|
package/build/cli.js
CHANGED
|
@@ -57846,6 +57846,7 @@ __export(imapClient_exports, {
|
|
|
57846
57846
|
decodeImapId: () => decodeImapId,
|
|
57847
57847
|
dropAllPools: () => dropAllPools,
|
|
57848
57848
|
encodeImapId: () => encodeImapId,
|
|
57849
|
+
imapAppendDraft: () => imapAppendDraft,
|
|
57849
57850
|
imapAppendSentCopy: () => imapAppendSentCopy,
|
|
57850
57851
|
imapBatchDelete: () => imapBatchDelete,
|
|
57851
57852
|
imapBatchFlag: () => imapBatchFlag,
|
|
@@ -57883,6 +57884,7 @@ __export(imapClient_exports, {
|
|
|
57883
57884
|
matchMailbox: () => matchMailbox,
|
|
57884
57885
|
normalizeMessageId: () => normalizeMessageId,
|
|
57885
57886
|
omittedNote: () => omittedNote,
|
|
57887
|
+
resolveDraftImapAccount: () => resolveDraftImapAccount,
|
|
57886
57888
|
resolveImapConfig: () => resolveImapConfig,
|
|
57887
57889
|
resolveImapConfigs: () => resolveImapConfigs,
|
|
57888
57890
|
resolveMailboxPath: () => resolveMailboxPath,
|
|
@@ -58940,6 +58942,40 @@ async function imapAppendSentCopy(smtpUser, raw, deps = {}) {
|
|
|
58940
58942
|
return { attempted: true, success: false, error: errText(e) };
|
|
58941
58943
|
}
|
|
58942
58944
|
}
|
|
58945
|
+
function resolveDraftImapAccount(account, smtpUser, env = process.env) {
|
|
58946
|
+
const specs = listImapAccountSpecs(env);
|
|
58947
|
+
const why = "Mail.app's AppleScript cannot set In-Reply-To/References on a draft, so a threaded draft is filed over IMAP into the account's Drafts mailbox";
|
|
58948
|
+
if (specs.length === 0) {
|
|
58949
|
+
throw new Error(
|
|
58950
|
+
`${why}, and IMAP is not configured. Configure IMAP, omit inReplyTo/references, or use reply-to-message with send=false. ${SETUP_HINT}`
|
|
58951
|
+
);
|
|
58952
|
+
}
|
|
58953
|
+
let spec;
|
|
58954
|
+
if (account) {
|
|
58955
|
+
spec = specs.find((s) => specMatchesSelector(s, account));
|
|
58956
|
+
if (!spec) {
|
|
58957
|
+
throw new Error(
|
|
58958
|
+
`${why}, and "${account}" is not a configured IMAP account. Configured: ${specs.map((s) => s.accountLabel).join(", ")}.`
|
|
58959
|
+
);
|
|
58960
|
+
}
|
|
58961
|
+
} else {
|
|
58962
|
+
spec = (smtpUser ? specs.find((s) => specMatchesSelector(s, smtpUser)) : void 0) ?? (specs.length === 1 ? specs[0] : void 0);
|
|
58963
|
+
if (!spec) {
|
|
58964
|
+
throw new Error(
|
|
58965
|
+
`${why}; several IMAP accounts are configured, so pass \`account\` to choose one: ${specs.map((s) => s.accountLabel).join(", ")}.`
|
|
58966
|
+
);
|
|
58967
|
+
}
|
|
58968
|
+
}
|
|
58969
|
+
return { label: spec.accountLabel, user: spec.user };
|
|
58970
|
+
}
|
|
58971
|
+
async function imapAppendDraft(account, raw, deps = {}) {
|
|
58972
|
+
return withClient({ ...deps, account: deps.account ?? account }, async (client, cfg) => {
|
|
58973
|
+
const path3 = await resolveMailboxPath(client, "drafts", "list");
|
|
58974
|
+
const res = await client.append(path3, raw, ["\\Draft", "\\Seen"]);
|
|
58975
|
+
if (!res) throw new Error(`server rejected the APPEND to "${path3}" (IMAP NO/BAD)`);
|
|
58976
|
+
return { mailbox: path3, account: cfg.accountLabel };
|
|
58977
|
+
});
|
|
58978
|
+
}
|
|
58943
58979
|
async function withMailbox(path3, deps, fn) {
|
|
58944
58980
|
return withClient(deps, async (client) => {
|
|
58945
58981
|
const lock = await client.getMailboxLock(path3);
|
|
@@ -59795,6 +59831,27 @@ function buildAttachments(attachments) {
|
|
|
59795
59831
|
return { filename: a.filename, content: decodeInlineAttachment(a.contentBase64) };
|
|
59796
59832
|
});
|
|
59797
59833
|
}
|
|
59834
|
+
function buildMailOptions(opts, from, attachments) {
|
|
59835
|
+
const html = opts.htmlBody?.trim() ? opts.htmlBody : void 0;
|
|
59836
|
+
return {
|
|
59837
|
+
from,
|
|
59838
|
+
to: opts.to,
|
|
59839
|
+
cc: opts.cc,
|
|
59840
|
+
bcc: opts.bcc,
|
|
59841
|
+
subject: opts.subject,
|
|
59842
|
+
text: opts.body,
|
|
59843
|
+
// When present, nodemailer emits multipart/alternative (text + html).
|
|
59844
|
+
html,
|
|
59845
|
+
attachments,
|
|
59846
|
+
// RFC 5322 threading for SMTP replies/forwards (2.5.0). nodemailer treats
|
|
59847
|
+
// In-Reply-To / References as structured msg-id headers and never
|
|
59848
|
+
// RFC 2047-encodes them, however long the id (pinned by a test, #267).
|
|
59849
|
+
inReplyTo: opts.inReplyTo?.trim() || void 0,
|
|
59850
|
+
references: opts.references?.length ? opts.references : void 0,
|
|
59851
|
+
// Reply-To header (2.18.0, issue #220): nodemailer already supports this.
|
|
59852
|
+
replyTo: opts.replyTo?.trim() || void 0
|
|
59853
|
+
};
|
|
59854
|
+
}
|
|
59798
59855
|
async function sendViaSmtp(opts, config, createTransport2 = nodemailer_default.createTransport, appendSentCopy = defaultAppendSentCopy) {
|
|
59799
59856
|
let cfg;
|
|
59800
59857
|
try {
|
|
@@ -59833,23 +59890,7 @@ async function sendViaSmtp(opts, config, createTransport2 = nodemailer_default.c
|
|
|
59833
59890
|
requireTLS,
|
|
59834
59891
|
auth: { user: cfg.user, pass: cfg.pass }
|
|
59835
59892
|
});
|
|
59836
|
-
const
|
|
59837
|
-
const mailOptions = {
|
|
59838
|
-
from: requestedFrom || cfg.from,
|
|
59839
|
-
to: opts.to,
|
|
59840
|
-
cc: opts.cc,
|
|
59841
|
-
bcc: opts.bcc,
|
|
59842
|
-
subject: opts.subject,
|
|
59843
|
-
text: opts.body,
|
|
59844
|
-
// When present, nodemailer emits multipart/alternative (text + html).
|
|
59845
|
-
html,
|
|
59846
|
-
attachments,
|
|
59847
|
-
// RFC 5322 threading for SMTP replies/forwards (2.5.0).
|
|
59848
|
-
inReplyTo: opts.inReplyTo?.trim() || void 0,
|
|
59849
|
-
references: opts.references?.length ? opts.references : void 0,
|
|
59850
|
-
// Reply-To header (2.18.0, issue #220): nodemailer already supports this.
|
|
59851
|
-
replyTo: opts.replyTo?.trim() || void 0
|
|
59852
|
-
};
|
|
59893
|
+
const mailOptions = buildMailOptions(opts, requestedFrom || cfg.from, attachments);
|
|
59853
59894
|
try {
|
|
59854
59895
|
const info = await transporter.sendMail(mailOptions);
|
|
59855
59896
|
let copyFields = {};
|
package/build/index.js
CHANGED
|
@@ -65187,6 +65187,7 @@ __export(imapClient_exports, {
|
|
|
65187
65187
|
decodeImapId: () => decodeImapId,
|
|
65188
65188
|
dropAllPools: () => dropAllPools,
|
|
65189
65189
|
encodeImapId: () => encodeImapId,
|
|
65190
|
+
imapAppendDraft: () => imapAppendDraft,
|
|
65190
65191
|
imapAppendSentCopy: () => imapAppendSentCopy,
|
|
65191
65192
|
imapBatchDelete: () => imapBatchDelete,
|
|
65192
65193
|
imapBatchFlag: () => imapBatchFlag,
|
|
@@ -65224,6 +65225,7 @@ __export(imapClient_exports, {
|
|
|
65224
65225
|
matchMailbox: () => matchMailbox,
|
|
65225
65226
|
normalizeMessageId: () => normalizeMessageId,
|
|
65226
65227
|
omittedNote: () => omittedNote,
|
|
65228
|
+
resolveDraftImapAccount: () => resolveDraftImapAccount,
|
|
65227
65229
|
resolveImapConfig: () => resolveImapConfig,
|
|
65228
65230
|
resolveImapConfigs: () => resolveImapConfigs,
|
|
65229
65231
|
resolveMailboxPath: () => resolveMailboxPath,
|
|
@@ -66281,6 +66283,40 @@ async function imapAppendSentCopy(smtpUser, raw, deps = {}) {
|
|
|
66281
66283
|
return { attempted: true, success: false, error: errText(e) };
|
|
66282
66284
|
}
|
|
66283
66285
|
}
|
|
66286
|
+
function resolveDraftImapAccount(account, smtpUser, env = process.env) {
|
|
66287
|
+
const specs = listImapAccountSpecs(env);
|
|
66288
|
+
const why = "Mail.app's AppleScript cannot set In-Reply-To/References on a draft, so a threaded draft is filed over IMAP into the account's Drafts mailbox";
|
|
66289
|
+
if (specs.length === 0) {
|
|
66290
|
+
throw new Error(
|
|
66291
|
+
`${why}, and IMAP is not configured. Configure IMAP, omit inReplyTo/references, or use reply-to-message with send=false. ${SETUP_HINT}`
|
|
66292
|
+
);
|
|
66293
|
+
}
|
|
66294
|
+
let spec;
|
|
66295
|
+
if (account) {
|
|
66296
|
+
spec = specs.find((s) => specMatchesSelector(s, account));
|
|
66297
|
+
if (!spec) {
|
|
66298
|
+
throw new Error(
|
|
66299
|
+
`${why}, and "${account}" is not a configured IMAP account. Configured: ${specs.map((s) => s.accountLabel).join(", ")}.`
|
|
66300
|
+
);
|
|
66301
|
+
}
|
|
66302
|
+
} else {
|
|
66303
|
+
spec = (smtpUser ? specs.find((s) => specMatchesSelector(s, smtpUser)) : void 0) ?? (specs.length === 1 ? specs[0] : void 0);
|
|
66304
|
+
if (!spec) {
|
|
66305
|
+
throw new Error(
|
|
66306
|
+
`${why}; several IMAP accounts are configured, so pass \`account\` to choose one: ${specs.map((s) => s.accountLabel).join(", ")}.`
|
|
66307
|
+
);
|
|
66308
|
+
}
|
|
66309
|
+
}
|
|
66310
|
+
return { label: spec.accountLabel, user: spec.user };
|
|
66311
|
+
}
|
|
66312
|
+
async function imapAppendDraft(account, raw, deps = {}) {
|
|
66313
|
+
return withClient({ ...deps, account: deps.account ?? account }, async (client, cfg) => {
|
|
66314
|
+
const path3 = await resolveMailboxPath(client, "drafts", "list");
|
|
66315
|
+
const res = await client.append(path3, raw, ["\\Draft", "\\Seen"]);
|
|
66316
|
+
if (!res) throw new Error(`server rejected the APPEND to "${path3}" (IMAP NO/BAD)`);
|
|
66317
|
+
return { mailbox: path3, account: cfg.accountLabel };
|
|
66318
|
+
});
|
|
66319
|
+
}
|
|
66284
66320
|
async function withMailbox(path3, deps, fn) {
|
|
66285
66321
|
return withClient(deps, async (client) => {
|
|
66286
66322
|
const lock = await client.getMailboxLock(path3);
|
|
@@ -67146,6 +67182,34 @@ function buildAttachments(attachments) {
|
|
|
67146
67182
|
return { filename: a.filename, content: decodeInlineAttachment(a.contentBase64) };
|
|
67147
67183
|
});
|
|
67148
67184
|
}
|
|
67185
|
+
function buildMailOptions(opts, from, attachments) {
|
|
67186
|
+
const html = opts.htmlBody?.trim() ? opts.htmlBody : void 0;
|
|
67187
|
+
return {
|
|
67188
|
+
from,
|
|
67189
|
+
to: opts.to,
|
|
67190
|
+
cc: opts.cc,
|
|
67191
|
+
bcc: opts.bcc,
|
|
67192
|
+
subject: opts.subject,
|
|
67193
|
+
text: opts.body,
|
|
67194
|
+
// When present, nodemailer emits multipart/alternative (text + html).
|
|
67195
|
+
html,
|
|
67196
|
+
attachments,
|
|
67197
|
+
// RFC 5322 threading for SMTP replies/forwards (2.5.0). nodemailer treats
|
|
67198
|
+
// In-Reply-To / References as structured msg-id headers and never
|
|
67199
|
+
// RFC 2047-encodes them, however long the id (pinned by a test, #267).
|
|
67200
|
+
inReplyTo: opts.inReplyTo?.trim() || void 0,
|
|
67201
|
+
references: opts.references?.length ? opts.references : void 0,
|
|
67202
|
+
// Reply-To header (2.18.0, issue #220): nodemailer already supports this.
|
|
67203
|
+
replyTo: opts.replyTo?.trim() || void 0
|
|
67204
|
+
};
|
|
67205
|
+
}
|
|
67206
|
+
function composeRawMime(opts, from) {
|
|
67207
|
+
return buildRawMime({
|
|
67208
|
+
...buildMailOptions(opts, from, buildAttachments(opts.attachments)),
|
|
67209
|
+
keepBcc: true,
|
|
67210
|
+
date: /* @__PURE__ */ new Date()
|
|
67211
|
+
});
|
|
67212
|
+
}
|
|
67149
67213
|
async function sendViaSmtp(opts, config2, createTransport2 = nodemailer_default.createTransport, appendSentCopy = defaultAppendSentCopy) {
|
|
67150
67214
|
let cfg;
|
|
67151
67215
|
try {
|
|
@@ -67184,23 +67248,7 @@ async function sendViaSmtp(opts, config2, createTransport2 = nodemailer_default.
|
|
|
67184
67248
|
requireTLS,
|
|
67185
67249
|
auth: { user: cfg.user, pass: cfg.pass }
|
|
67186
67250
|
});
|
|
67187
|
-
const
|
|
67188
|
-
const mailOptions = {
|
|
67189
|
-
from: requestedFrom || cfg.from,
|
|
67190
|
-
to: opts.to,
|
|
67191
|
-
cc: opts.cc,
|
|
67192
|
-
bcc: opts.bcc,
|
|
67193
|
-
subject: opts.subject,
|
|
67194
|
-
text: opts.body,
|
|
67195
|
-
// When present, nodemailer emits multipart/alternative (text + html).
|
|
67196
|
-
html,
|
|
67197
|
-
attachments,
|
|
67198
|
-
// RFC 5322 threading for SMTP replies/forwards (2.5.0).
|
|
67199
|
-
inReplyTo: opts.inReplyTo?.trim() || void 0,
|
|
67200
|
-
references: opts.references?.length ? opts.references : void 0,
|
|
67201
|
-
// Reply-To header (2.18.0, issue #220): nodemailer already supports this.
|
|
67202
|
-
replyTo: opts.replyTo?.trim() || void 0
|
|
67203
|
-
};
|
|
67251
|
+
const mailOptions = buildMailOptions(opts, requestedFrom || cfg.from, attachments);
|
|
67204
67252
|
try {
|
|
67205
67253
|
const info = await transporter.sendMail(mailOptions);
|
|
67206
67254
|
let copyFields = {};
|
|
@@ -82144,7 +82192,7 @@ function materializeAttachments(attachments) {
|
|
|
82144
82192
|
if (!dir) dir = mkdtempSync(join3(tmpdir2(), "amcp-att-"));
|
|
82145
82193
|
const safeName = a.filename.replace(/[/\\]/g, "_");
|
|
82146
82194
|
const p = join3(dir, safeName);
|
|
82147
|
-
writeFileSync2(p, decodeInlineAttachment(a.contentBase64));
|
|
82195
|
+
writeFileSync2(p, decodeInlineAttachment(a.contentBase64), { mode: 384 });
|
|
82148
82196
|
return p;
|
|
82149
82197
|
});
|
|
82150
82198
|
} catch (error3) {
|
|
@@ -82158,6 +82206,18 @@ function materializeAttachments(attachments) {
|
|
|
82158
82206
|
}
|
|
82159
82207
|
};
|
|
82160
82208
|
}
|
|
82209
|
+
function preflightAttachments(attachments) {
|
|
82210
|
+
for (const a of attachments ?? []) {
|
|
82211
|
+
if (typeof a === "string") {
|
|
82212
|
+
resolveAttachmentReadPath(a);
|
|
82213
|
+
continue;
|
|
82214
|
+
}
|
|
82215
|
+
if (!a.filename || !a.contentBase64) {
|
|
82216
|
+
throw new Error("Inline attachment requires both filename and contentBase64.");
|
|
82217
|
+
}
|
|
82218
|
+
decodeInlineAttachment(a.contentBase64);
|
|
82219
|
+
}
|
|
82220
|
+
}
|
|
82161
82221
|
|
|
82162
82222
|
// src/services/appleMailManager.ts
|
|
82163
82223
|
init_attachmentReadPolicy();
|
|
@@ -82421,6 +82481,11 @@ function buildAttachmentCommands(attachments) {
|
|
|
82421
82481
|
}
|
|
82422
82482
|
return commands;
|
|
82423
82483
|
}
|
|
82484
|
+
function composeAttachmentBlock(msgVar, paths) {
|
|
82485
|
+
const commands = buildAttachmentCommands(paths);
|
|
82486
|
+
return commands ? `tell ${msgVar}
|
|
82487
|
+
${commands}end tell` : "";
|
|
82488
|
+
}
|
|
82424
82489
|
function asDateToString(varName) {
|
|
82425
82490
|
return `((year of ${varName}) as string) & "-" & ((month of ${varName} as integer) as string) & "-" & ((day of ${varName}) as string) & "-" & ((hours of ${varName}) as string) & "-" & ((minutes of ${varName}) as string) & "-" & ((seconds of ${varName}) as string)`;
|
|
82426
82491
|
}
|
|
@@ -84670,20 +84735,28 @@ ${indent}end try${this.sanitizeFragment("_uacct", indent)}${this.sanitizeFragmen
|
|
|
84670
84735
|
* @param body - Reply body
|
|
84671
84736
|
* @param replyAll - If true, reply to all recipients
|
|
84672
84737
|
* @param send - If true, send immediately; if false, save as draft
|
|
84738
|
+
* @param attachments - Files to attach (#267): allowlisted paths and/or inline
|
|
84739
|
+
* base64 items (materialized to 0600 temp files, removed afterwards)
|
|
84673
84740
|
* @returns true if reply created/sent successfully
|
|
84674
84741
|
*/
|
|
84675
|
-
replyToMessage(id, body, replyAll = false, send = true) {
|
|
84742
|
+
replyToMessage(id, body, replyAll = false, send = true, attachments) {
|
|
84676
84743
|
const safeBody = escapeForAppleScriptBody(body);
|
|
84677
84744
|
const replyAllClause = replyAll ? " with reply to all" : "";
|
|
84678
84745
|
const finalAction = send ? "send theReply" : "save theReply\n close theReply saving yes";
|
|
84679
|
-
const
|
|
84680
|
-
|
|
84681
|
-
|
|
84746
|
+
const mat = materializeAttachments(attachments);
|
|
84747
|
+
try {
|
|
84748
|
+
const script = this.findMessageScript(
|
|
84749
|
+
id,
|
|
84750
|
+
`
|
|
84682
84751
|
set theReply to reply msg without opening window${replyAllClause}
|
|
84683
84752
|
set content of theReply to "${safeBody}"
|
|
84753
|
+
${composeAttachmentBlock("theReply", mat.paths)}
|
|
84684
84754
|
${finalAction}`
|
|
84685
|
-
|
|
84686
|
-
|
|
84755
|
+
);
|
|
84756
|
+
return this.runComposeScript(script, "reply to");
|
|
84757
|
+
} finally {
|
|
84758
|
+
mat.cleanup();
|
|
84759
|
+
}
|
|
84687
84760
|
}
|
|
84688
84761
|
/**
|
|
84689
84762
|
* Forward a message.
|
|
@@ -84692,9 +84765,10 @@ ${indent}end try${this.sanitizeFragment("_uacct", indent)}${this.sanitizeFragmen
|
|
|
84692
84765
|
* @param to - Recipients to forward to
|
|
84693
84766
|
* @param body - Optional body to prepend
|
|
84694
84767
|
* @param send - If true, send immediately; if false, save as draft
|
|
84768
|
+
* @param attachments - Additional files to attach (#267), as for replyToMessage
|
|
84695
84769
|
* @returns true if forward created/sent successfully
|
|
84696
84770
|
*/
|
|
84697
|
-
forwardMessage(id, to, body, send = true) {
|
|
84771
|
+
forwardMessage(id, to, body, send = true, attachments) {
|
|
84698
84772
|
const safeBody = body ? escapeForAppleScriptBody(body) : "";
|
|
84699
84773
|
const finalAction = send ? "send theForward" : "save theForward\n close theForward saving yes";
|
|
84700
84774
|
let recipientCommands = "";
|
|
@@ -84702,15 +84776,21 @@ ${indent}end try${this.sanitizeFragment("_uacct", indent)}${this.sanitizeFragmen
|
|
|
84702
84776
|
recipientCommands += `make new to recipient at end of to recipients of theForward with properties {address:"${escapeForAppleScript(addr)}"}
|
|
84703
84777
|
`;
|
|
84704
84778
|
}
|
|
84705
|
-
const
|
|
84706
|
-
|
|
84707
|
-
|
|
84779
|
+
const mat = materializeAttachments(attachments);
|
|
84780
|
+
try {
|
|
84781
|
+
const script = this.findMessageScript(
|
|
84782
|
+
id,
|
|
84783
|
+
`
|
|
84708
84784
|
set theForward to forward msg without opening window
|
|
84709
84785
|
${recipientCommands}
|
|
84710
84786
|
${safeBody ? `set content of theForward to "${safeBody}"` : ""}
|
|
84787
|
+
${composeAttachmentBlock("theForward", mat.paths)}
|
|
84711
84788
|
${finalAction}`
|
|
84712
|
-
|
|
84713
|
-
|
|
84789
|
+
);
|
|
84790
|
+
return this.runComposeScript(script, "forward");
|
|
84791
|
+
} finally {
|
|
84792
|
+
mat.cleanup();
|
|
84793
|
+
}
|
|
84714
84794
|
}
|
|
84715
84795
|
/**
|
|
84716
84796
|
* Helper to find and operate on a message by ID, scoped to the mailbox the id
|
|
@@ -84745,7 +84825,7 @@ ${indent}end try${this.sanitizeFragment("_uacct", indent)}${this.sanitizeFragmen
|
|
|
84745
84825
|
* behind one indistinguishable message.
|
|
84746
84826
|
*/
|
|
84747
84827
|
runComposeScript(script, verb) {
|
|
84748
|
-
const result = executeAppleScript(script, { timeoutMs: 6e4 });
|
|
84828
|
+
const result = executeAppleScript(script, { timeoutMs: 6e4, maxRetries: 1 });
|
|
84749
84829
|
if (!result.success || result.output.startsWith("error:")) {
|
|
84750
84830
|
const raw = result.error || result.output;
|
|
84751
84831
|
const error3 = raw.startsWith("error:") ? raw.slice("error:".length) : raw;
|
|
@@ -84935,13 +85015,13 @@ ${indent}end try${this.sanitizeFragment("_uacct", indent)}${this.sanitizeFragmen
|
|
|
84935
85015
|
try
|
|
84936
85016
|
set inMb to mailbox "INBOX" of acct
|
|
84937
85017
|
set inHits to ${matchClause("inMb")}
|
|
84938
|
-
if (count of inHits) > 0 then return (id of (item 1 of inHits)) as string
|
|
85018
|
+
if (count of inHits) > 0 then return ((id of (item 1 of inHits)) as string) & tab & (name of inMb) & tab & (name of acct)
|
|
84939
85019
|
end try
|
|
84940
85020
|
repeat with mb in mailboxes of acct
|
|
84941
85021
|
try
|
|
84942
85022
|
set matchingMsgs to ${matchClause("mb")}
|
|
84943
85023
|
if (count of matchingMsgs) > 0 then
|
|
84944
|
-
return (id of (item 1 of matchingMsgs)) as string
|
|
85024
|
+
return ((id of (item 1 of matchingMsgs)) as string) & tab & (name of mb) & tab & (name of acct)
|
|
84945
85025
|
end if
|
|
84946
85026
|
end try
|
|
84947
85027
|
end repeat
|
|
@@ -84953,8 +85033,10 @@ ${indent}end try${this.sanitizeFragment("_uacct", indent)}${this.sanitizeFragmen
|
|
|
84953
85033
|
`);
|
|
84954
85034
|
const result = executeAppleScript(script, { timeoutMs: 6e4 });
|
|
84955
85035
|
if (!result.success || result.output.startsWith("error:")) return null;
|
|
84956
|
-
const
|
|
84957
|
-
|
|
85036
|
+
const [numericId, mailbox, account] = result.output.trim().split(" ");
|
|
85037
|
+
if (!/^\d+$/.test(numericId ?? "")) return null;
|
|
85038
|
+
if (mailbox && account) this.rememberLocation(numericId, account, mailbox);
|
|
85039
|
+
return numericId;
|
|
84958
85040
|
}
|
|
84959
85041
|
/**
|
|
84960
85042
|
* Unflag a message.
|
|
@@ -87195,19 +87277,28 @@ async function replyComposeBody(deps, id, body) {
|
|
|
87195
87277
|
const source = await readOriginalForQuote(deps, id);
|
|
87196
87278
|
return source ? buildReplyBody(body, source.original, source.plainText) : body;
|
|
87197
87279
|
}
|
|
87198
|
-
async function forwardComposeBody(deps, id, body) {
|
|
87199
|
-
if (!body) return body;
|
|
87280
|
+
async function forwardComposeBody(deps, id, body, hasAttachments = false) {
|
|
87281
|
+
if (!body && !hasAttachments) return body;
|
|
87200
87282
|
const source = await readOriginalForQuote(deps, id);
|
|
87201
87283
|
return source ? buildForwardBody(source.original, source.plainText, body) : body;
|
|
87202
87284
|
}
|
|
87203
87285
|
async function runCompose(deps, args) {
|
|
87204
|
-
const { id, send, transport: transport2 } = args;
|
|
87286
|
+
const { id, send, transport: transport2, attachments } = args;
|
|
87205
87287
|
const verb = args.kind === "reply" ? "reply to" : "forward";
|
|
87206
87288
|
if (!send && transport2 === "smtp") {
|
|
87207
87289
|
return errorResponse(
|
|
87208
87290
|
"SMTP cannot save a Mail.app draft. Omit transport or use transport=applescript with send=false."
|
|
87209
87291
|
);
|
|
87210
87292
|
}
|
|
87293
|
+
try {
|
|
87294
|
+
preflightAttachments(attachments);
|
|
87295
|
+
} catch (error3) {
|
|
87296
|
+
return errorResponse(
|
|
87297
|
+
`Failed to ${verb} message "${id}": ${error3 instanceof Error ? error3.message : String(error3)}`
|
|
87298
|
+
);
|
|
87299
|
+
}
|
|
87300
|
+
const attachmentCount = attachments?.length ?? 0;
|
|
87301
|
+
const withFiles = attachmentCount ? ` with ${attachmentCount} attachment(s)` : "";
|
|
87211
87302
|
const smtp = send && transport2 !== "applescript" && (transport2 === "smtp" || deps.smtpConfigured());
|
|
87212
87303
|
if (smtp) {
|
|
87213
87304
|
try {
|
|
@@ -87239,19 +87330,21 @@ async function runCompose(deps, args) {
|
|
|
87239
87330
|
body: args.body,
|
|
87240
87331
|
from: cfg.from
|
|
87241
87332
|
});
|
|
87333
|
+
if (attachmentCount) opts.attachments = attachments;
|
|
87242
87334
|
const result = await deps.smtpSend(opts, cfg);
|
|
87243
87335
|
if (!result.success)
|
|
87244
87336
|
return errorResponse(
|
|
87245
87337
|
`Failed to ${verb} message "${id}" via SMTP: ${result.error ?? "unknown SMTP error"}`
|
|
87246
87338
|
);
|
|
87247
87339
|
return successResponse(
|
|
87248
|
-
args.kind === "reply" ?
|
|
87340
|
+
args.kind === "reply" ? `Reply sent via SMTP${withFiles}` : `Message forwarded via SMTP to ${args.to.join(", ")}${withFiles}`,
|
|
87249
87341
|
{
|
|
87250
87342
|
ok: true,
|
|
87251
87343
|
sent: true,
|
|
87252
87344
|
id,
|
|
87253
87345
|
transport: "smtp",
|
|
87254
87346
|
messageId: result.messageId,
|
|
87347
|
+
attachmentCount,
|
|
87255
87348
|
...args.kind === "forward" ? { recipients: args.to } : {},
|
|
87256
87349
|
// Best-effort Sent-folder copy (issue #220) — same field shape as
|
|
87257
87350
|
// send-email, since it's the same sendViaSmtp underneath.
|
|
@@ -87274,23 +87367,26 @@ async function runCompose(deps, args) {
|
|
|
87274
87367
|
resolved.numericId,
|
|
87275
87368
|
await replyComposeBody(deps, id, args.body),
|
|
87276
87369
|
args.replyAll,
|
|
87277
|
-
send
|
|
87370
|
+
send,
|
|
87371
|
+
attachmentCount ? attachments : void 0
|
|
87278
87372
|
) : deps.mail.forwardMessage(
|
|
87279
87373
|
resolved.numericId,
|
|
87280
87374
|
args.to,
|
|
87281
|
-
await forwardComposeBody(deps, id, args.body),
|
|
87282
|
-
send
|
|
87375
|
+
await forwardComposeBody(deps, id, args.body, attachmentCount > 0),
|
|
87376
|
+
send,
|
|
87377
|
+
attachmentCount ? attachments : void 0
|
|
87283
87378
|
);
|
|
87284
87379
|
if (!outcome.success)
|
|
87285
87380
|
return errorResponse(
|
|
87286
87381
|
`Failed to ${verb} message "${id}": ${outcome.error ?? "Mail.app compose failed"}`
|
|
87287
87382
|
);
|
|
87288
|
-
const text = args.kind === "reply" ? send ? "Reply sent via AppleScript" : "Reply saved as draft" : send ? `Message forwarded to ${args.to.join(", ")}` : "Forward saved as draft";
|
|
87383
|
+
const text = (args.kind === "reply" ? send ? "Reply sent via AppleScript" : "Reply saved as draft" : send ? `Message forwarded to ${args.to.join(", ")}` : "Forward saved as draft") + withFiles;
|
|
87289
87384
|
return successResponse(text, {
|
|
87290
87385
|
ok: true,
|
|
87291
87386
|
sent: send,
|
|
87292
87387
|
id,
|
|
87293
87388
|
transport: "applescript",
|
|
87389
|
+
attachmentCount,
|
|
87294
87390
|
...args.kind === "forward" ? { recipients: args.to } : {}
|
|
87295
87391
|
});
|
|
87296
87392
|
}
|
|
@@ -87301,6 +87397,70 @@ function runForward(deps, args) {
|
|
|
87301
87397
|
return runCompose(deps, { ...args, kind: "forward" });
|
|
87302
87398
|
}
|
|
87303
87399
|
|
|
87400
|
+
// src/tools/threading.ts
|
|
87401
|
+
function threadingHeaders(args) {
|
|
87402
|
+
const inReplyTo = args.inReplyTo?.trim() || void 0;
|
|
87403
|
+
const references = args.references?.length ? args.references : void 0;
|
|
87404
|
+
if (!inReplyTo && !references) return void 0;
|
|
87405
|
+
return { inReplyTo, references: references ?? (inReplyTo ? [inReplyTo] : void 0) };
|
|
87406
|
+
}
|
|
87407
|
+
var SEND_THREADING_NEEDS_SMTP = "inReplyTo/references require the SMTP transport: Mail.app's AppleScript cannot set In-Reply-To or References, so the message would go out unthreaded. Configure SMTP (and do not pass transport=applescript or a Mail.app account label), or use reply-to-message, which threads on both transports.";
|
|
87408
|
+
function sendEmailThreading(args, useSmtp) {
|
|
87409
|
+
const headers = threadingHeaders(args);
|
|
87410
|
+
if (headers && !useSmtp) return { ok: false, error: SEND_THREADING_NEEDS_SMTP };
|
|
87411
|
+
return { ok: true, headers };
|
|
87412
|
+
}
|
|
87413
|
+
function messageIdOf(raw) {
|
|
87414
|
+
const head = raw.toString("utf8").split(/\r?\n\r?\n/)[0] ?? "";
|
|
87415
|
+
return head.replace(/\r?\n[ \t]+/g, " ").match(/^Message-ID:\s*(<[^>\s]+>)/im)?.[1];
|
|
87416
|
+
}
|
|
87417
|
+
async function runThreadedDraft(deps, args) {
|
|
87418
|
+
const threading = threadingHeaders(args);
|
|
87419
|
+
if (!threading) throw new Error("runThreadedDraft called without threading headers");
|
|
87420
|
+
const fail = (e) => errorResponse(
|
|
87421
|
+
`Failed to create threaded draft: ${e instanceof Error ? e.message : String(e)} Nothing was filed; no Mail.app fallback was attempted (it would drop the threading headers).`
|
|
87422
|
+
);
|
|
87423
|
+
try {
|
|
87424
|
+
preflightAttachments(args.attachments);
|
|
87425
|
+
const smtp = deps.smtpIdentity();
|
|
87426
|
+
const acct = deps.resolveAccount(args.account, smtp.user);
|
|
87427
|
+
const from = smtp.user && smtp.user.toLowerCase() === acct.user.toLowerCase() && smtp.from ? smtp.from : acct.user;
|
|
87428
|
+
const raw = await deps.compose(
|
|
87429
|
+
{
|
|
87430
|
+
to: args.to,
|
|
87431
|
+
cc: args.cc,
|
|
87432
|
+
bcc: args.bcc,
|
|
87433
|
+
subject: args.subject,
|
|
87434
|
+
body: args.body,
|
|
87435
|
+
attachments: args.attachments,
|
|
87436
|
+
inReplyTo: threading.inReplyTo,
|
|
87437
|
+
references: threading.references
|
|
87438
|
+
},
|
|
87439
|
+
from
|
|
87440
|
+
);
|
|
87441
|
+
const filed = await deps.append(acct.label, raw);
|
|
87442
|
+
const attachmentCount = args.attachments?.length ?? 0;
|
|
87443
|
+
const attachInfo = attachmentCount ? ` with ${attachmentCount} attachment(s)` : "";
|
|
87444
|
+
const messageId = messageIdOf(raw);
|
|
87445
|
+
return successResponse(
|
|
87446
|
+
`Threaded draft for ${args.to.join(", ")}${attachInfo} filed over IMAP in "${filed.mailbox}" (account ${filed.account}). Review and send it from Mail.app.`,
|
|
87447
|
+
{
|
|
87448
|
+
ok: true,
|
|
87449
|
+
recipients: args.to,
|
|
87450
|
+
attachmentCount,
|
|
87451
|
+
transport: "imap",
|
|
87452
|
+
account: filed.account,
|
|
87453
|
+
mailbox: filed.mailbox,
|
|
87454
|
+
...messageId ? { messageId } : {},
|
|
87455
|
+
...threading.inReplyTo ? { inReplyTo: threading.inReplyTo } : {},
|
|
87456
|
+
references: threading.references
|
|
87457
|
+
}
|
|
87458
|
+
);
|
|
87459
|
+
} catch (e) {
|
|
87460
|
+
return fail(e);
|
|
87461
|
+
}
|
|
87462
|
+
}
|
|
87463
|
+
|
|
87304
87464
|
// src/index.ts
|
|
87305
87465
|
init_imapClient();
|
|
87306
87466
|
|
|
@@ -87812,6 +87972,17 @@ var ATTACHMENTS_SCHEMA = external_exports.array(
|
|
|
87812
87972
|
).max(20, "Cannot attach more than 20 files").optional().describe(
|
|
87813
87973
|
"Files to attach: absolute paths in the configured attachment read roots (e.g. '/Users/me/Documents/report.pdf') and/or inline {filename, contentBase64} objects up to 25 MiB decoded each."
|
|
87814
87974
|
);
|
|
87975
|
+
var RFC_MESSAGE_ID_TOKEN = /^<[^<>\s@]+@[^<>\s@]+>$/;
|
|
87976
|
+
var MESSAGE_ID_TOKEN_SCHEMA = external_exports.string().max(998, "Message-ID is longer than an RFC 5322 header line (998 chars)").regex(
|
|
87977
|
+
RFC_MESSAGE_ID_TOKEN,
|
|
87978
|
+
"Must be an RFC 5322 Message-ID token including angle brackets, e.g. <abc123@mail.example.com>"
|
|
87979
|
+
);
|
|
87980
|
+
var IN_REPLY_TO_SCHEMA = MESSAGE_ID_TOKEN_SCHEMA.optional().describe(
|
|
87981
|
+
"Thread this message as a reply: the parent's Message-ID token, angle brackets included (e.g. '<abc123@mail.example.com>'). Emitted verbatim as In-Reply-To, never RFC 2047-encoded. When `references` is omitted, References defaults to [inReplyTo]. Prefer reply-to-message, which derives recipients, threading and the quote from the original for you."
|
|
87982
|
+
);
|
|
87983
|
+
var REFERENCES_SCHEMA = external_exports.array(MESSAGE_ID_TOKEN_SCHEMA).min(1, "references must contain at least one Message-ID").max(100, "Cannot list more than 100 Message-IDs in references").optional().describe(
|
|
87984
|
+
"Thread chain, oldest first: the parent's own References followed by the parent's Message-ID (each an angle-bracketed token). Emitted verbatim, never RFC 2047-encoded."
|
|
87985
|
+
);
|
|
87815
87986
|
|
|
87816
87987
|
// src/tools/thread.ts
|
|
87817
87988
|
function normalizeSubject(subject) {
|
|
@@ -88896,7 +89067,7 @@ var SENT_COPY_ERROR_SCHEMA = external_exports.string().optional().describe("Pres
|
|
|
88896
89067
|
registerTool(
|
|
88897
89068
|
"send-email",
|
|
88898
89069
|
{
|
|
88899
|
-
description: "Use when: the user has explicitly confirmed they want to send a single email now to the given recipients (to/cc/bcc are arrays), optionally with attachments
|
|
89070
|
+
description: "Use when: the user has explicitly confirmed they want to send a single email now to the given recipients (to/cc/bcc are arrays), optionally with attachments, a chosen transport, and caller-built threading headers (inReplyTo/references, SMTP only).\nReturns: a confirmation naming the recipients and attachment count; over SMTP, also whether a Sent-folder copy was filed (sentCopy).\nDo not use when: the user wants to review first (use create-draft), is replying to or forwarding an existing message (use reply-to-message / forward-message \u2014 they derive recipients, threading and the quote, and also take attachments), or wants per-recipient personalized copies (use send-serial-email).\nSafety: this SENDS real email immediately and it cannot be unsent \u2014 require explicit user confirmation of the exact recipients, subject, and body before calling. Prefer create-draft when there is any doubt.",
|
|
88900
89071
|
inputSchema: {
|
|
88901
89072
|
to: external_exports.array(external_exports.string()).min(1, "At least one recipient is required"),
|
|
88902
89073
|
subject: external_exports.string().min(1, "Subject is required"),
|
|
@@ -88905,6 +89076,8 @@ registerTool(
|
|
|
88905
89076
|
bcc: external_exports.array(external_exports.string()).optional().describe("BCC recipients"),
|
|
88906
89077
|
account: external_exports.string().optional().describe("Account to send from"),
|
|
88907
89078
|
attachments: ATTACHMENTS_SCHEMA,
|
|
89079
|
+
inReplyTo: IN_REPLY_TO_SCHEMA,
|
|
89080
|
+
references: REFERENCES_SCHEMA,
|
|
88908
89081
|
replyTo: external_exports.string().optional().describe(
|
|
88909
89082
|
"SMTP only (issue #220): sets the Reply-To header when replies should go somewhere other than the From/login address, e.g. a domain-alias setup where APPLE_MAIL_MCP_SMTP_FROM differs from APPLE_MAIL_MCP_SMTP_USER. Ignored on the AppleScript transport."
|
|
88910
89083
|
),
|
|
@@ -88917,15 +89090,31 @@ registerTool(
|
|
|
88917
89090
|
recipients: external_exports.array(external_exports.string()).optional(),
|
|
88918
89091
|
attachmentCount: external_exports.number().optional(),
|
|
88919
89092
|
transport: external_exports.string().optional(),
|
|
89093
|
+
messageId: external_exports.string().optional(),
|
|
88920
89094
|
sentCopy: SENT_COPY_SCHEMA,
|
|
88921
89095
|
sentCopyError: SENT_COPY_ERROR_SCHEMA
|
|
88922
89096
|
}
|
|
88923
89097
|
},
|
|
88924
89098
|
withErrorHandling(
|
|
88925
|
-
async ({
|
|
89099
|
+
async ({
|
|
89100
|
+
to,
|
|
89101
|
+
subject,
|
|
89102
|
+
body,
|
|
89103
|
+
cc,
|
|
89104
|
+
bcc,
|
|
89105
|
+
account,
|
|
89106
|
+
attachments,
|
|
89107
|
+
transport: transport2,
|
|
89108
|
+
replyTo,
|
|
89109
|
+
inReplyTo,
|
|
89110
|
+
references
|
|
89111
|
+
}) => {
|
|
88926
89112
|
const attachInfo = attachments?.length ? ` with ${attachments.length} attachment(s)` : "";
|
|
88927
89113
|
const attachmentCount = attachments?.length ?? 0;
|
|
88928
|
-
|
|
89114
|
+
const useSmtp = shouldUseSmtp(transport2, account);
|
|
89115
|
+
const threading = sendEmailThreading({ inReplyTo, references }, useSmtp);
|
|
89116
|
+
if (!threading.ok) return errorResponse(threading.error);
|
|
89117
|
+
if (useSmtp) {
|
|
88929
89118
|
const smtpFrom = account?.includes("@") ? account : void 0;
|
|
88930
89119
|
const result = await sendViaSmtp({
|
|
88931
89120
|
to,
|
|
@@ -88935,7 +89124,8 @@ registerTool(
|
|
|
88935
89124
|
bcc,
|
|
88936
89125
|
from: smtpFrom,
|
|
88937
89126
|
attachments,
|
|
88938
|
-
replyTo
|
|
89127
|
+
replyTo,
|
|
89128
|
+
...threading.headers
|
|
88939
89129
|
});
|
|
88940
89130
|
if (!result.success) {
|
|
88941
89131
|
return errorResponse(result.error ?? "Failed to send email via SMTP.");
|
|
@@ -88946,6 +89136,7 @@ registerTool(
|
|
|
88946
89136
|
recipients: to,
|
|
88947
89137
|
attachmentCount,
|
|
88948
89138
|
transport: "smtp",
|
|
89139
|
+
...result.messageId ? { messageId: result.messageId } : {},
|
|
88949
89140
|
...result.sentCopy !== void 0 ? { sentCopy: result.sentCopy } : {},
|
|
88950
89141
|
...result.sentCopyError !== void 0 ? { sentCopyError: result.sentCopyError } : {}
|
|
88951
89142
|
});
|
|
@@ -89023,36 +89214,73 @@ ${details}`,
|
|
|
89023
89214
|
registerTool(
|
|
89024
89215
|
"create-draft",
|
|
89025
89216
|
{
|
|
89026
|
-
description: "Use when: composing an email the user should review in Mail.app before sending \u2014 the safe default for any new message (to/cc/bcc are arrays, optional attachments).\nReturns: a confirmation that the draft was created, with recipients and attachment count.\nDo not use when: the user has already confirmed they want it sent now (use send-email).\nSafety: low risk \u2014 creates a draft only and sends nothing; the user must open Mail.app and send it themselves.",
|
|
89217
|
+
description: "Use when: composing an email the user should review in Mail.app before sending \u2014 the safe default for any new message (to/cc/bcc are arrays, optional attachments). With inReplyTo/references the draft is threaded: it is composed by this server and filed over IMAP into the account's Drafts mailbox (Mail.app's AppleScript cannot set those headers), so an IMAP account is required.\nReturns: a confirmation that the draft was created, with recipients and attachment count; for a threaded draft also its Drafts mailbox, account and Message-ID (transport: imap).\nDo not use when: the user has already confirmed they want it sent now (use send-email), or is replying to an existing message (use reply-to-message with send=false \u2014 it derives recipients, threading and the quote and takes attachments).\nSafety: low risk \u2014 creates a draft only and sends nothing; the user must open Mail.app and send it themselves.",
|
|
89027
89218
|
inputSchema: {
|
|
89028
89219
|
to: external_exports.array(external_exports.string()).min(1, "At least one recipient is required"),
|
|
89029
89220
|
subject: external_exports.string().min(1, "Subject is required"),
|
|
89030
89221
|
body: external_exports.string().min(1, "Body is required"),
|
|
89031
89222
|
cc: external_exports.array(external_exports.string()).optional().describe("CC recipients"),
|
|
89032
89223
|
bcc: external_exports.array(external_exports.string()).optional().describe("BCC recipients"),
|
|
89033
|
-
account: external_exports.string().optional().describe(
|
|
89034
|
-
|
|
89224
|
+
account: external_exports.string().optional().describe(
|
|
89225
|
+
"Account to create draft in. With inReplyTo/references this must name a configured IMAP account (label or login); when omitted, the IMAP account matching the SMTP identity, else the only IMAP account, is used."
|
|
89226
|
+
),
|
|
89227
|
+
attachments: ATTACHMENTS_SCHEMA,
|
|
89228
|
+
inReplyTo: IN_REPLY_TO_SCHEMA,
|
|
89229
|
+
references: REFERENCES_SCHEMA
|
|
89035
89230
|
},
|
|
89036
89231
|
outputSchema: {
|
|
89037
89232
|
ok: external_exports.boolean().optional(),
|
|
89038
89233
|
recipients: external_exports.array(external_exports.string()).optional(),
|
|
89039
|
-
attachmentCount: external_exports.number().optional()
|
|
89234
|
+
attachmentCount: external_exports.number().optional(),
|
|
89235
|
+
transport: external_exports.enum(["applescript", "imap"]).optional(),
|
|
89236
|
+
account: external_exports.string().optional().describe("Threaded draft only: IMAP account it was filed in"),
|
|
89237
|
+
mailbox: external_exports.string().optional().describe("Threaded draft only: Drafts mailbox path"),
|
|
89238
|
+
messageId: external_exports.string().optional().describe("Threaded draft only: the draft's Message-ID"),
|
|
89239
|
+
inReplyTo: external_exports.string().optional(),
|
|
89240
|
+
references: external_exports.array(external_exports.string()).optional()
|
|
89040
89241
|
}
|
|
89041
89242
|
},
|
|
89042
|
-
withErrorHandling(
|
|
89043
|
-
|
|
89044
|
-
|
|
89045
|
-
|
|
89046
|
-
|
|
89047
|
-
|
|
89048
|
-
|
|
89049
|
-
|
|
89050
|
-
|
|
89051
|
-
|
|
89052
|
-
|
|
89053
|
-
|
|
89054
|
-
|
|
89243
|
+
withErrorHandling(
|
|
89244
|
+
async ({ to, subject, body, cc, bcc, account, attachments, inReplyTo, references }) => {
|
|
89245
|
+
if (threadingHeaders({ inReplyTo, references })) {
|
|
89246
|
+
return runThreadedDraft(threadedDraftDeps, {
|
|
89247
|
+
to,
|
|
89248
|
+
subject,
|
|
89249
|
+
body,
|
|
89250
|
+
cc,
|
|
89251
|
+
bcc,
|
|
89252
|
+
account,
|
|
89253
|
+
attachments,
|
|
89254
|
+
inReplyTo,
|
|
89255
|
+
references
|
|
89256
|
+
});
|
|
89257
|
+
}
|
|
89258
|
+
const success = mailManager.createDraft(to, subject, body, cc, bcc, account, attachments);
|
|
89259
|
+
if (!success) {
|
|
89260
|
+
return errorResponse("Failed to create draft. Check Mail.app configuration.");
|
|
89261
|
+
}
|
|
89262
|
+
const attachmentCount = attachments?.length ?? 0;
|
|
89263
|
+
const attachInfo = attachmentCount ? ` with ${attachmentCount} attachment(s)` : "";
|
|
89264
|
+
return successResponse(`Draft created for ${to.join(", ")}${attachInfo}`, {
|
|
89265
|
+
ok: true,
|
|
89266
|
+
recipients: to,
|
|
89267
|
+
attachmentCount,
|
|
89268
|
+
transport: "applescript"
|
|
89269
|
+
});
|
|
89270
|
+
},
|
|
89271
|
+
"Error creating draft"
|
|
89272
|
+
)
|
|
89055
89273
|
);
|
|
89274
|
+
var threadedDraftDeps = {
|
|
89275
|
+
resolveAccount: (account, smtpUser) => resolveDraftImapAccount(account, smtpUser),
|
|
89276
|
+
smtpIdentity: () => {
|
|
89277
|
+
if (!isSmtpConfigured()) return {};
|
|
89278
|
+
const user = process.env[SMTP_ENV.user]?.trim();
|
|
89279
|
+
return { user, from: process.env[SMTP_ENV.from]?.trim() || user };
|
|
89280
|
+
},
|
|
89281
|
+
compose: composeRawMime,
|
|
89282
|
+
append: (account, raw) => imapAppendDraft(account, raw)
|
|
89283
|
+
};
|
|
89056
89284
|
function resolveSmtpOrFallback() {
|
|
89057
89285
|
try {
|
|
89058
89286
|
return resolveSmtpConfig();
|
|
@@ -89091,13 +89319,14 @@ var COMPOSE_TRANSPORT_SCHEMA = external_exports.enum(["applescript", "smtp"]).op
|
|
|
89091
89319
|
registerTool(
|
|
89092
89320
|
"reply-to-message",
|
|
89093
89321
|
{
|
|
89094
|
-
description: "Use when: replying to an existing message by id, preserving its threading headers. Set replyAll for all recipients; set send=false to save as a draft instead of sending.\nReturns: a confirmation that the reply was sent or saved as a draft; over SMTP, also whether a Sent-folder copy was filed (sentCopy).\nDo not use when: composing a brand-new message (use send-email / create-draft) or forwarding to new recipients (use forward-message).\nSafety: with the default send=true this SENDS real email immediately and cannot be unsent \u2014 require explicit user confirmation of the recipients and body, or pass send=false to let the user review.",
|
|
89322
|
+
description: "Use when: replying to an existing message by id, preserving its threading headers and quoting the original, optionally with attachments (same rules as send-email). Set replyAll for all recipients; set send=false to save as a draft instead of sending.\nReturns: a confirmation that the reply was sent or saved as a draft, with the attachment count; over SMTP, also whether a Sent-folder copy was filed (sentCopy).\nDo not use when: composing a brand-new message (use send-email / create-draft) or forwarding to new recipients (use forward-message).\nSafety: with the default send=true this SENDS real email immediately and cannot be unsent \u2014 require explicit user confirmation of the recipients and body, or pass send=false to let the user review.",
|
|
89095
89323
|
inputSchema: {
|
|
89096
89324
|
id: MESSAGE_ID_SCHEMA,
|
|
89097
89325
|
transport: COMPOSE_TRANSPORT_SCHEMA,
|
|
89098
89326
|
body: external_exports.string().min(1, "Reply body is required").describe("Reply body (plain text; HTML tags such as <br> are not rendered)"),
|
|
89099
89327
|
replyAll: external_exports.boolean().optional().default(false).describe("Reply to all recipients"),
|
|
89100
|
-
send: external_exports.boolean().optional().default(true).describe("Send immediately (false = save as draft)")
|
|
89328
|
+
send: external_exports.boolean().optional().default(true).describe("Send immediately (false = save as draft)"),
|
|
89329
|
+
attachments: ATTACHMENTS_SCHEMA
|
|
89101
89330
|
},
|
|
89102
89331
|
outputSchema: {
|
|
89103
89332
|
transport: external_exports.enum(["smtp", "applescript"]).optional(),
|
|
@@ -89105,6 +89334,7 @@ registerTool(
|
|
|
89105
89334
|
ok: external_exports.boolean().optional(),
|
|
89106
89335
|
sent: external_exports.boolean().optional(),
|
|
89107
89336
|
id: external_exports.string().optional(),
|
|
89337
|
+
attachmentCount: external_exports.number().optional(),
|
|
89108
89338
|
sentCopy: SENT_COPY_SCHEMA,
|
|
89109
89339
|
sentCopyError: SENT_COPY_ERROR_SCHEMA
|
|
89110
89340
|
}
|
|
@@ -89114,13 +89344,14 @@ registerTool(
|
|
|
89114
89344
|
registerTool(
|
|
89115
89345
|
"forward-message",
|
|
89116
89346
|
{
|
|
89117
|
-
description: "Use when: forwarding an existing message (by id) to new recipients (to is an array), with an optional body to prepend. Set send=false to save as a draft.\nReturns: a confirmation that the message was forwarded or saved as a draft; over SMTP, also whether a Sent-folder copy was filed (sentCopy).\nDo not use when: replying to the sender/recipients (use reply-to-message) or composing a new message (use send-email / create-draft).\nSafety: with the default send=true this SENDS real email immediately and cannot be unsent \u2014 require explicit user confirmation of the recipients and any prepended body, or pass send=false to let the user review.",
|
|
89347
|
+
description: "Use when: forwarding an existing message (by id) to new recipients (to is an array), with an optional body to prepend and optional extra attachments (same rules as send-email). Set send=false to save as a draft.\nReturns: a confirmation that the message was forwarded or saved as a draft, with the attachment count; over SMTP, also whether a Sent-folder copy was filed (sentCopy).\nDo not use when: replying to the sender/recipients (use reply-to-message) or composing a new message (use send-email / create-draft).\nSafety: with the default send=true this SENDS real email immediately and cannot be unsent \u2014 require explicit user confirmation of the recipients and any prepended body, or pass send=false to let the user review.",
|
|
89118
89348
|
inputSchema: {
|
|
89119
89349
|
id: MESSAGE_ID_SCHEMA,
|
|
89120
89350
|
transport: COMPOSE_TRANSPORT_SCHEMA,
|
|
89121
89351
|
to: external_exports.array(external_exports.string()).min(1, "At least one recipient is required"),
|
|
89122
89352
|
body: external_exports.string().optional().describe("Optional message to prepend (plain text)"),
|
|
89123
|
-
send: external_exports.boolean().optional().default(true).describe("Send immediately (false = save as draft)")
|
|
89353
|
+
send: external_exports.boolean().optional().default(true).describe("Send immediately (false = save as draft)"),
|
|
89354
|
+
attachments: ATTACHMENTS_SCHEMA
|
|
89124
89355
|
},
|
|
89125
89356
|
outputSchema: {
|
|
89126
89357
|
transport: external_exports.enum(["smtp", "applescript"]).optional(),
|
|
@@ -89129,6 +89360,7 @@ registerTool(
|
|
|
89129
89360
|
sent: external_exports.boolean().optional(),
|
|
89130
89361
|
recipients: external_exports.array(external_exports.string()).optional(),
|
|
89131
89362
|
id: external_exports.string().optional(),
|
|
89363
|
+
attachmentCount: external_exports.number().optional(),
|
|
89132
89364
|
sentCopy: SENT_COPY_SCHEMA,
|
|
89133
89365
|
sentCopyError: SENT_COPY_ERROR_SCHEMA
|
|
89134
89366
|
}
|
package/package.json
CHANGED