@agenticmail/core 0.7.4 → 0.7.5
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/dist/index.cjs +109 -4
- package/dist/index.d.cts +74 -1
- package/dist/index.d.ts +74 -1
- package/dist/index.js +109 -4
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -994,14 +994,79 @@ var MailReceiver = class {
|
|
|
994
994
|
lock.release();
|
|
995
995
|
}
|
|
996
996
|
}
|
|
997
|
-
|
|
997
|
+
/**
|
|
998
|
+
* Permanently remove a single message via IMAP EXPUNGE.
|
|
999
|
+
*
|
|
1000
|
+
* DANGEROUS — EXPUNGE is mailbox-wide. The IMAP semantics are:
|
|
1001
|
+
*
|
|
1002
|
+
* 1. STORE +FLAGS (\Deleted) on the target UID
|
|
1003
|
+
* 2. EXPUNGE → removes EVERY message in the mailbox that has
|
|
1004
|
+
* \Deleted set, not just the one we just flagged
|
|
1005
|
+
*
|
|
1006
|
+
* If any other messages in the mailbox already had \Deleted
|
|
1007
|
+
* (from a previous half-completed delete, an agent operation,
|
|
1008
|
+
* an external client) they all vanish too. This is the IMAP
|
|
1009
|
+
* spec, not an ImapFlow quirk.
|
|
1010
|
+
*
|
|
1011
|
+
* Callers that just want "delete this email" — i.e. the Gmail
|
|
1012
|
+
* UX — should use `moveToTrash()` instead, which moves the
|
|
1013
|
+
* message to the trash mailbox without touching \Deleted.
|
|
1014
|
+
* Reserve `expungeMessage` for explicit "empty trash" /
|
|
1015
|
+
* permanent-delete UI paths.
|
|
1016
|
+
*
|
|
1017
|
+
* If the server supports UIDPLUS (RFC 4315), we use UID EXPUNGE
|
|
1018
|
+
* to limit the scope to the target UID — even then, callers
|
|
1019
|
+
* should treat this as the destructive option.
|
|
1020
|
+
*/
|
|
1021
|
+
async expungeMessage(uid, mailbox = "INBOX") {
|
|
998
1022
|
const lock = await this.client.getMailboxLock(mailbox);
|
|
999
1023
|
try {
|
|
1024
|
+
const caps = this.client.capabilities;
|
|
1025
|
+
const hasUidPlus = caps && (Array.isArray(caps) ? caps.includes("UIDPLUS") : caps.has("UIDPLUS"));
|
|
1026
|
+
if (hasUidPlus) {
|
|
1027
|
+
await this.client.messageFlagsAdd(String(uid), ["\\Deleted"], { uid: true });
|
|
1028
|
+
const exec = this.client.exec;
|
|
1029
|
+
if (typeof exec === "function") {
|
|
1030
|
+
await exec.call(this.client, "UID EXPUNGE", [String(uid)]);
|
|
1031
|
+
return;
|
|
1032
|
+
}
|
|
1033
|
+
}
|
|
1000
1034
|
await this.client.messageDelete(String(uid), { uid: true });
|
|
1001
1035
|
} finally {
|
|
1002
1036
|
lock.release();
|
|
1003
1037
|
}
|
|
1004
1038
|
}
|
|
1039
|
+
/**
|
|
1040
|
+
* Move a single message to the trash mailbox.
|
|
1041
|
+
*
|
|
1042
|
+
* This is the Gmail / Outlook "delete" semantics — the user
|
|
1043
|
+
* still sees the message under Trash and can restore it. No
|
|
1044
|
+
* \Deleted flag is set, no EXPUNGE happens, so other messages
|
|
1045
|
+
* in the source mailbox are untouched.
|
|
1046
|
+
*
|
|
1047
|
+
* `trashMailbox` is the IMAP folder name (varies by server:
|
|
1048
|
+
* Stalwart uses "Deleted Items" by default; Gmail uses
|
|
1049
|
+
* "[Gmail]/Trash"; etc.). Callers should pass the discovered
|
|
1050
|
+
* name rather than hard-coding.
|
|
1051
|
+
*/
|
|
1052
|
+
async moveToTrash(uid, fromMailbox, trashMailbox) {
|
|
1053
|
+
if (fromMailbox === trashMailbox) {
|
|
1054
|
+
throw new Error("source and trash mailbox are the same; use expungeMessage for permanent delete");
|
|
1055
|
+
}
|
|
1056
|
+
return this.moveMessage(uid, fromMailbox, trashMailbox);
|
|
1057
|
+
}
|
|
1058
|
+
/**
|
|
1059
|
+
* Back-compat alias for callers that haven't migrated to the
|
|
1060
|
+
* explicit moveToTrash / expungeMessage split yet. Behaviour is
|
|
1061
|
+
* unchanged: this still EXPUNGES (mailbox-wide). New callers
|
|
1062
|
+
* should use moveToTrash() unless they specifically want the
|
|
1063
|
+
* destructive variant.
|
|
1064
|
+
*
|
|
1065
|
+
* @deprecated Use moveToTrash() or expungeMessage() instead.
|
|
1066
|
+
*/
|
|
1067
|
+
async deleteMessage(uid, mailbox = "INBOX") {
|
|
1068
|
+
return this.expungeMessage(uid, mailbox);
|
|
1069
|
+
}
|
|
1005
1070
|
/** Mark a message as unseen (unread) */
|
|
1006
1071
|
async markUnseen(uid, mailbox = "INBOX") {
|
|
1007
1072
|
const lock = await this.client.getMailboxLock(mailbox);
|
|
@@ -1030,10 +1095,34 @@ var MailReceiver = class {
|
|
|
1030
1095
|
}
|
|
1031
1096
|
}
|
|
1032
1097
|
/** Move a message to another folder */
|
|
1098
|
+
/**
|
|
1099
|
+
* Move a single message from one mailbox to another.
|
|
1100
|
+
*
|
|
1101
|
+
* Uses the IMAP MOVE extension (RFC 6851) when the server
|
|
1102
|
+
* advertises it — that command is atomic and scoped: only the
|
|
1103
|
+
* named UID moves, no other mailbox state is touched.
|
|
1104
|
+
*
|
|
1105
|
+
* Falls back to **COPY + STORE +\Deleted on the source UID
|
|
1106
|
+
* ONLY (no EXPUNGE)** when the server doesn't support MOVE.
|
|
1107
|
+
* The source message is left in place with the `\Deleted`
|
|
1108
|
+
* flag; it disappears on the next expunge from a permanent-
|
|
1109
|
+
* delete action. This is intentional: a mailbox-wide EXPUNGE
|
|
1110
|
+
* here would wipe every previously-`\Deleted` message in the
|
|
1111
|
+
* source mailbox as a side effect, which was the bug that
|
|
1112
|
+
* cleared a user's inbox in 0.8.32. Leaving the flag set is
|
|
1113
|
+
* the safe fallback.
|
|
1114
|
+
*/
|
|
1033
1115
|
async moveMessage(uid, fromMailbox, toMailbox) {
|
|
1034
1116
|
const lock = await this.client.getMailboxLock(fromMailbox);
|
|
1035
1117
|
try {
|
|
1036
|
-
|
|
1118
|
+
const caps = this.client.capabilities;
|
|
1119
|
+
const hasMove = caps && (Array.isArray(caps) ? caps.includes("MOVE") : caps.has("MOVE"));
|
|
1120
|
+
if (hasMove) {
|
|
1121
|
+
await this.client.messageMove(String(uid), toMailbox, { uid: true });
|
|
1122
|
+
return;
|
|
1123
|
+
}
|
|
1124
|
+
await this.client.messageCopy(String(uid), toMailbox, { uid: true });
|
|
1125
|
+
await this.client.messageFlagsAdd(String(uid), ["\\Deleted"], { uid: true });
|
|
1037
1126
|
} finally {
|
|
1038
1127
|
lock.release();
|
|
1039
1128
|
}
|
|
@@ -1098,12 +1187,28 @@ var MailReceiver = class {
|
|
|
1098
1187
|
lock.release();
|
|
1099
1188
|
}
|
|
1100
1189
|
}
|
|
1101
|
-
/**
|
|
1190
|
+
/**
|
|
1191
|
+
* Batch move multiple messages to another folder.
|
|
1192
|
+
*
|
|
1193
|
+
* Same safety model as `moveMessage`: prefers the IMAP MOVE
|
|
1194
|
+
* extension (atomic, scoped per UID); falls back to
|
|
1195
|
+
* COPY + STORE \Deleted with NO mailbox-wide EXPUNGE so an
|
|
1196
|
+
* existing `\Deleted` flag on an unrelated message can't
|
|
1197
|
+
* be amplified into a full inbox wipe.
|
|
1198
|
+
*/
|
|
1102
1199
|
async batchMove(uids, fromMailbox, toMailbox) {
|
|
1103
1200
|
if (uids.length === 0) return;
|
|
1201
|
+
const range = uids.join(",");
|
|
1104
1202
|
const lock = await this.client.getMailboxLock(fromMailbox);
|
|
1105
1203
|
try {
|
|
1106
|
-
|
|
1204
|
+
const caps = this.client.capabilities;
|
|
1205
|
+
const hasMove = caps && (Array.isArray(caps) ? caps.includes("MOVE") : caps.has("MOVE"));
|
|
1206
|
+
if (hasMove) {
|
|
1207
|
+
await this.client.messageMove(range, toMailbox, { uid: true });
|
|
1208
|
+
return;
|
|
1209
|
+
}
|
|
1210
|
+
await this.client.messageCopy(range, toMailbox, { uid: true });
|
|
1211
|
+
await this.client.messageFlagsAdd(range, ["\\Deleted"], { uid: true });
|
|
1107
1212
|
} finally {
|
|
1108
1213
|
lock.release();
|
|
1109
1214
|
}
|
package/dist/index.d.cts
CHANGED
|
@@ -470,6 +470,54 @@ declare class MailReceiver {
|
|
|
470
470
|
fetchMessage(uid: number, mailbox?: string): Promise<Buffer>;
|
|
471
471
|
search(criteria: SearchCriteria, mailbox?: string): Promise<number[]>;
|
|
472
472
|
markSeen(uid: number, mailbox?: string): Promise<void>;
|
|
473
|
+
/**
|
|
474
|
+
* Permanently remove a single message via IMAP EXPUNGE.
|
|
475
|
+
*
|
|
476
|
+
* DANGEROUS — EXPUNGE is mailbox-wide. The IMAP semantics are:
|
|
477
|
+
*
|
|
478
|
+
* 1. STORE +FLAGS (\Deleted) on the target UID
|
|
479
|
+
* 2. EXPUNGE → removes EVERY message in the mailbox that has
|
|
480
|
+
* \Deleted set, not just the one we just flagged
|
|
481
|
+
*
|
|
482
|
+
* If any other messages in the mailbox already had \Deleted
|
|
483
|
+
* (from a previous half-completed delete, an agent operation,
|
|
484
|
+
* an external client) they all vanish too. This is the IMAP
|
|
485
|
+
* spec, not an ImapFlow quirk.
|
|
486
|
+
*
|
|
487
|
+
* Callers that just want "delete this email" — i.e. the Gmail
|
|
488
|
+
* UX — should use `moveToTrash()` instead, which moves the
|
|
489
|
+
* message to the trash mailbox without touching \Deleted.
|
|
490
|
+
* Reserve `expungeMessage` for explicit "empty trash" /
|
|
491
|
+
* permanent-delete UI paths.
|
|
492
|
+
*
|
|
493
|
+
* If the server supports UIDPLUS (RFC 4315), we use UID EXPUNGE
|
|
494
|
+
* to limit the scope to the target UID — even then, callers
|
|
495
|
+
* should treat this as the destructive option.
|
|
496
|
+
*/
|
|
497
|
+
expungeMessage(uid: number, mailbox?: string): Promise<void>;
|
|
498
|
+
/**
|
|
499
|
+
* Move a single message to the trash mailbox.
|
|
500
|
+
*
|
|
501
|
+
* This is the Gmail / Outlook "delete" semantics — the user
|
|
502
|
+
* still sees the message under Trash and can restore it. No
|
|
503
|
+
* \Deleted flag is set, no EXPUNGE happens, so other messages
|
|
504
|
+
* in the source mailbox are untouched.
|
|
505
|
+
*
|
|
506
|
+
* `trashMailbox` is the IMAP folder name (varies by server:
|
|
507
|
+
* Stalwart uses "Deleted Items" by default; Gmail uses
|
|
508
|
+
* "[Gmail]/Trash"; etc.). Callers should pass the discovered
|
|
509
|
+
* name rather than hard-coding.
|
|
510
|
+
*/
|
|
511
|
+
moveToTrash(uid: number, fromMailbox: string, trashMailbox: string): Promise<void>;
|
|
512
|
+
/**
|
|
513
|
+
* Back-compat alias for callers that haven't migrated to the
|
|
514
|
+
* explicit moveToTrash / expungeMessage split yet. Behaviour is
|
|
515
|
+
* unchanged: this still EXPUNGES (mailbox-wide). New callers
|
|
516
|
+
* should use moveToTrash() unless they specifically want the
|
|
517
|
+
* destructive variant.
|
|
518
|
+
*
|
|
519
|
+
* @deprecated Use moveToTrash() or expungeMessage() instead.
|
|
520
|
+
*/
|
|
473
521
|
deleteMessage(uid: number, mailbox?: string): Promise<void>;
|
|
474
522
|
/** Mark a message as unseen (unread) */
|
|
475
523
|
markUnseen(uid: number, mailbox?: string): Promise<void>;
|
|
@@ -481,6 +529,23 @@ declare class MailReceiver {
|
|
|
481
529
|
*/
|
|
482
530
|
setStarred(uid: number, starred: boolean, mailbox?: string): Promise<void>;
|
|
483
531
|
/** Move a message to another folder */
|
|
532
|
+
/**
|
|
533
|
+
* Move a single message from one mailbox to another.
|
|
534
|
+
*
|
|
535
|
+
* Uses the IMAP MOVE extension (RFC 6851) when the server
|
|
536
|
+
* advertises it — that command is atomic and scoped: only the
|
|
537
|
+
* named UID moves, no other mailbox state is touched.
|
|
538
|
+
*
|
|
539
|
+
* Falls back to **COPY + STORE +\Deleted on the source UID
|
|
540
|
+
* ONLY (no EXPUNGE)** when the server doesn't support MOVE.
|
|
541
|
+
* The source message is left in place with the `\Deleted`
|
|
542
|
+
* flag; it disappears on the next expunge from a permanent-
|
|
543
|
+
* delete action. This is intentional: a mailbox-wide EXPUNGE
|
|
544
|
+
* here would wipe every previously-`\Deleted` message in the
|
|
545
|
+
* source mailbox as a side effect, which was the bug that
|
|
546
|
+
* cleared a user's inbox in 0.8.32. Leaving the flag set is
|
|
547
|
+
* the safe fallback.
|
|
548
|
+
*/
|
|
484
549
|
moveMessage(uid: number, fromMailbox: string, toMailbox: string): Promise<void>;
|
|
485
550
|
/** List all IMAP folders/mailboxes */
|
|
486
551
|
listFolders(): Promise<FolderInfo[]>;
|
|
@@ -494,7 +559,15 @@ declare class MailReceiver {
|
|
|
494
559
|
batchDelete(uids: number[], mailbox?: string): Promise<void>;
|
|
495
560
|
/** Batch fetch raw message content for multiple UIDs */
|
|
496
561
|
batchFetch(uids: number[], mailbox?: string): Promise<Map<number, Buffer>>;
|
|
497
|
-
/**
|
|
562
|
+
/**
|
|
563
|
+
* Batch move multiple messages to another folder.
|
|
564
|
+
*
|
|
565
|
+
* Same safety model as `moveMessage`: prefers the IMAP MOVE
|
|
566
|
+
* extension (atomic, scoped per UID); falls back to
|
|
567
|
+
* COPY + STORE \Deleted with NO mailbox-wide EXPUNGE so an
|
|
568
|
+
* existing `\Deleted` flag on an unrelated message can't
|
|
569
|
+
* be amplified into a full inbox wipe.
|
|
570
|
+
*/
|
|
498
571
|
batchMove(uids: number[], fromMailbox: string, toMailbox: string): Promise<void>;
|
|
499
572
|
/** Append a raw RFC822 message to a mailbox (e.g. "Sent") with given flags */
|
|
500
573
|
appendMessage(raw: Buffer, mailbox: string, flags?: string[]): Promise<void>;
|
package/dist/index.d.ts
CHANGED
|
@@ -470,6 +470,54 @@ declare class MailReceiver {
|
|
|
470
470
|
fetchMessage(uid: number, mailbox?: string): Promise<Buffer>;
|
|
471
471
|
search(criteria: SearchCriteria, mailbox?: string): Promise<number[]>;
|
|
472
472
|
markSeen(uid: number, mailbox?: string): Promise<void>;
|
|
473
|
+
/**
|
|
474
|
+
* Permanently remove a single message via IMAP EXPUNGE.
|
|
475
|
+
*
|
|
476
|
+
* DANGEROUS — EXPUNGE is mailbox-wide. The IMAP semantics are:
|
|
477
|
+
*
|
|
478
|
+
* 1. STORE +FLAGS (\Deleted) on the target UID
|
|
479
|
+
* 2. EXPUNGE → removes EVERY message in the mailbox that has
|
|
480
|
+
* \Deleted set, not just the one we just flagged
|
|
481
|
+
*
|
|
482
|
+
* If any other messages in the mailbox already had \Deleted
|
|
483
|
+
* (from a previous half-completed delete, an agent operation,
|
|
484
|
+
* an external client) they all vanish too. This is the IMAP
|
|
485
|
+
* spec, not an ImapFlow quirk.
|
|
486
|
+
*
|
|
487
|
+
* Callers that just want "delete this email" — i.e. the Gmail
|
|
488
|
+
* UX — should use `moveToTrash()` instead, which moves the
|
|
489
|
+
* message to the trash mailbox without touching \Deleted.
|
|
490
|
+
* Reserve `expungeMessage` for explicit "empty trash" /
|
|
491
|
+
* permanent-delete UI paths.
|
|
492
|
+
*
|
|
493
|
+
* If the server supports UIDPLUS (RFC 4315), we use UID EXPUNGE
|
|
494
|
+
* to limit the scope to the target UID — even then, callers
|
|
495
|
+
* should treat this as the destructive option.
|
|
496
|
+
*/
|
|
497
|
+
expungeMessage(uid: number, mailbox?: string): Promise<void>;
|
|
498
|
+
/**
|
|
499
|
+
* Move a single message to the trash mailbox.
|
|
500
|
+
*
|
|
501
|
+
* This is the Gmail / Outlook "delete" semantics — the user
|
|
502
|
+
* still sees the message under Trash and can restore it. No
|
|
503
|
+
* \Deleted flag is set, no EXPUNGE happens, so other messages
|
|
504
|
+
* in the source mailbox are untouched.
|
|
505
|
+
*
|
|
506
|
+
* `trashMailbox` is the IMAP folder name (varies by server:
|
|
507
|
+
* Stalwart uses "Deleted Items" by default; Gmail uses
|
|
508
|
+
* "[Gmail]/Trash"; etc.). Callers should pass the discovered
|
|
509
|
+
* name rather than hard-coding.
|
|
510
|
+
*/
|
|
511
|
+
moveToTrash(uid: number, fromMailbox: string, trashMailbox: string): Promise<void>;
|
|
512
|
+
/**
|
|
513
|
+
* Back-compat alias for callers that haven't migrated to the
|
|
514
|
+
* explicit moveToTrash / expungeMessage split yet. Behaviour is
|
|
515
|
+
* unchanged: this still EXPUNGES (mailbox-wide). New callers
|
|
516
|
+
* should use moveToTrash() unless they specifically want the
|
|
517
|
+
* destructive variant.
|
|
518
|
+
*
|
|
519
|
+
* @deprecated Use moveToTrash() or expungeMessage() instead.
|
|
520
|
+
*/
|
|
473
521
|
deleteMessage(uid: number, mailbox?: string): Promise<void>;
|
|
474
522
|
/** Mark a message as unseen (unread) */
|
|
475
523
|
markUnseen(uid: number, mailbox?: string): Promise<void>;
|
|
@@ -481,6 +529,23 @@ declare class MailReceiver {
|
|
|
481
529
|
*/
|
|
482
530
|
setStarred(uid: number, starred: boolean, mailbox?: string): Promise<void>;
|
|
483
531
|
/** Move a message to another folder */
|
|
532
|
+
/**
|
|
533
|
+
* Move a single message from one mailbox to another.
|
|
534
|
+
*
|
|
535
|
+
* Uses the IMAP MOVE extension (RFC 6851) when the server
|
|
536
|
+
* advertises it — that command is atomic and scoped: only the
|
|
537
|
+
* named UID moves, no other mailbox state is touched.
|
|
538
|
+
*
|
|
539
|
+
* Falls back to **COPY + STORE +\Deleted on the source UID
|
|
540
|
+
* ONLY (no EXPUNGE)** when the server doesn't support MOVE.
|
|
541
|
+
* The source message is left in place with the `\Deleted`
|
|
542
|
+
* flag; it disappears on the next expunge from a permanent-
|
|
543
|
+
* delete action. This is intentional: a mailbox-wide EXPUNGE
|
|
544
|
+
* here would wipe every previously-`\Deleted` message in the
|
|
545
|
+
* source mailbox as a side effect, which was the bug that
|
|
546
|
+
* cleared a user's inbox in 0.8.32. Leaving the flag set is
|
|
547
|
+
* the safe fallback.
|
|
548
|
+
*/
|
|
484
549
|
moveMessage(uid: number, fromMailbox: string, toMailbox: string): Promise<void>;
|
|
485
550
|
/** List all IMAP folders/mailboxes */
|
|
486
551
|
listFolders(): Promise<FolderInfo[]>;
|
|
@@ -494,7 +559,15 @@ declare class MailReceiver {
|
|
|
494
559
|
batchDelete(uids: number[], mailbox?: string): Promise<void>;
|
|
495
560
|
/** Batch fetch raw message content for multiple UIDs */
|
|
496
561
|
batchFetch(uids: number[], mailbox?: string): Promise<Map<number, Buffer>>;
|
|
497
|
-
/**
|
|
562
|
+
/**
|
|
563
|
+
* Batch move multiple messages to another folder.
|
|
564
|
+
*
|
|
565
|
+
* Same safety model as `moveMessage`: prefers the IMAP MOVE
|
|
566
|
+
* extension (atomic, scoped per UID); falls back to
|
|
567
|
+
* COPY + STORE \Deleted with NO mailbox-wide EXPUNGE so an
|
|
568
|
+
* existing `\Deleted` flag on an unrelated message can't
|
|
569
|
+
* be amplified into a full inbox wipe.
|
|
570
|
+
*/
|
|
498
571
|
batchMove(uids: number[], fromMailbox: string, toMailbox: string): Promise<void>;
|
|
499
572
|
/** Append a raw RFC822 message to a mailbox (e.g. "Sent") with given flags */
|
|
500
573
|
appendMessage(raw: Buffer, mailbox: string, flags?: string[]): Promise<void>;
|
package/dist/index.js
CHANGED
|
@@ -240,14 +240,79 @@ var MailReceiver = class {
|
|
|
240
240
|
lock.release();
|
|
241
241
|
}
|
|
242
242
|
}
|
|
243
|
-
|
|
243
|
+
/**
|
|
244
|
+
* Permanently remove a single message via IMAP EXPUNGE.
|
|
245
|
+
*
|
|
246
|
+
* DANGEROUS — EXPUNGE is mailbox-wide. The IMAP semantics are:
|
|
247
|
+
*
|
|
248
|
+
* 1. STORE +FLAGS (\Deleted) on the target UID
|
|
249
|
+
* 2. EXPUNGE → removes EVERY message in the mailbox that has
|
|
250
|
+
* \Deleted set, not just the one we just flagged
|
|
251
|
+
*
|
|
252
|
+
* If any other messages in the mailbox already had \Deleted
|
|
253
|
+
* (from a previous half-completed delete, an agent operation,
|
|
254
|
+
* an external client) they all vanish too. This is the IMAP
|
|
255
|
+
* spec, not an ImapFlow quirk.
|
|
256
|
+
*
|
|
257
|
+
* Callers that just want "delete this email" — i.e. the Gmail
|
|
258
|
+
* UX — should use `moveToTrash()` instead, which moves the
|
|
259
|
+
* message to the trash mailbox without touching \Deleted.
|
|
260
|
+
* Reserve `expungeMessage` for explicit "empty trash" /
|
|
261
|
+
* permanent-delete UI paths.
|
|
262
|
+
*
|
|
263
|
+
* If the server supports UIDPLUS (RFC 4315), we use UID EXPUNGE
|
|
264
|
+
* to limit the scope to the target UID — even then, callers
|
|
265
|
+
* should treat this as the destructive option.
|
|
266
|
+
*/
|
|
267
|
+
async expungeMessage(uid, mailbox = "INBOX") {
|
|
244
268
|
const lock = await this.client.getMailboxLock(mailbox);
|
|
245
269
|
try {
|
|
270
|
+
const caps = this.client.capabilities;
|
|
271
|
+
const hasUidPlus = caps && (Array.isArray(caps) ? caps.includes("UIDPLUS") : caps.has("UIDPLUS"));
|
|
272
|
+
if (hasUidPlus) {
|
|
273
|
+
await this.client.messageFlagsAdd(String(uid), ["\\Deleted"], { uid: true });
|
|
274
|
+
const exec = this.client.exec;
|
|
275
|
+
if (typeof exec === "function") {
|
|
276
|
+
await exec.call(this.client, "UID EXPUNGE", [String(uid)]);
|
|
277
|
+
return;
|
|
278
|
+
}
|
|
279
|
+
}
|
|
246
280
|
await this.client.messageDelete(String(uid), { uid: true });
|
|
247
281
|
} finally {
|
|
248
282
|
lock.release();
|
|
249
283
|
}
|
|
250
284
|
}
|
|
285
|
+
/**
|
|
286
|
+
* Move a single message to the trash mailbox.
|
|
287
|
+
*
|
|
288
|
+
* This is the Gmail / Outlook "delete" semantics — the user
|
|
289
|
+
* still sees the message under Trash and can restore it. No
|
|
290
|
+
* \Deleted flag is set, no EXPUNGE happens, so other messages
|
|
291
|
+
* in the source mailbox are untouched.
|
|
292
|
+
*
|
|
293
|
+
* `trashMailbox` is the IMAP folder name (varies by server:
|
|
294
|
+
* Stalwart uses "Deleted Items" by default; Gmail uses
|
|
295
|
+
* "[Gmail]/Trash"; etc.). Callers should pass the discovered
|
|
296
|
+
* name rather than hard-coding.
|
|
297
|
+
*/
|
|
298
|
+
async moveToTrash(uid, fromMailbox, trashMailbox) {
|
|
299
|
+
if (fromMailbox === trashMailbox) {
|
|
300
|
+
throw new Error("source and trash mailbox are the same; use expungeMessage for permanent delete");
|
|
301
|
+
}
|
|
302
|
+
return this.moveMessage(uid, fromMailbox, trashMailbox);
|
|
303
|
+
}
|
|
304
|
+
/**
|
|
305
|
+
* Back-compat alias for callers that haven't migrated to the
|
|
306
|
+
* explicit moveToTrash / expungeMessage split yet. Behaviour is
|
|
307
|
+
* unchanged: this still EXPUNGES (mailbox-wide). New callers
|
|
308
|
+
* should use moveToTrash() unless they specifically want the
|
|
309
|
+
* destructive variant.
|
|
310
|
+
*
|
|
311
|
+
* @deprecated Use moveToTrash() or expungeMessage() instead.
|
|
312
|
+
*/
|
|
313
|
+
async deleteMessage(uid, mailbox = "INBOX") {
|
|
314
|
+
return this.expungeMessage(uid, mailbox);
|
|
315
|
+
}
|
|
251
316
|
/** Mark a message as unseen (unread) */
|
|
252
317
|
async markUnseen(uid, mailbox = "INBOX") {
|
|
253
318
|
const lock = await this.client.getMailboxLock(mailbox);
|
|
@@ -276,10 +341,34 @@ var MailReceiver = class {
|
|
|
276
341
|
}
|
|
277
342
|
}
|
|
278
343
|
/** Move a message to another folder */
|
|
344
|
+
/**
|
|
345
|
+
* Move a single message from one mailbox to another.
|
|
346
|
+
*
|
|
347
|
+
* Uses the IMAP MOVE extension (RFC 6851) when the server
|
|
348
|
+
* advertises it — that command is atomic and scoped: only the
|
|
349
|
+
* named UID moves, no other mailbox state is touched.
|
|
350
|
+
*
|
|
351
|
+
* Falls back to **COPY + STORE +\Deleted on the source UID
|
|
352
|
+
* ONLY (no EXPUNGE)** when the server doesn't support MOVE.
|
|
353
|
+
* The source message is left in place with the `\Deleted`
|
|
354
|
+
* flag; it disappears on the next expunge from a permanent-
|
|
355
|
+
* delete action. This is intentional: a mailbox-wide EXPUNGE
|
|
356
|
+
* here would wipe every previously-`\Deleted` message in the
|
|
357
|
+
* source mailbox as a side effect, which was the bug that
|
|
358
|
+
* cleared a user's inbox in 0.8.32. Leaving the flag set is
|
|
359
|
+
* the safe fallback.
|
|
360
|
+
*/
|
|
279
361
|
async moveMessage(uid, fromMailbox, toMailbox) {
|
|
280
362
|
const lock = await this.client.getMailboxLock(fromMailbox);
|
|
281
363
|
try {
|
|
282
|
-
|
|
364
|
+
const caps = this.client.capabilities;
|
|
365
|
+
const hasMove = caps && (Array.isArray(caps) ? caps.includes("MOVE") : caps.has("MOVE"));
|
|
366
|
+
if (hasMove) {
|
|
367
|
+
await this.client.messageMove(String(uid), toMailbox, { uid: true });
|
|
368
|
+
return;
|
|
369
|
+
}
|
|
370
|
+
await this.client.messageCopy(String(uid), toMailbox, { uid: true });
|
|
371
|
+
await this.client.messageFlagsAdd(String(uid), ["\\Deleted"], { uid: true });
|
|
283
372
|
} finally {
|
|
284
373
|
lock.release();
|
|
285
374
|
}
|
|
@@ -344,12 +433,28 @@ var MailReceiver = class {
|
|
|
344
433
|
lock.release();
|
|
345
434
|
}
|
|
346
435
|
}
|
|
347
|
-
/**
|
|
436
|
+
/**
|
|
437
|
+
* Batch move multiple messages to another folder.
|
|
438
|
+
*
|
|
439
|
+
* Same safety model as `moveMessage`: prefers the IMAP MOVE
|
|
440
|
+
* extension (atomic, scoped per UID); falls back to
|
|
441
|
+
* COPY + STORE \Deleted with NO mailbox-wide EXPUNGE so an
|
|
442
|
+
* existing `\Deleted` flag on an unrelated message can't
|
|
443
|
+
* be amplified into a full inbox wipe.
|
|
444
|
+
*/
|
|
348
445
|
async batchMove(uids, fromMailbox, toMailbox) {
|
|
349
446
|
if (uids.length === 0) return;
|
|
447
|
+
const range = uids.join(",");
|
|
350
448
|
const lock = await this.client.getMailboxLock(fromMailbox);
|
|
351
449
|
try {
|
|
352
|
-
|
|
450
|
+
const caps = this.client.capabilities;
|
|
451
|
+
const hasMove = caps && (Array.isArray(caps) ? caps.includes("MOVE") : caps.has("MOVE"));
|
|
452
|
+
if (hasMove) {
|
|
453
|
+
await this.client.messageMove(range, toMailbox, { uid: true });
|
|
454
|
+
return;
|
|
455
|
+
}
|
|
456
|
+
await this.client.messageCopy(range, toMailbox, { uid: true });
|
|
457
|
+
await this.client.messageFlagsAdd(range, ["\\Deleted"], { uid: true });
|
|
353
458
|
} finally {
|
|
354
459
|
lock.release();
|
|
355
460
|
}
|