proton-mail-bridge-client 2.1.20 → 2.1.21

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.js CHANGED
@@ -154,6 +154,7 @@ const TOOLS = [
154
154
  type: "object",
155
155
  properties: {
156
156
  emailId: { type: "string", description: "Original email id." },
157
+ undoWindowSeconds: { type: "number", description: "Override PROTONMAIL_SEND_DELAY_SECONDS for this one send: queue it for this many seconds (cancelable via cancel_send) instead of the server's configured default. 0 sends immediately even if the server has a default window configured. Same caveat as the server default: only fires while this server process stays running." },
157
158
  body: { type: "string", description: "Reply body to prepend (plain text). Required unless markdownBody is provided." },
158
159
  markdownBody: { type: "string", description: "Reply body in Markdown. Rendered to HTML with body as plain-text fallback; takes precedence over body+isHtml." },
159
160
  replyAll: { type: "boolean", description: "Reply to all original recipients.", default: false },
@@ -194,6 +195,7 @@ const TOOLS = [
194
195
  type: "object",
195
196
  properties: {
196
197
  emailId: { type: "string", description: "Original email id." },
198
+ undoWindowSeconds: { type: "number", description: "Override PROTONMAIL_SEND_DELAY_SECONDS for this one send: queue it for this many seconds (cancelable via cancel_send) instead of the server's configured default. 0 sends immediately even if the server has a default window configured. Same caveat as the server default: only fires while this server process stays running." },
197
199
  body: { type: "string", description: "Reply body to prepend (plain text). Required unless markdownBody is provided." },
198
200
  markdownBody: { type: "string", description: "Reply body in Markdown. Rendered to HTML with body as plain-text fallback; takes precedence over body+isHtml." },
199
201
  isHtml: { type: "boolean", description: "Send body as HTML (ignored when markdownBody is provided).", default: false },
@@ -233,6 +235,7 @@ const TOOLS = [
233
235
  type: "object",
234
236
  properties: {
235
237
  emailId: { type: "string", description: "Original email id." },
238
+ undoWindowSeconds: { type: "number", description: "Override PROTONMAIL_SEND_DELAY_SECONDS for this one send: queue it for this many seconds (cancelable via cancel_send) instead of the server's configured default. 0 sends immediately even if the server has a default window configured. Same caveat as the server default: only fires while this server process stays running." },
236
239
  to: { type: "string", description: "Forward recipient list, comma-separated." },
237
240
  body: { type: "string", description: "Optional message before the forwarded content (plain text)." },
238
241
  markdownBody: { type: "string", description: "Optional introductory note in Markdown. Rendered to HTML with body as plain-text fallback; takes precedence over body+isHtml." },
@@ -3441,6 +3444,33 @@ export function createServer(config, options = {}) {
3441
3444
  function responseSlug(bundle) {
3442
3445
  return bundle.account.slug === accountManager.primary().account.slug ? undefined : bundle.account.slug;
3443
3446
  }
3447
+ // Undo-send window for one send: the server default (PROTONMAIL_SEND_DELAY_SECONDS),
3448
+ // overridable per call with undoWindowSeconds (0 = send immediately). Shared by
3449
+ // send_email, reply_to_email, reply_all_email and forward_email.
3450
+ function resolveUndoWindowSeconds(args) {
3451
+ if (args.undoWindowSeconds === undefined) {
3452
+ return config.runtime.sendDelaySeconds ?? 0;
3453
+ }
3454
+ const requested = Number(args.undoWindowSeconds);
3455
+ if (!Number.isInteger(requested) || requested < 0 || requested > 300) {
3456
+ throw new McpError(ErrorCode.InvalidParams, "undoWindowSeconds must be an integer between 0 and 300.");
3457
+ }
3458
+ return requested;
3459
+ }
3460
+ // Queues a send on the SENDING account's own delivery queue (so it fires through that
3461
+ // account's SMTP) and returns the tool result. The id carries that account's slug so
3462
+ // cancel_send/list_scheduled_sends can find it again.
3463
+ async function queueUndoSend(bundle, payload, undoWindowSeconds, toolName, toolArgs, extra = {}) {
3464
+ const sendAt = new Date(Date.now() + undoWindowSeconds * 1000).toISOString();
3465
+ const queued = await withAudit(auditService, toolName, toolArgs, async () => bundle.deliveryQueueService.enqueue(payload, sendAt, "undo_send"));
3466
+ return createTextResult({
3467
+ queued: true,
3468
+ id: withAccountPrefix(responseSlug(bundle), queued.id),
3469
+ sendAt: queued.sendAt,
3470
+ ...extra,
3471
+ note: `Not sent yet — will send in ~${undoWindowSeconds}s unless canceled with cancel_send. This server must stay running for the send to fire; if it's restarted before sendAt, the send fires on next startup instead.`,
3472
+ });
3473
+ }
3444
3474
  // Alias for responseSlug — tagAccountIds (added alongside the digest/follow-up
3445
3475
  // multi-account fan-out) was written against this name; kept as a thin alias rather
3446
3476
  // than rewriting every call site to the other name.
@@ -3706,14 +3736,7 @@ export function createServer(config, options = {}) {
3706
3736
  // immediately, cancelable via cancel_send until the window elapses.
3707
3737
  // undoWindowSeconds overrides the server default for this one send —
3708
3738
  // including forcing 0 (send immediately) when a default is configured.
3709
- let undoWindowSeconds = config.runtime.sendDelaySeconds;
3710
- if (args.undoWindowSeconds !== undefined) {
3711
- const requested = Number(args.undoWindowSeconds);
3712
- if (!Number.isInteger(requested) || requested < 0 || requested > 300) {
3713
- throw new McpError(ErrorCode.InvalidParams, "undoWindowSeconds must be an integer between 0 and 300.");
3714
- }
3715
- undoWindowSeconds = requested;
3716
- }
3739
+ const undoWindowSeconds = resolveUndoWindowSeconds(args);
3717
3740
  // A `from` matching a configured additional account routes through that
3718
3741
  // account's own SMTP connection — required under Bridge's Split Addresses
3719
3742
  // feature, where each address is its own separate IMAP/SMTP login, not just
@@ -3928,7 +3951,7 @@ export function createServer(config, options = {}) {
3928
3951
  if (dryRunReply) {
3929
3952
  return createTextResult({ dryRun: true, wouldSendTo: { to, cc, bcc: extraBcc }, subject: prefixedSubject(detail.subject, "Re:"), note: "No email was sent." }, false, [emailSource({ ...detail, id: prefixedIdFor(readBundleReply, detail.id) })]);
3930
3953
  }
3931
- const result = await withAudit(auditService, name, args, async () => sendBundleForReply.smtpService.sendEmail({
3954
+ const replyPayload = {
3932
3955
  to,
3933
3956
  cc,
3934
3957
  bcc: extraBcc,
@@ -3944,7 +3967,12 @@ export function createServer(config, options = {}) {
3944
3967
  attachments,
3945
3968
  // Already applied above, before quote-wrapping.
3946
3969
  appendSignature: false,
3947
- }));
3970
+ };
3971
+ const undoWindowreply = resolveUndoWindowSeconds(args);
3972
+ if (undoWindowreply > 0) {
3973
+ return queueUndoSend(sendBundleForReply, replyPayload, undoWindowreply, name, args, { repliedTo: prefixedIdFor(readBundleReply, detail.id), to, cc });
3974
+ }
3975
+ const result = await withAudit(auditService, name, args, async () => sendBundleForReply.smtpService.sendEmail(replyPayload));
3948
3976
  let sentCopyTokenReply = "[sent-copy:unverified]";
3949
3977
  try {
3950
3978
  const verifyMsgId = result.messageId;
@@ -4017,7 +4045,7 @@ export function createServer(config, options = {}) {
4017
4045
  const replyHtmlBodyRa = includeQuoteRa && signedReplyRa.htmlBody !== undefined
4018
4046
  ? buildReplyHtml(detailRa, signedReplyRa.htmlBody)
4019
4047
  : signedReplyRa.htmlBody;
4020
- const resultRa = await withAudit(auditService, name, args, async () => sendBundleForRa.smtpService.sendEmail({
4048
+ const replyAllPayload = {
4021
4049
  to: toRa,
4022
4050
  cc: ccRa,
4023
4051
  bcc: extraBccRa,
@@ -4033,7 +4061,12 @@ export function createServer(config, options = {}) {
4033
4061
  attachments: attachmentsRa,
4034
4062
  // Already applied above, before quote-wrapping.
4035
4063
  appendSignature: false,
4036
- }));
4064
+ };
4065
+ const undoWindowreplyAll = resolveUndoWindowSeconds(args);
4066
+ if (undoWindowreplyAll > 0) {
4067
+ return queueUndoSend(sendBundleForRa, replyAllPayload, undoWindowreplyAll, name, args, { repliedTo: prefixedIdFor(readBundleRa, detailRa.id), to: toRa, cc: ccRa });
4068
+ }
4069
+ const resultRa = await withAudit(auditService, name, args, async () => sendBundleForRa.smtpService.sendEmail(replyAllPayload));
4037
4070
  let sentCopyTokenRa = "[sent-copy:unverified]";
4038
4071
  try {
4039
4072
  const verifyMsgId = resultRa.messageId;
@@ -4130,7 +4163,7 @@ export function createServer(config, options = {}) {
4130
4163
  const forwardHtmlBody = signedFwd.htmlBody !== undefined
4131
4164
  ? buildForwardHtml(detail, signedFwd.htmlBody)
4132
4165
  : signedFwd.htmlBody;
4133
- const result = await withAudit(auditService, name, args, async () => sendBundleForFwd.smtpService.sendEmail({
4166
+ const forwardPayload = {
4134
4167
  to,
4135
4168
  cc,
4136
4169
  bcc,
@@ -4144,7 +4177,12 @@ export function createServer(config, options = {}) {
4144
4177
  attachments: fwdAttachments,
4145
4178
  // Already applied above, before quote-wrapping.
4146
4179
  appendSignature: false,
4147
- }));
4180
+ };
4181
+ const undoWindowforward = resolveUndoWindowSeconds(args);
4182
+ if (undoWindowforward > 0) {
4183
+ return queueUndoSend(sendBundleForFwd, forwardPayload, undoWindowforward, name, args, { forwardedMessage: prefixedIdFor(readBundleFwd, detail.id), to, cc });
4184
+ }
4185
+ const result = await withAudit(auditService, name, args, async () => sendBundleForFwd.smtpService.sendEmail(forwardPayload));
4148
4186
  let sentCopyTokenFwd = "[sent-copy:unverified]";
4149
4187
  try {
4150
4188
  const verifyMsgId = result.messageId;