proton-mail-bridge-client 2.0.0 → 2.0.2
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 +2 -2
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +6 -2
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +83 -37
- package/dist/index.js.map +1 -1
- package/dist/services/delivery-queue-service.d.ts +5 -0
- package/dist/services/delivery-queue-service.d.ts.map +1 -1
- package/dist/services/delivery-queue-service.js +110 -15
- package/dist/services/delivery-queue-service.js.map +1 -1
- package/dist/services/draft-store-service.d.ts +3 -0
- package/dist/services/draft-store-service.d.ts.map +1 -1
- package/dist/services/draft-store-service.js +64 -1
- package/dist/services/draft-store-service.js.map +1 -1
- package/dist/services/local-index-service.d.ts +5 -0
- package/dist/services/local-index-service.d.ts.map +1 -1
- package/dist/services/local-index-service.js +191 -18
- package/dist/services/local-index-service.js.map +1 -1
- package/dist/services/simple-imap-service.d.ts +7 -1
- package/dist/services/simple-imap-service.d.ts.map +1 -1
- package/dist/services/simple-imap-service.js +163 -23
- package/dist/services/simple-imap-service.js.map +1 -1
- package/dist/services/snooze-service.d.ts +3 -0
- package/dist/services/snooze-service.d.ts.map +1 -1
- package/dist/services/snooze-service.js +103 -7
- package/dist/services/snooze-service.js.map +1 -1
- package/dist/services/template-service.d.ts +1 -0
- package/dist/services/template-service.d.ts.map +1 -1
- package/dist/services/template-service.js +8 -0
- package/dist/services/template-service.js.map +1 -1
- package/dist/types/index.d.ts +7 -1
- package/dist/types/index.d.ts.map +1 -1
- package/dist/utils/account-identity.d.ts +8 -0
- package/dist/utils/account-identity.d.ts.map +1 -0
- package/dist/utils/account-identity.js +78 -0
- package/dist/utils/account-identity.js.map +1 -0
- package/dist/utils/file-lock.d.ts +1 -0
- package/dist/utils/file-lock.d.ts.map +1 -1
- package/dist/utils/file-lock.js +24 -4
- package/dist/utils/file-lock.js.map +1 -1
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -135,13 +135,14 @@ const TOOLS = [
|
|
|
135
135
|
},
|
|
136
136
|
{
|
|
137
137
|
name: "send_test_email",
|
|
138
|
-
description: "Send a minimal diagnostic email to confirm Proton Bridge SMTP credentials and connectivity. Use before relying on send_email in a new environment. Prefer get_connection_status for a connectivity check that does not actually send mail. Returns transport debug info and delivery status.",
|
|
138
|
+
description: "Send a minimal diagnostic email to confirm Proton Bridge SMTP credentials and connectivity. Use before relying on send_email in a new environment. Prefer get_connection_status for a connectivity check that does not actually send mail. Returns transport debug info and delivery status. Requires PROTONMAIL_ALLOW_SEND, and confirmed:true when PROTONMAIL_CONFIRM_DESTRUCTIVE is enabled; subject to PROTONMAIL_RESTRICT_OUTBOUND_TO_SELF like every other send path.",
|
|
139
139
|
annotations: { destructiveHint: true },
|
|
140
140
|
inputSchema: {
|
|
141
141
|
type: "object",
|
|
142
142
|
properties: {
|
|
143
143
|
to: { type: "string", description: "Recipient email address." },
|
|
144
144
|
customMessage: { type: "string", description: "Optional custom test body." },
|
|
145
|
+
confirmed: { type: "boolean", description: "Set to true to confirm this send when PROTONMAIL_CONFIRM_DESTRUCTIVE is enabled." },
|
|
145
146
|
},
|
|
146
147
|
required: ["to"],
|
|
147
148
|
},
|
|
@@ -2776,6 +2777,9 @@ export function createServer(config, options = {}) {
|
|
|
2776
2777
|
const draftStore = new DraftStoreService(config, logger);
|
|
2777
2778
|
const backgroundSyncService = new BackgroundSyncService(config, imapService, localIndexService, logger);
|
|
2778
2779
|
const deliveryQueueService = new DeliveryQueueService(config, smtpService, logger);
|
|
2780
|
+
// Lets a fired scheduled_send close the loop back to its source draft —
|
|
2781
|
+
// see DeliveryQueueService.checkDue()'s draftStore usage.
|
|
2782
|
+
deliveryQueueService.setDraftStore(draftStore);
|
|
2779
2783
|
const snoozeService = new SnoozeService(config, imapService, logger);
|
|
2780
2784
|
const templateService = new TemplateService(config, logger);
|
|
2781
2785
|
if (options.startBackgroundSync) {
|
|
@@ -3075,11 +3079,13 @@ export function createServer(config, options = {}) {
|
|
|
3075
3079
|
});
|
|
3076
3080
|
}
|
|
3077
3081
|
case "send_test_email": {
|
|
3078
|
-
ensureSendAllowed(config.runtime);
|
|
3079
3082
|
const to = requireString(args, "to");
|
|
3080
3083
|
if (!isValidEmail(to)) {
|
|
3081
3084
|
throw new McpError(ErrorCode.InvalidParams, "to must be a valid email address.");
|
|
3082
3085
|
}
|
|
3086
|
+
ensureDestructiveConfirmed(config.runtime, normalizeBoolean(args.confirmed, false), `Send test email to ${to}`);
|
|
3087
|
+
ensureSendAllowed(config.runtime);
|
|
3088
|
+
ensureOutboundRecipientsAllowed(config.runtime, config.smtp.username, [to]);
|
|
3083
3089
|
const result = await withAudit(auditService, name, args, async () => smtpService.sendTestEmail(to, optionalString(args, "customMessage")));
|
|
3084
3090
|
return createTextResult({
|
|
3085
3091
|
messageId: result.messageId,
|
|
@@ -3676,24 +3682,41 @@ export function createServer(config, options = {}) {
|
|
|
3676
3682
|
note: "No email was sent.",
|
|
3677
3683
|
}, false, [draftSource(draft)]);
|
|
3678
3684
|
}
|
|
3679
|
-
|
|
3680
|
-
|
|
3681
|
-
|
|
3682
|
-
|
|
3683
|
-
|
|
3684
|
-
|
|
3685
|
-
|
|
3686
|
-
|
|
3687
|
-
|
|
3688
|
-
|
|
3689
|
-
|
|
3690
|
-
|
|
3691
|
-
|
|
3692
|
-
|
|
3693
|
-
|
|
3694
|
-
|
|
3695
|
-
|
|
3696
|
-
|
|
3685
|
+
// Atomic claim: the status/pending-scheduled checks above are read
|
|
3686
|
+
// then acted on non-atomically, so two concurrent send_draft calls
|
|
3687
|
+
// (or a scheduled send firing at the same instant) could both pass
|
|
3688
|
+
// them. claimForSending() re-reads status under the draft store's
|
|
3689
|
+
// lock and flips "draft" -> "sending" in that same critical
|
|
3690
|
+
// section — only the call that wins the claim proceeds to SMTP.
|
|
3691
|
+
await draftStore.claimForSending(draft.id);
|
|
3692
|
+
let result;
|
|
3693
|
+
try {
|
|
3694
|
+
result = await withAudit(auditService, name, args, async () => smtpService.sendEmail({
|
|
3695
|
+
to: draft.to,
|
|
3696
|
+
cc: draft.cc,
|
|
3697
|
+
bcc: draft.bcc,
|
|
3698
|
+
subject: draft.subject,
|
|
3699
|
+
body: draft.body,
|
|
3700
|
+
isHtml: draft.isHtml,
|
|
3701
|
+
priority: draft.priority,
|
|
3702
|
+
replyTo: draft.replyTo,
|
|
3703
|
+
inReplyTo: draft.inReplyTo,
|
|
3704
|
+
references: draft.references,
|
|
3705
|
+
attachments: draft.attachments,
|
|
3706
|
+
// Draft content is already finalized and reviewed — appending a
|
|
3707
|
+
// signature invisibly at send time would change what the user
|
|
3708
|
+
// saw and approved. If a signature is wanted, it belongs in the
|
|
3709
|
+
// draft body itself.
|
|
3710
|
+
appendSignature: false,
|
|
3711
|
+
}));
|
|
3712
|
+
}
|
|
3713
|
+
catch (error) {
|
|
3714
|
+
// Mirrors SnoozeService.wake()'s catch handler: revert the claim
|
|
3715
|
+
// so the draft is retryable instead of stuck in "sending"
|
|
3716
|
+
// forever because SMTP failed.
|
|
3717
|
+
await draftStore.revertSending(draft.id);
|
|
3718
|
+
throw error;
|
|
3719
|
+
}
|
|
3697
3720
|
let sentDraft = await draftStore.markSent(draft.id, {
|
|
3698
3721
|
messageId: result.messageId,
|
|
3699
3722
|
accepted: result.accepted,
|
|
@@ -4002,7 +4025,7 @@ export function createServer(config, options = {}) {
|
|
|
4002
4025
|
return createTextResult(result);
|
|
4003
4026
|
}
|
|
4004
4027
|
case "bulk_move": {
|
|
4005
|
-
|
|
4028
|
+
ensureEmailActionAllowed(config.runtime, "move");
|
|
4006
4029
|
const emailIds = Array.isArray(args.emailIds)
|
|
4007
4030
|
? args.emailIds.map(String) : undefined;
|
|
4008
4031
|
const match = args.match && typeof args.match === "object"
|
|
@@ -4055,15 +4078,21 @@ export function createServer(config, options = {}) {
|
|
|
4055
4078
|
const folder = optionalString(args, "folder") ?? "INBOX";
|
|
4056
4079
|
const max = getBulkMaxBatchSize(args);
|
|
4057
4080
|
const notFoundEmailIds = getBulkNotFoundEmailIds(emailIds, folder);
|
|
4058
|
-
|
|
4059
|
-
|
|
4060
|
-
|
|
4061
|
-
|
|
4062
|
-
|
|
4063
|
-
|
|
4064
|
-
});
|
|
4065
|
-
ensureBulkBatchSize(preview.total, max);
|
|
4081
|
+
// Resolve the match/emailIds set exactly once and reuse it for both
|
|
4082
|
+
// the preview and the real run — see resolveUidsForBulkOp's
|
|
4083
|
+
// comment for why re-resolving `match` a second time (the old
|
|
4084
|
+
// dry-run-then-real-run pattern) could silently exceed maxBatchSize.
|
|
4085
|
+
const uids = await imapService.resolveUidsForBulkOp(folder, emailIds, match);
|
|
4086
|
+
ensureBulkBatchSize(uids.length, max);
|
|
4066
4087
|
if (normalizeBoolean(args.dryRun, false)) {
|
|
4088
|
+
const preview = await imapService.bulkDelete({
|
|
4089
|
+
emailIds,
|
|
4090
|
+
match,
|
|
4091
|
+
folder,
|
|
4092
|
+
permanent,
|
|
4093
|
+
resolvedUids: uids,
|
|
4094
|
+
dryRun: true,
|
|
4095
|
+
});
|
|
4067
4096
|
return createTextResult(withBulkNotFound(preview, notFoundEmailIds));
|
|
4068
4097
|
}
|
|
4069
4098
|
const result = await withAudit(auditService, name, args, () => imapService.bulkDelete({
|
|
@@ -4071,6 +4100,7 @@ export function createServer(config, options = {}) {
|
|
|
4071
4100
|
match,
|
|
4072
4101
|
folder,
|
|
4073
4102
|
permanent,
|
|
4103
|
+
resolvedUids: uids,
|
|
4074
4104
|
dryRun: false,
|
|
4075
4105
|
}));
|
|
4076
4106
|
return createTextResult(withBulkNotFound(result, notFoundEmailIds));
|
|
@@ -4094,12 +4124,17 @@ export function createServer(config, options = {}) {
|
|
|
4094
4124
|
const folder = optionalString(args, "folder") ?? "INBOX";
|
|
4095
4125
|
const max = getBulkMaxBatchSize(args);
|
|
4096
4126
|
const notFoundEmailIds = getBulkNotFoundEmailIds(emailIds, folder);
|
|
4097
|
-
|
|
4098
|
-
|
|
4127
|
+
// Resolve the match/emailIds set exactly once and reuse it for both
|
|
4128
|
+
// the preview and the real run — see resolveUidsForBulkOp's
|
|
4129
|
+
// comment for why re-resolving `match` a second time (the old
|
|
4130
|
+
// dry-run-then-real-run pattern) could silently exceed maxBatchSize.
|
|
4131
|
+
const uids = await imapService.resolveUidsForBulkOp(folder, emailIds, match);
|
|
4132
|
+
ensureBulkBatchSize(uids.length, max);
|
|
4099
4133
|
if (normalizeBoolean(args.dryRun, false)) {
|
|
4134
|
+
const preview = await imapService.bulkUpdateFlags({ emailIds, match, folder, flagsToAdd, flagsToRemove, resolvedUids: uids, dryRun: true });
|
|
4100
4135
|
return createTextResult(withBulkNotFound(preview, notFoundEmailIds));
|
|
4101
4136
|
}
|
|
4102
|
-
const result = await withAudit(auditService, name, args, () => imapService.bulkUpdateFlags({ emailIds, match, folder, flagsToAdd, flagsToRemove, dryRun: false }));
|
|
4137
|
+
const result = await withAudit(auditService, name, args, () => imapService.bulkUpdateFlags({ emailIds, match, folder, flagsToAdd, flagsToRemove, resolvedUids: uids, dryRun: false }));
|
|
4103
4138
|
return createTextResult(withBulkNotFound(result, notFoundEmailIds));
|
|
4104
4139
|
}
|
|
4105
4140
|
case "bulk_update_labels": {
|
|
@@ -4119,12 +4154,17 @@ export function createServer(config, options = {}) {
|
|
|
4119
4154
|
const folder = optionalString(args, "folder") ?? "INBOX";
|
|
4120
4155
|
const max = getBulkMaxBatchSize(args);
|
|
4121
4156
|
const notFoundEmailIds = getBulkNotFoundEmailIds(emailIds, folder);
|
|
4122
|
-
|
|
4123
|
-
|
|
4157
|
+
// Resolve the match/emailIds set exactly once and reuse it for both
|
|
4158
|
+
// the preview and the real run — see resolveUidsForBulkOp's
|
|
4159
|
+
// comment for why re-resolving `match` a second time (the old
|
|
4160
|
+
// dry-run-then-real-run pattern) could silently exceed maxBatchSize.
|
|
4161
|
+
const uids = await imapService.resolveUidsForBulkOp(folder, emailIds, match);
|
|
4162
|
+
ensureBulkBatchSize(uids.length, max);
|
|
4124
4163
|
if (normalizeBoolean(args.dryRun, false)) {
|
|
4164
|
+
const preview = await imapService.bulkUpdateLabels({ emailIds, match, folder, labelsToAdd, labelsToRemove, resolvedUids: uids, dryRun: true });
|
|
4125
4165
|
return createTextResult(withBulkNotFound(preview, notFoundEmailIds));
|
|
4126
4166
|
}
|
|
4127
|
-
const result = await withAudit(auditService, name, args, () => imapService.bulkUpdateLabels({ emailIds, match, folder, labelsToAdd, labelsToRemove, dryRun: false }));
|
|
4167
|
+
const result = await withAudit(auditService, name, args, () => imapService.bulkUpdateLabels({ emailIds, match, folder, labelsToAdd, labelsToRemove, resolvedUids: uids, dryRun: false }));
|
|
4128
4168
|
return createTextResult(withBulkNotFound(result, notFoundEmailIds));
|
|
4129
4169
|
}
|
|
4130
4170
|
case "top_senders": {
|
|
@@ -4230,7 +4270,7 @@ export function createServer(config, options = {}) {
|
|
|
4230
4270
|
return createTextResult(await withAudit(auditService, name, args, async () => imapService.starEmail(requireString(args, "emailId"), normalizeBoolean(args.isStarred, true))));
|
|
4231
4271
|
case "move_email":
|
|
4232
4272
|
{
|
|
4233
|
-
|
|
4273
|
+
ensureEmailActionAllowed(config.runtime, "move");
|
|
4234
4274
|
const emailId = requireString(args, "emailId");
|
|
4235
4275
|
const targetFolder = requireString(args, "targetFolder");
|
|
4236
4276
|
const result = await withAudit(auditService, name, args, async () => {
|
|
@@ -4379,6 +4419,9 @@ export function createServer(config, options = {}) {
|
|
|
4379
4419
|
}
|
|
4380
4420
|
const action = requireEmailAction(args);
|
|
4381
4421
|
ensureEmailActionAllowed(config.runtime, action);
|
|
4422
|
+
if (action === "delete") {
|
|
4423
|
+
ensureDestructiveConfirmed(config.runtime, normalizeBoolean(args.confirmed, false), `Permanently delete ${emailIds.length} email(s) (cannot be recovered)`);
|
|
4424
|
+
}
|
|
4382
4425
|
const result = await withAudit(auditService, name, args, async () => applyBatchEmailAction(imapService, [], {
|
|
4383
4426
|
emailIds,
|
|
4384
4427
|
action,
|
|
@@ -4606,7 +4649,7 @@ export function createServer(config, options = {}) {
|
|
|
4606
4649
|
},
|
|
4607
4650
|
drafts: {
|
|
4608
4651
|
total: drafts.length,
|
|
4609
|
-
active: drafts.filter((draft) => draft.status
|
|
4652
|
+
active: drafts.filter((draft) => draft.status !== "sent").length,
|
|
4610
4653
|
remoteSynced: drafts.filter((draft) => draft.remoteSyncState === "synced").length,
|
|
4611
4654
|
syncFailed: drafts.filter((draft) => draft.remoteSyncState === "sync_failed").length,
|
|
4612
4655
|
},
|
|
@@ -4956,6 +4999,9 @@ export function createServer(config, options = {}) {
|
|
|
4956
4999
|
const emailIds = [...new Set(thread.messages
|
|
4957
5000
|
.filter((message) => !unreadOnly || !message.isRead)
|
|
4958
5001
|
.map((message) => message.primaryEmailId))];
|
|
5002
|
+
if (action === "delete") {
|
|
5003
|
+
ensureDestructiveConfirmed(config.runtime, normalizeBoolean(args.confirmed, false), `Permanently delete ${emailIds.length} email(s) in thread (cannot be recovered)`);
|
|
5004
|
+
}
|
|
4959
5005
|
const result = await withAudit(auditService, name, args, async () => applyBatchEmailAction(imapService, [], {
|
|
4960
5006
|
emailIds,
|
|
4961
5007
|
action,
|