@push.rocks/smartmta 14.1.0 → 14.2.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.
Files changed (32) hide show
  1. package/changelog.md +18 -2
  2. package/dist_rust/mailer-bin_linux_amd64_musl +0 -0
  3. package/dist_rust/mailer-bin_linux_amd64_musl.tsrust-build.json +4 -4
  4. package/dist_rust/mailer-bin_linux_arm64_musl +0 -0
  5. package/dist_rust/mailer-bin_linux_arm64_musl.tsrust-build.json +4 -4
  6. package/dist_ts/00_commitinfo_data.js +1 -1
  7. package/dist_ts/mail/core/classes.email.d.ts +24 -3
  8. package/dist_ts/mail/core/classes.email.js +55 -6
  9. package/dist_ts/mail/core/functions.mailbox.d.ts +8 -0
  10. package/dist_ts/mail/core/functions.mailbox.js +21 -0
  11. package/dist_ts/mail/delivery/classes.delivery.queue.js +41 -4
  12. package/dist_ts/mail/delivery/classes.delivery.system.d.ts +1 -0
  13. package/dist_ts/mail/delivery/classes.delivery.system.js +4 -2
  14. package/dist_ts/mail/delivery/functions.raw-email.js +4 -5
  15. package/dist_ts/mail/routing/classes.email.action.executor.js +3 -3
  16. package/dist_ts/mail/routing/classes.unified.email.server.d.ts +7 -1
  17. package/dist_ts/mail/routing/classes.unified.email.server.js +32 -14
  18. package/dist_ts/security/classes.contentscanner.js +3 -3
  19. package/dist_ts/security/classes.rustsecuritybridge.d.ts +3 -0
  20. package/dist_ts/security/classes.rustsecuritybridge.js +1 -1
  21. package/package.json +1 -1
  22. package/readme.md +13 -0
  23. package/ts/00_commitinfo_data.ts +1 -1
  24. package/ts/mail/core/classes.email.ts +57 -6
  25. package/ts/mail/core/functions.mailbox.ts +21 -0
  26. package/ts/mail/delivery/classes.delivery.queue.ts +48 -2
  27. package/ts/mail/delivery/classes.delivery.system.ts +3 -1
  28. package/ts/mail/delivery/functions.raw-email.ts +3 -4
  29. package/ts/mail/routing/classes.email.action.executor.ts +2 -2
  30. package/ts/mail/routing/classes.unified.email.server.ts +32 -13
  31. package/ts/security/classes.contentscanner.ts +2 -2
  32. package/ts/security/classes.rustsecuritybridge.ts +3 -0
@@ -1057,12 +1057,14 @@ export class UnifiedEmailServer extends EventEmitter {
1057
1057
  ? await this.resolveOutboundDkim(options.dkimDomain, options.dkimSelector || 'default')
1058
1058
  : undefined;
1059
1059
 
1060
- // Serialize the Email to the outbound format
1060
+ // Serialize the Email to the outbound format. RCPT TO is the one accessor's
1061
+ // list, which mailer-bin sends verbatim; to and cc are only written.
1061
1062
  const outboundEmail: IOutboundEmail = {
1062
1063
  from: email.from,
1063
1064
  to: email.to,
1064
1065
  cc: email.cc || [],
1065
1066
  bcc: email.bcc || [],
1067
+ envelopeTo: email.getAllRecipients(),
1066
1068
  subject: email.subject || '',
1067
1069
  text: email.text || '',
1068
1070
  html: email.html || undefined,
@@ -2595,7 +2597,7 @@ export class UnifiedEmailServer extends EventEmitter {
2595
2597
  }
2596
2598
  }
2597
2599
 
2598
- /** Build a route-evaluation view containing only the current SMTP envelope recipient. */
2600
+ /** A copy of the email whose envelope is only the given SMTP envelope recipient. */
2599
2601
  private cloneEmailForRoutingRecipient(email: Email, recipient: string): Email {
2600
2602
  return new Email({
2601
2603
  from: email.from,
@@ -2615,7 +2617,8 @@ export class UnifiedEmailServer extends EventEmitter {
2615
2617
  variables: structuredClone(email.variables),
2616
2618
  })
2617
2619
  .setMessageId(email.getMessageId())
2618
- .setEnvelopeFrom(email.getEnvelopeFrom());
2620
+ .setEnvelopeFrom(email.getEnvelopeFrom())
2621
+ .setEnvelopeTo([recipient]);
2619
2622
  }
2620
2623
 
2621
2624
  private scopeSessionForRoutingRecipient(
@@ -3514,6 +3517,19 @@ export class UnifiedEmailServer extends EventEmitter {
3514
3517
  }
3515
3518
  }
3516
3519
 
3520
+ /**
3521
+ * The envelope recipients of a received message's session. A received
3522
+ * message is delivered to its envelope alone, so an empty one is refused;
3523
+ * its To and Cc fields never stand in for it.
3524
+ */
3525
+ private envelopeRecipientsOf(session: IExtendedSmtpSession): string[] {
3526
+ const recipients = (session.envelope?.rcptTo || []).map((recipient) => recipient.address);
3527
+ if (recipients.length === 0) {
3528
+ throw new Error('A received message needs at least one envelope recipient');
3529
+ }
3530
+ return recipients;
3531
+ }
3532
+
3517
3533
  /**
3518
3534
  * Builds the Email of stored message bytes (the replay paths
3519
3535
  * dispatchAcceptedEnvelope and processEmailByMode(Buffer)) with the parser
@@ -3534,7 +3550,7 @@ export class UnifiedEmailServer extends EventEmitter {
3534
3550
  /** The Email of a message the Rust parser read (docs/cleanroom/mailer-mime.md section 2.2). */
3535
3551
  private createEmailFromParsed(parsed: IBridgeParsedMessage, session: IExtendedSmtpSession): Email {
3536
3552
  const headerFromMailboxes = this.fromFieldAddresses(parsed).flat();
3537
- const envelopeRecipients = session.envelope.rcptTo.map(recipient => recipient.address);
3553
+ const envelopeRecipients = this.envelopeRecipientsOf(session);
3538
3554
  const parsedHeaders = new Map<string, string>();
3539
3555
  for (const header of parsed.headers) {
3540
3556
  if (this.isStandardParsedHeader(header.key)) {
@@ -3550,9 +3566,7 @@ export class UnifiedEmailServer extends EventEmitter {
3550
3566
  return new Email({
3551
3567
  // The first mailbox of the From fields in message order, groups flattened
3552
3568
  from: headerFromMailboxes[0] || session.envelope.mailFrom.address,
3553
- to: envelopeRecipients.length > 0
3554
- ? envelopeRecipients
3555
- : parsed.to.map(recipient => recipient.email),
3569
+ to: envelopeRecipients,
3556
3570
  cc: parsed.cc.map(recipient => recipient.email),
3557
3571
  bcc: [],
3558
3572
  subject: parsed.subject || '',
@@ -3565,7 +3579,7 @@ export class UnifiedEmailServer extends EventEmitter {
3565
3579
  contentType: attachment.contentType,
3566
3580
  ...(attachment.contentId ? { contentId: attachment.contentId } : {}),
3567
3581
  })),
3568
- });
3582
+ }).setEnvelopeTo(envelopeRecipients);
3569
3583
  }
3570
3584
 
3571
3585
  private isStandardParsedHeader(headerName: string): boolean {
@@ -3971,6 +3985,8 @@ export class UnifiedEmailServer extends EventEmitter {
3971
3985
  } else {
3972
3986
  email = emailData;
3973
3987
  }
3988
+ // The session's envelope alone decides who receives the message.
3989
+ email.setEnvelopeTo(this.envelopeRecipientsOf(session));
3974
3990
 
3975
3991
  // Run inbound security verification (DKIM/SPF/DMARC) via Rust bridge
3976
3992
  if (session.remoteAddress && session.remoteAddress !== '127.0.0.1') {
@@ -4281,23 +4297,24 @@ export class UnifiedEmailServer extends EventEmitter {
4281
4297
  }
4282
4298
  ): Promise<string> {
4283
4299
  this.assertAcceptingOutboundWork();
4284
- logger.log('info', `Sending email: ${email.subject} to ${email.to.join(', ')}`);
4300
+ const recipients = email.getAllRecipients();
4301
+ logger.log('info', `Sending email: ${email.subject} to ${recipients.join(', ')}`);
4285
4302
 
4286
4303
  try {
4287
4304
  if (!email.from) {
4288
4305
  throw new Error('Email must have a sender address');
4289
4306
  }
4290
4307
 
4291
- if (!email.to || email.to.length === 0) {
4308
+ if (recipients.length === 0) {
4292
4309
  throw new Error('Email must have at least one recipient');
4293
4310
  }
4294
4311
 
4295
4312
  // Check if any recipients are on the suppression list
4296
4313
  if (!options?.skipSuppressionCheck) {
4297
- const suppressedRecipients = email.to.filter(recipient => this.isEmailSuppressed(recipient));
4314
+ const suppressedRecipients = recipients.filter(recipient => this.isEmailSuppressed(recipient));
4298
4315
 
4299
4316
  if (suppressedRecipients.length > 0) {
4300
- const originalCount = email.to.length;
4317
+ const originalCount = recipients.length;
4301
4318
  const suppressed = suppressedRecipients.map(recipient => {
4302
4319
  const info = this.getSuppressionInfo(recipient);
4303
4320
  return {
@@ -4313,7 +4330,9 @@ export class UnifiedEmailServer extends EventEmitter {
4313
4330
  throw new Error('All recipients are on the suppression list');
4314
4331
  }
4315
4332
 
4316
- email.to = email.to.filter(recipient => !this.isEmailSuppressed(recipient));
4333
+ // The message's fields stay as written; the suppressed recipients
4334
+ // only leave the envelope.
4335
+ email.setEnvelopeTo(recipients.filter(recipient => !this.isEmailSuppressed(recipient)));
4317
4336
  }
4318
4337
  }
4319
4338
 
@@ -346,7 +346,7 @@ export class ContentScanner {
346
346
  void callObserverSafely('content scan security log', () => SecurityLogger.getInstance().logEvent({
347
347
  level: SecurityLogLevel.ERROR,
348
348
  type: SecurityEventType.MALWARE,
349
- message: `High threat content detected in email from ${email.from} to ${email.to.join(', ')}`,
349
+ message: `High threat content detected in email from ${email.from} to ${email.getAllRecipients().join(', ')}`,
350
350
  details: {
351
351
  messageId: email.getMessageId(),
352
352
  threatType: result.threatType,
@@ -369,7 +369,7 @@ export class ContentScanner {
369
369
  void callObserverSafely('content scan security log', () => SecurityLogger.getInstance().logEvent({
370
370
  level: SecurityLogLevel.WARN,
371
371
  type: SecurityEventType.SPAM,
372
- message: `Suspicious content detected in email from ${email.from} to ${email.to.join(', ')}`,
372
+ message: `Suspicious content detected in email from ${email.from} to ${email.getAllRecipients().join(', ')}`,
373
373
  details: {
374
374
  messageId: email.getMessageId(),
375
375
  threatType: result.threatType,
@@ -358,9 +358,12 @@ const isSmtpDeliveryPhase = (valueArg: unknown): valueArg is TSmtpDeliveryPhase
358
358
 
359
359
  interface IOutboundEmail {
360
360
  from: string;
361
+ /** The message's To field; written as To, never used for RCPT TO when `envelopeTo` is set. */
361
362
  to: string[];
362
363
  cc?: string[];
363
364
  bcc?: string[];
365
+ /** The SMTP envelope recipients; when present, RCPT TO goes to them alone. */
366
+ envelopeTo?: string[];
364
367
  subject?: string;
365
368
  text?: string;
366
369
  html?: string;