@push.rocks/smartmta 8.0.1 → 8.1.1
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 +51 -0
- package/dist_rust/mailer-bin_linux_amd64 +0 -0
- package/dist_rust/mailer-bin_linux_arm64 +0 -0
- package/dist_ts/00_commitinfo_data.js +1 -1
- package/dist_ts/index.d.ts +6 -0
- package/dist_ts/index.js +7 -1
- package/dist_ts/mail/delivery/classes.delivery.queue.d.ts +43 -3
- package/dist_ts/mail/delivery/classes.delivery.queue.js +310 -10
- package/dist_ts/mail/delivery/classes.delivery.system.d.ts +13 -2
- package/dist_ts/mail/delivery/classes.delivery.system.js +106 -36
- package/dist_ts/mail/interfaces.storage.d.ts +34 -0
- package/dist_ts/mail/interfaces.storage.js +89 -3
- package/dist_ts/mail/routing/classes.domain.registry.js +5 -1
- package/dist_ts/mail/routing/classes.email.action.executor.d.ts +15 -0
- package/dist_ts/mail/routing/classes.email.action.executor.js +38 -13
- package/dist_ts/mail/routing/classes.email.router.d.ts +2 -0
- package/dist_ts/mail/routing/classes.email.router.js +28 -21
- package/dist_ts/mail/routing/classes.unified.email.server.d.ts +156 -4
- package/dist_ts/mail/routing/classes.unified.email.server.js +1001 -61
- package/dist_ts/mail/routing/functions.routing.validation.d.ts +8 -0
- package/dist_ts/mail/routing/functions.routing.validation.js +18 -0
- package/dist_ts/mail/routing/interfaces.d.ts +11 -1
- package/dist_ts/plugins.d.ts +2 -1
- package/dist_ts/plugins.js +3 -2
- package/dist_ts/security/classes.rustsecuritybridge.d.ts +32 -1
- package/dist_ts/security/classes.rustsecuritybridge.js +13 -1
- package/dist_ts/security/index.d.ts +1 -1
- package/dist_ts/security/index.js +1 -1
- package/package.json +1 -1
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts/index.ts +6 -0
- package/ts/mail/delivery/classes.delivery.queue.ts +429 -7
- package/ts/mail/delivery/classes.delivery.system.ts +137 -41
- package/ts/mail/interfaces.storage.ts +148 -2
- package/ts/mail/routing/classes.domain.registry.ts +4 -0
- package/ts/mail/routing/classes.email.action.executor.ts +97 -13
- package/ts/mail/routing/classes.email.router.ts +33 -20
- package/ts/mail/routing/classes.unified.email.server.ts +1367 -74
- package/ts/mail/routing/functions.routing.validation.ts +36 -0
- package/ts/mail/routing/interfaces.ts +13 -1
- package/ts/plugins.ts +2 -0
- package/ts/security/classes.rustsecuritybridge.ts +46 -1
- package/ts/security/index.ts +2 -0
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { EventEmitter } from 'events';
|
|
2
2
|
import { DKIMCreator } from '../security/classes.dkimcreator.js';
|
|
3
3
|
import type { IStorageManager } from '../interfaces.storage.js';
|
|
4
|
-
import type {
|
|
4
|
+
import type { IInboundEmailSecurityResult } from '../../security/classes.rustsecuritybridge.js';
|
|
5
|
+
import type { IEmailRoute, IEmailAction, IEmailDomainConfig } from './interfaces.js';
|
|
5
6
|
import { Email } from '../core/classes.email.js';
|
|
6
7
|
import { DomainRegistry } from './classes.domain.registry.js';
|
|
7
8
|
import { BounceType, BounceCategory } from '../core/classes.bouncemanager.js';
|
|
@@ -16,6 +17,25 @@ interface DcRouter {
|
|
|
16
17
|
dnsServer?: any;
|
|
17
18
|
options?: any;
|
|
18
19
|
}
|
|
20
|
+
export type TRecipientRouteSource = 'explicit-route' | 'domain-policy' | 'no-route';
|
|
21
|
+
export type TRecipientRejectionReason = 'explicit-route' | 'domain-policy' | 'no-route' | 'domain-not-hosted' | 'relay-denied' | 'recipient-hook' | 'policy-error';
|
|
22
|
+
export interface IResolvedRecipientRoute {
|
|
23
|
+
recipient: string;
|
|
24
|
+
localDomain: string | null;
|
|
25
|
+
route: IEmailRoute | null;
|
|
26
|
+
source: TRecipientRouteSource;
|
|
27
|
+
}
|
|
28
|
+
export interface IRecipientRejectedEvent {
|
|
29
|
+
timestamp: string;
|
|
30
|
+
sender: string;
|
|
31
|
+
recipient: string;
|
|
32
|
+
localDomain: string | null;
|
|
33
|
+
remoteAddress: string;
|
|
34
|
+
smtpCode: number;
|
|
35
|
+
smtpMessage: string;
|
|
36
|
+
routeName: string | null;
|
|
37
|
+
reason: TRecipientRejectionReason;
|
|
38
|
+
}
|
|
19
39
|
/**
|
|
20
40
|
* Extended SMTP session interface with route information
|
|
21
41
|
*/
|
|
@@ -24,6 +44,10 @@ export interface IExtendedSmtpSession extends ISmtpSession {
|
|
|
24
44
|
* Matched route for this session
|
|
25
45
|
*/
|
|
26
46
|
matchedRoute?: IEmailRoute;
|
|
47
|
+
/** Per-envelope-recipient routes resolved before DATA acceptance. */
|
|
48
|
+
resolvedRecipientRoutes?: IResolvedRecipientRoute[];
|
|
49
|
+
/** Results computed by the Rust inbound pipeline before the message event is emitted. */
|
|
50
|
+
securityResults?: IInboundEmailSecurityResult | null;
|
|
27
51
|
}
|
|
28
52
|
export interface ISmtpDecision {
|
|
29
53
|
accepted: boolean;
|
|
@@ -46,7 +70,78 @@ export interface IMessageAcceptanceContext {
|
|
|
46
70
|
email: Email;
|
|
47
71
|
rawMessage: Buffer;
|
|
48
72
|
session: IExtendedSmtpSession;
|
|
73
|
+
matchedRoute?: IEmailRoute;
|
|
74
|
+
/** Full-envelope route resolution. */
|
|
75
|
+
resolvedRecipientRoutes: IResolvedRecipientRoute[];
|
|
76
|
+
abortSignal?: AbortSignal;
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Terminal durable acceptance context for an envelope containing at least one `store` route.
|
|
80
|
+
* The consumer owns persistence and all recipient fan-out before this hook resolves.
|
|
81
|
+
*/
|
|
82
|
+
export interface IAcceptEnvelopeContext {
|
|
83
|
+
email: Email;
|
|
84
|
+
/** Exact bytes received during SMTP DATA, before parsing or transformation. */
|
|
85
|
+
rawMessage: Buffer;
|
|
86
|
+
session: IExtendedSmtpSession;
|
|
87
|
+
envelope: {
|
|
88
|
+
mailFrom: string;
|
|
89
|
+
rcptTo: string[];
|
|
90
|
+
};
|
|
91
|
+
/** Complete resolved plan for every SMTP envelope recipient. */
|
|
92
|
+
resolvedRecipientRoutes: IResolvedRecipientRoute[];
|
|
49
93
|
abortSignal?: AbortSignal;
|
|
94
|
+
/** Immutable attachment count derived from the parsed message. */
|
|
95
|
+
readonly attachmentCount: number;
|
|
96
|
+
/** Rust-computed inbound verdicts, available before durable acceptance. */
|
|
97
|
+
securityResults: IInboundEmailSecurityResult | null;
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* JSON-safe, immutable snapshot of one accepted envelope recipient's exact
|
|
101
|
+
* routing decision. The snapshot is safe to persist and replay without
|
|
102
|
+
* consulting the live route table.
|
|
103
|
+
*/
|
|
104
|
+
export interface IAcceptedEnvelopeRecipientPlan {
|
|
105
|
+
readonly recipient: string;
|
|
106
|
+
readonly localDomain: string | null;
|
|
107
|
+
readonly routeName: string;
|
|
108
|
+
readonly source: TRecipientRouteSource;
|
|
109
|
+
readonly action: IEmailAction;
|
|
110
|
+
readonly idempotencyKey: string;
|
|
111
|
+
}
|
|
112
|
+
/** Serializable SMTP metadata needed to recreate an isolated action session. */
|
|
113
|
+
export interface IAcceptedEnvelopeDispatchMetadata {
|
|
114
|
+
readonly mailFrom: string;
|
|
115
|
+
readonly session?: {
|
|
116
|
+
readonly id?: string;
|
|
117
|
+
readonly remoteAddress?: string;
|
|
118
|
+
readonly clientHostname?: string;
|
|
119
|
+
readonly secure?: boolean;
|
|
120
|
+
readonly authenticated?: boolean;
|
|
121
|
+
readonly authenticatedUser?: string | null;
|
|
122
|
+
readonly user?: Readonly<{
|
|
123
|
+
username: string;
|
|
124
|
+
[key: string]: unknown;
|
|
125
|
+
}>;
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
export type TAcceptedEnvelopeRecipientDispatchStatus = 'stored' | 'queued' | 'rejected' | 'failed';
|
|
129
|
+
export interface IAcceptedEnvelopeRecipientDispatchResult {
|
|
130
|
+
recipient: string;
|
|
131
|
+
localDomain: string | null;
|
|
132
|
+
routeName: string;
|
|
133
|
+
source: TRecipientRouteSource;
|
|
134
|
+
actionType: IEmailAction['type'];
|
|
135
|
+
idempotencyKey: string;
|
|
136
|
+
status: TAcceptedEnvelopeRecipientDispatchStatus;
|
|
137
|
+
replayed: boolean;
|
|
138
|
+
queueId?: string;
|
|
139
|
+
smtpCode?: number;
|
|
140
|
+
message?: string;
|
|
141
|
+
}
|
|
142
|
+
export interface IAcceptedEnvelopeDispatchResult {
|
|
143
|
+
complete: boolean;
|
|
144
|
+
results: IAcceptedEnvelopeRecipientDispatchResult[];
|
|
50
145
|
}
|
|
51
146
|
export interface IMessageAcceptanceDecision extends ISmtpDecision {
|
|
52
147
|
/** Continue SmartMTA's built-in route processing after the consumer accepted responsibility. */
|
|
@@ -69,6 +164,16 @@ export interface IOutboundConnectionProxy {
|
|
|
69
164
|
ehloHostname?: string;
|
|
70
165
|
close?: () => Promise<void> | void;
|
|
71
166
|
}
|
|
167
|
+
export interface IOutboundTopologyIdentity {
|
|
168
|
+
hostnames?: string[];
|
|
169
|
+
ipAddresses?: string[];
|
|
170
|
+
}
|
|
171
|
+
export interface ISelfMxIdentityMatch {
|
|
172
|
+
isSelf: boolean;
|
|
173
|
+
source: 'explicit' | 'dns-derived' | 'dynamic' | null;
|
|
174
|
+
indeterminate: boolean;
|
|
175
|
+
evidence: string[];
|
|
176
|
+
}
|
|
72
177
|
/**
|
|
73
178
|
* Options for the unified email server
|
|
74
179
|
*/
|
|
@@ -123,10 +228,14 @@ export interface IUnifiedEmailServerOptions {
|
|
|
123
228
|
};
|
|
124
229
|
};
|
|
125
230
|
hooks?: {
|
|
126
|
-
/** Async recipient policy hook
|
|
231
|
+
/** Async recipient policy hook that may further restrict an accepted built-in decision. */
|
|
127
232
|
onRcptTo?: (context: IRcptToPolicyContext) => Promise<ISmtpDecision>;
|
|
128
|
-
/** Async message acceptance hook
|
|
233
|
+
/** Async message acceptance hook for envelopes with no resolved `store` action. */
|
|
129
234
|
onMessageData?: (context: IMessageAcceptanceContext) => Promise<IMessageAcceptanceDecision>;
|
|
235
|
+
/** Required terminal durable owner for any envelope containing a resolved `store` action. */
|
|
236
|
+
onAcceptEnvelope?: (context: IAcceptEnvelopeContext) => Promise<void>;
|
|
237
|
+
/** Observer-safe callback for SMTP recipient rejections. */
|
|
238
|
+
onRecipientRejected?: (event: IRecipientRejectedEvent) => Promise<void> | void;
|
|
130
239
|
};
|
|
131
240
|
defaults?: {
|
|
132
241
|
dnsMode?: 'forward' | 'internal-dns' | 'external-dns';
|
|
@@ -141,6 +250,10 @@ export interface IUnifiedEmailServerOptions {
|
|
|
141
250
|
socketTimeout?: number;
|
|
142
251
|
retryAttempts?: number;
|
|
143
252
|
defaultFrom?: string;
|
|
253
|
+
/** Static public listener identities that must never be selected as outbound MX targets. */
|
|
254
|
+
selfListenerIdentities?: IOutboundTopologyIdentity;
|
|
255
|
+
/** Current proven mail-edge identities. Connection proxy targets are deliberately not identities. */
|
|
256
|
+
topologyIdentityProvider?: () => Promise<IOutboundTopologyIdentity | null | undefined>;
|
|
144
257
|
connectionProxyProvider?: (context: IOutboundConnectionProxyContext) => Promise<IOutboundConnectionProxy | null | undefined>;
|
|
145
258
|
};
|
|
146
259
|
queue: IQueueOptions;
|
|
@@ -214,9 +327,18 @@ export declare class UnifiedEmailServer extends EventEmitter {
|
|
|
214
327
|
private bridgeEmailReceivedHandler?;
|
|
215
328
|
private bridgeAuthRequestHandler?;
|
|
216
329
|
private bridgeScramCredentialRequestHandler?;
|
|
330
|
+
private acceptedEnvelopeDispatchCleanupTimer?;
|
|
331
|
+
private acceptedEnvelopeDispatchCleanupPromise?;
|
|
332
|
+
private acceptedEnvelopeDispatchCleanupCursor?;
|
|
217
333
|
constructor(dcRouter: DcRouter, options: IUnifiedEmailServerOptions);
|
|
334
|
+
onRecipientRejected(listener: (event: IRecipientRejectedEvent) => Promise<void> | void): this;
|
|
335
|
+
offRecipientRejected(listener: (event: IRecipientRejectedEvent) => Promise<void> | void): this;
|
|
218
336
|
private getAdvertisedHostname;
|
|
219
337
|
private getOutboundHostname;
|
|
338
|
+
private normalizeTopologyIdentity;
|
|
339
|
+
private getExplicitTopologyIdentity;
|
|
340
|
+
private withTopologyTimeout;
|
|
341
|
+
private resolveTopologyHostname;
|
|
220
342
|
/**
|
|
221
343
|
* Whether a hostname refers to this server's own SMTP identity.
|
|
222
344
|
* Used by MTA delivery to refuse self-addressed relaying: a domain whose
|
|
@@ -224,6 +346,8 @@ export declare class UnifiedEmailServer extends EventEmitter {
|
|
|
224
346
|
* full outbound/inbound pipeline forever.
|
|
225
347
|
*/
|
|
226
348
|
isSelfHostname(hostname: string): boolean;
|
|
349
|
+
/** Resolve a candidate MX against explicit and current proven listener topology. */
|
|
350
|
+
resolveSelfMxIdentity(hostnameArg: string): Promise<ISelfMxIdentityMatch>;
|
|
227
351
|
/**
|
|
228
352
|
* Send an outbound email via the Rust SMTP client.
|
|
229
353
|
* Uses connection pooling in the Rust binary for efficiency.
|
|
@@ -257,13 +381,41 @@ export declare class UnifiedEmailServer extends EventEmitter {
|
|
|
257
381
|
stop(): Promise<void>;
|
|
258
382
|
private handleRustRcptToRequest;
|
|
259
383
|
private evaluateDefaultRcptToPolicy;
|
|
260
|
-
private
|
|
384
|
+
private resolveRecipientPolicy;
|
|
385
|
+
private getRejectionReasonForResolution;
|
|
386
|
+
private notifyRecipientRejected;
|
|
261
387
|
private buildPolicySession;
|
|
262
388
|
private getAddressDomain;
|
|
263
389
|
private getRecipientValidationTimeoutMs;
|
|
264
390
|
private isRecipientValidationEnabled;
|
|
265
391
|
private getMessageAcceptanceTimeoutMs;
|
|
266
392
|
private runHookWithTimeout;
|
|
393
|
+
/** Build a route-evaluation view containing only the current SMTP envelope recipient. */
|
|
394
|
+
private cloneEmailForRoutingRecipient;
|
|
395
|
+
private scopeSessionForRoutingRecipient;
|
|
396
|
+
private buildDataPolicyContext;
|
|
397
|
+
private resolveDataRecipientRoutes;
|
|
398
|
+
private acceptDurableEnvelope;
|
|
399
|
+
private cloneJsonSafe;
|
|
400
|
+
private stableJson;
|
|
401
|
+
private validateAcceptedEnvelopeAction;
|
|
402
|
+
private normalizeAcceptedEnvelopePlan;
|
|
403
|
+
/**
|
|
404
|
+
* Snapshot the already-resolved acceptance plan for durable consumer storage.
|
|
405
|
+
* No live route lookup is performed when the returned plan is dispatched.
|
|
406
|
+
*/
|
|
407
|
+
createAcceptedEnvelopeDispatchPlan(context: IAcceptEnvelopeContext, idempotencyKeys: readonly string[]): readonly IAcceptedEnvelopeRecipientPlan[];
|
|
408
|
+
private acceptedEnvelopeDispatchStorageKey;
|
|
409
|
+
private startAcceptedEnvelopeDispatchCleanup;
|
|
410
|
+
private cleanupExpiredAcceptedEnvelopeDispatchResults;
|
|
411
|
+
private readAcceptedEnvelopeDispatchResult;
|
|
412
|
+
private writeAcceptedEnvelopeDispatchResult;
|
|
413
|
+
/**
|
|
414
|
+
* Dispatch a durably accepted raw envelope using only its persisted exact
|
|
415
|
+
* action snapshots. Each recipient receives an isolated Email and session.
|
|
416
|
+
* Queue-backed actions require managed queue storage and are replay-safe.
|
|
417
|
+
*/
|
|
418
|
+
dispatchAcceptedEnvelope(rawMessage: Uint8Array, planArg: readonly IAcceptedEnvelopeRecipientPlan[], metadataArg: IAcceptedEnvelopeDispatchMetadata): Promise<IAcceptedEnvelopeDispatchResult>;
|
|
267
419
|
/**
|
|
268
420
|
* Handle an emailReceived event from the Rust SMTP server.
|
|
269
421
|
*/
|