@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.
- package/changelog.md +18 -2
- package/dist_rust/mailer-bin_linux_amd64_musl +0 -0
- package/dist_rust/mailer-bin_linux_amd64_musl.tsrust-build.json +4 -4
- package/dist_rust/mailer-bin_linux_arm64_musl +0 -0
- package/dist_rust/mailer-bin_linux_arm64_musl.tsrust-build.json +4 -4
- package/dist_ts/00_commitinfo_data.js +1 -1
- package/dist_ts/mail/core/classes.email.d.ts +24 -3
- package/dist_ts/mail/core/classes.email.js +55 -6
- package/dist_ts/mail/core/functions.mailbox.d.ts +8 -0
- package/dist_ts/mail/core/functions.mailbox.js +21 -0
- package/dist_ts/mail/delivery/classes.delivery.queue.js +41 -4
- package/dist_ts/mail/delivery/classes.delivery.system.d.ts +1 -0
- package/dist_ts/mail/delivery/classes.delivery.system.js +4 -2
- package/dist_ts/mail/delivery/functions.raw-email.js +4 -5
- package/dist_ts/mail/routing/classes.email.action.executor.js +3 -3
- package/dist_ts/mail/routing/classes.unified.email.server.d.ts +7 -1
- package/dist_ts/mail/routing/classes.unified.email.server.js +32 -14
- package/dist_ts/security/classes.contentscanner.js +3 -3
- package/dist_ts/security/classes.rustsecuritybridge.d.ts +3 -0
- package/dist_ts/security/classes.rustsecuritybridge.js +1 -1
- package/package.json +1 -1
- package/readme.md +13 -0
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts/mail/core/classes.email.ts +57 -6
- package/ts/mail/core/functions.mailbox.ts +21 -0
- package/ts/mail/delivery/classes.delivery.queue.ts +48 -2
- package/ts/mail/delivery/classes.delivery.system.ts +3 -1
- package/ts/mail/delivery/functions.raw-email.ts +3 -4
- package/ts/mail/routing/classes.email.action.executor.ts +2 -2
- package/ts/mail/routing/classes.unified.email.server.ts +32 -13
- package/ts/security/classes.contentscanner.ts +2 -2
- 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
|
-
/**
|
|
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 =
|
|
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
|
|
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
|
-
|
|
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 (
|
|
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 =
|
|
4314
|
+
const suppressedRecipients = recipients.filter(recipient => this.isEmailSuppressed(recipient));
|
|
4298
4315
|
|
|
4299
4316
|
if (suppressedRecipients.length > 0) {
|
|
4300
|
-
const originalCount =
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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;
|