@push.rocks/smartmta 8.1.0 → 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 +16 -0
- package/dist_ts/00_commitinfo_data.js +1 -1
- package/dist_ts/index.d.ts +1 -0
- package/dist_ts/index.js +2 -1
- package/dist_ts/mail/delivery/classes.delivery.queue.d.ts +28 -1
- package/dist_ts/mail/delivery/classes.delivery.queue.js +220 -3
- 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.email.action.executor.d.ts +15 -0
- package/dist_ts/mail/routing/classes.email.action.executor.js +36 -13
- package/dist_ts/mail/routing/classes.unified.email.server.d.ts +71 -1
- package/dist_ts/mail/routing/classes.unified.email.server.js +488 -5
- package/package.json +1 -1
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts/index.ts +1 -0
- package/ts/mail/delivery/classes.delivery.queue.ts +303 -1
- package/ts/mail/interfaces.storage.ts +148 -2
- package/ts/mail/routing/classes.email.action.executor.ts +93 -13
- package/ts/mail/routing/classes.unified.email.server.ts +667 -5
|
@@ -9,6 +9,20 @@ export interface IStorageManager {
|
|
|
9
9
|
get(key: string): Promise<string | null>;
|
|
10
10
|
set(key: string, value: string): Promise<void>;
|
|
11
11
|
list(prefix: string): Promise<string[]>;
|
|
12
|
+
/**
|
|
13
|
+
* Return at most `limit` keys from one backend-bounded page.
|
|
14
|
+
*
|
|
15
|
+
* Continuation tokens are opaque, scoped to the supplied prefix, and must
|
|
16
|
+
* only be passed back to the same storage implementation. A returned token
|
|
17
|
+
* must advance a non-overlapping traversal, remain valid after any keys in
|
|
18
|
+
* its page are deleted, and eventually exhaust an otherwise stable prefix.
|
|
19
|
+
* Offset-based tokens do not satisfy this deletion-stability requirement.
|
|
20
|
+
*
|
|
21
|
+
* This optional method preserves compatibility for existing callers;
|
|
22
|
+
* features which need bounded cleanup must require it explicitly and must
|
|
23
|
+
* never fall back to list().
|
|
24
|
+
*/
|
|
25
|
+
listPage?(prefix: string, options: IStorageListPageOptions): Promise<IStorageListPageResult>;
|
|
12
26
|
delete(key: string): Promise<void>;
|
|
13
27
|
}
|
|
14
28
|
/** Blob storage for queued messages and template attachments. */
|
|
@@ -16,22 +30,42 @@ export interface IBlobStorageManager {
|
|
|
16
30
|
get(key: string): Promise<Buffer | null>;
|
|
17
31
|
set(key: string, value: Buffer): Promise<void>;
|
|
18
32
|
list(prefix: string): Promise<string[]>;
|
|
33
|
+
/** Backend-bounded counterpart to list(); see IStorageManager.listPage. */
|
|
34
|
+
listPage?(prefix: string, options: IStorageListPageOptions): Promise<IStorageListPageResult>;
|
|
19
35
|
delete(key: string): Promise<void>;
|
|
20
36
|
}
|
|
37
|
+
export interface IStorageListPageOptions {
|
|
38
|
+
/** Positive maximum number of keys the backend may return. */
|
|
39
|
+
readonly limit: number;
|
|
40
|
+
/** Opaque token returned by the immediately preceding page. */
|
|
41
|
+
readonly continuationToken?: string;
|
|
42
|
+
}
|
|
43
|
+
export interface IStorageListPageResult {
|
|
44
|
+
/** Unique keys under the requested prefix; length must not exceed limit. */
|
|
45
|
+
readonly keys: string[];
|
|
46
|
+
/** Omitted when the prefix has been exhausted. */
|
|
47
|
+
readonly continuationToken?: string;
|
|
48
|
+
}
|
|
49
|
+
/** Hard upper bound for one SmartMTA storage listing page. */
|
|
50
|
+
export declare const SMARTMTA_STORAGE_LIST_PAGE_MAX_LIMIT = 1000;
|
|
21
51
|
/** In-memory small-state store for tests and explicitly non-durable runtimes. */
|
|
22
52
|
export declare class MemoryStorageManager implements IStorageManager {
|
|
23
53
|
private readonly entries;
|
|
54
|
+
private readonly sortedKeys;
|
|
24
55
|
get(key: string): Promise<string | null>;
|
|
25
56
|
set(key: string, value: string): Promise<void>;
|
|
26
57
|
list(prefix: string): Promise<string[]>;
|
|
58
|
+
listPage(prefix: string, options: IStorageListPageOptions): Promise<IStorageListPageResult>;
|
|
27
59
|
delete(key: string): Promise<void>;
|
|
28
60
|
}
|
|
29
61
|
/** In-memory blob store for tests and explicitly non-durable runtimes. */
|
|
30
62
|
export declare class MemoryBlobStorageManager implements IBlobStorageManager {
|
|
31
63
|
private readonly entries;
|
|
64
|
+
private readonly sortedKeys;
|
|
32
65
|
get(key: string): Promise<Buffer | null>;
|
|
33
66
|
set(key: string, value: Buffer): Promise<void>;
|
|
34
67
|
list(prefix: string): Promise<string[]>;
|
|
68
|
+
listPage(prefix: string, options: IStorageListPageOptions): Promise<IStorageListPageResult>;
|
|
35
69
|
delete(key: string): Promise<void>;
|
|
36
70
|
}
|
|
37
71
|
/** @deprecated Use IStorageManager. */
|
|
@@ -1,34 +1,120 @@
|
|
|
1
|
+
/** Hard upper bound for one SmartMTA storage listing page. */
|
|
2
|
+
export const SMARTMTA_STORAGE_LIST_PAGE_MAX_LIMIT = 1_000;
|
|
3
|
+
function assertValidListPageOptions(options) {
|
|
4
|
+
if (!Number.isInteger(options.limit)
|
|
5
|
+
|| options.limit < 1
|
|
6
|
+
|| options.limit > SMARTMTA_STORAGE_LIST_PAGE_MAX_LIMIT) {
|
|
7
|
+
throw new Error(`Storage list page limit must be an integer from 1 to ${SMARTMTA_STORAGE_LIST_PAGE_MAX_LIMIT}`);
|
|
8
|
+
}
|
|
9
|
+
if (options.continuationToken !== undefined
|
|
10
|
+
&& (typeof options.continuationToken !== 'string'
|
|
11
|
+
|| options.continuationToken.length === 0)) {
|
|
12
|
+
throw new Error('Storage list page continuation token must be a non-empty string');
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
function lowerBound(sortedKeys, target) {
|
|
16
|
+
let low = 0;
|
|
17
|
+
let high = sortedKeys.length;
|
|
18
|
+
while (low < high) {
|
|
19
|
+
const middle = low + Math.floor((high - low) / 2);
|
|
20
|
+
if (sortedKeys[middle] < target)
|
|
21
|
+
low = middle + 1;
|
|
22
|
+
else
|
|
23
|
+
high = middle;
|
|
24
|
+
}
|
|
25
|
+
return low;
|
|
26
|
+
}
|
|
27
|
+
function upperBound(sortedKeys, target) {
|
|
28
|
+
let low = 0;
|
|
29
|
+
let high = sortedKeys.length;
|
|
30
|
+
while (low < high) {
|
|
31
|
+
const middle = low + Math.floor((high - low) / 2);
|
|
32
|
+
if (sortedKeys[middle] <= target)
|
|
33
|
+
low = middle + 1;
|
|
34
|
+
else
|
|
35
|
+
high = middle;
|
|
36
|
+
}
|
|
37
|
+
return low;
|
|
38
|
+
}
|
|
39
|
+
function insertSortedKey(sortedKeys, key) {
|
|
40
|
+
sortedKeys.splice(lowerBound(sortedKeys, key), 0, key);
|
|
41
|
+
}
|
|
42
|
+
function removeSortedKey(sortedKeys, key) {
|
|
43
|
+
const index = lowerBound(sortedKeys, key);
|
|
44
|
+
if (sortedKeys[index] === key)
|
|
45
|
+
sortedKeys.splice(index, 1);
|
|
46
|
+
}
|
|
47
|
+
function listSortedPrefixPage(sortedKeys, prefix, options) {
|
|
48
|
+
assertValidListPageOptions(options);
|
|
49
|
+
if (options.continuationToken !== undefined
|
|
50
|
+
&& !options.continuationToken.startsWith(prefix)) {
|
|
51
|
+
throw new Error('Storage list page continuation token does not belong to the prefix');
|
|
52
|
+
}
|
|
53
|
+
let index = options.continuationToken === undefined
|
|
54
|
+
? lowerBound(sortedKeys, prefix)
|
|
55
|
+
: Math.max(lowerBound(sortedKeys, prefix), upperBound(sortedKeys, options.continuationToken));
|
|
56
|
+
const keys = [];
|
|
57
|
+
while (index < sortedKeys.length && keys.length < options.limit) {
|
|
58
|
+
const key = sortedKeys[index];
|
|
59
|
+
if (!key.startsWith(prefix))
|
|
60
|
+
break;
|
|
61
|
+
keys.push(key);
|
|
62
|
+
index++;
|
|
63
|
+
}
|
|
64
|
+
const hasMore = (index < sortedKeys.length
|
|
65
|
+
&& sortedKeys[index].startsWith(prefix));
|
|
66
|
+
return {
|
|
67
|
+
keys,
|
|
68
|
+
...(hasMore && keys.length > 0
|
|
69
|
+
? { continuationToken: keys[keys.length - 1] }
|
|
70
|
+
: {}),
|
|
71
|
+
};
|
|
72
|
+
}
|
|
1
73
|
/** In-memory small-state store for tests and explicitly non-durable runtimes. */
|
|
2
74
|
export class MemoryStorageManager {
|
|
3
75
|
entries = new Map();
|
|
76
|
+
sortedKeys = [];
|
|
4
77
|
async get(key) {
|
|
5
78
|
return this.entries.get(key) ?? null;
|
|
6
79
|
}
|
|
7
80
|
async set(key, value) {
|
|
81
|
+
if (!this.entries.has(key))
|
|
82
|
+
insertSortedKey(this.sortedKeys, key);
|
|
8
83
|
this.entries.set(key, value);
|
|
9
84
|
}
|
|
10
85
|
async list(prefix) {
|
|
11
86
|
return [...this.entries.keys()].filter((key) => key.startsWith(prefix)).sort();
|
|
12
87
|
}
|
|
88
|
+
async listPage(prefix, options) {
|
|
89
|
+
return listSortedPrefixPage(this.sortedKeys, prefix, options);
|
|
90
|
+
}
|
|
13
91
|
async delete(key) {
|
|
14
|
-
this.entries.delete(key)
|
|
92
|
+
if (this.entries.delete(key))
|
|
93
|
+
removeSortedKey(this.sortedKeys, key);
|
|
15
94
|
}
|
|
16
95
|
}
|
|
17
96
|
/** In-memory blob store for tests and explicitly non-durable runtimes. */
|
|
18
97
|
export class MemoryBlobStorageManager {
|
|
19
98
|
entries = new Map();
|
|
99
|
+
sortedKeys = [];
|
|
20
100
|
async get(key) {
|
|
21
101
|
const value = this.entries.get(key);
|
|
22
102
|
return value ? Buffer.from(value) : null;
|
|
23
103
|
}
|
|
24
104
|
async set(key, value) {
|
|
105
|
+
if (!this.entries.has(key))
|
|
106
|
+
insertSortedKey(this.sortedKeys, key);
|
|
25
107
|
this.entries.set(key, Buffer.from(value));
|
|
26
108
|
}
|
|
27
109
|
async list(prefix) {
|
|
28
110
|
return [...this.entries.keys()].filter((key) => key.startsWith(prefix)).sort();
|
|
29
111
|
}
|
|
112
|
+
async listPage(prefix, options) {
|
|
113
|
+
return listSortedPrefixPage(this.sortedKeys, prefix, options);
|
|
114
|
+
}
|
|
30
115
|
async delete(key) {
|
|
31
|
-
this.entries.delete(key)
|
|
116
|
+
if (this.entries.delete(key))
|
|
117
|
+
removeSortedKey(this.sortedKeys, key);
|
|
32
118
|
}
|
|
33
119
|
}
|
|
34
|
-
//# sourceMappingURL=data:application/json;base64,
|
|
120
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW50ZXJmYWNlcy5zdG9yYWdlLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vdHMvbWFpbC9pbnRlcmZhY2VzLnN0b3JhZ2UudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBMERBLDhEQUE4RDtBQUM5RCxNQUFNLENBQUMsTUFBTSxvQ0FBb0MsR0FBRyxLQUFLLENBQUM7QUFFMUQsU0FBUywwQkFBMEIsQ0FBQyxPQUFnQztJQUNsRSxJQUNFLENBQUMsTUFBTSxDQUFDLFNBQVMsQ0FBQyxPQUFPLENBQUMsS0FBSyxDQUFDO1dBQzdCLE9BQU8sQ0FBQyxLQUFLLEdBQUcsQ0FBQztXQUNqQixPQUFPLENBQUMsS0FBSyxHQUFHLG9DQUFvQyxFQUN2RCxDQUFDO1FBQ0QsTUFBTSxJQUFJLEtBQUssQ0FDYix3REFBd0Qsb0NBQW9DLEVBQUUsQ0FDL0YsQ0FBQztJQUNKLENBQUM7SUFDRCxJQUNFLE9BQU8sQ0FBQyxpQkFBaUIsS0FBSyxTQUFTO1dBQ3BDLENBQ0QsT0FBTyxPQUFPLENBQUMsaUJBQWlCLEtBQUssUUFBUTtlQUMxQyxPQUFPLENBQUMsaUJBQWlCLENBQUMsTUFBTSxLQUFLLENBQUMsQ0FDMUMsRUFDRCxDQUFDO1FBQ0QsTUFBTSxJQUFJLEtBQUssQ0FBQyxpRUFBaUUsQ0FBQyxDQUFDO0lBQ3JGLENBQUM7QUFDSCxDQUFDO0FBRUQsU0FBUyxVQUFVLENBQUMsVUFBNkIsRUFBRSxNQUFjO0lBQy9ELElBQUksR0FBRyxHQUFHLENBQUMsQ0FBQztJQUNaLElBQUksSUFBSSxHQUFHLFVBQVUsQ0FBQyxNQUFNLENBQUM7SUFDN0IsT0FBTyxHQUFHLEdBQUcsSUFBSSxFQUFFLENBQUM7UUFDbEIsTUFBTSxNQUFNLEdBQUcsR0FBRyxHQUFHLElBQUksQ0FBQyxLQUFLLENBQUMsQ0FBQyxJQUFJLEdBQUcsR0FBRyxDQUFDLEdBQUcsQ0FBQyxDQUFDLENBQUM7UUFDbEQsSUFBSSxVQUFVLENBQUMsTUFBTSxDQUFDLEdBQUcsTUFBTTtZQUFF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
|
|
@@ -19,6 +19,20 @@ export interface IActionExecutorDeps {
|
|
|
19
19
|
bounceManager: BounceManager;
|
|
20
20
|
deliveryQueue: UnifiedDeliveryQueue;
|
|
21
21
|
}
|
|
22
|
+
export interface IActionExecutionOptions {
|
|
23
|
+
/**
|
|
24
|
+
* Queue the action with a deterministic durable id instead of performing
|
|
25
|
+
* direct delivery. Used when replaying an already-accepted envelope.
|
|
26
|
+
*/
|
|
27
|
+
durableQueue?: {
|
|
28
|
+
idempotencyKey: string;
|
|
29
|
+
fingerprint: string;
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
export interface IActionExecutionResult {
|
|
33
|
+
outcome: 'completed' | 'queued';
|
|
34
|
+
queueId?: string;
|
|
35
|
+
}
|
|
22
36
|
/**
|
|
23
37
|
* Executes email routing actions (forward, process, deliver, reject)
|
|
24
38
|
*/
|
|
@@ -27,6 +41,7 @@ export declare class EmailActionExecutor {
|
|
|
27
41
|
constructor(deps: IActionExecutorDeps);
|
|
28
42
|
private suppressPermanentRcptFailures;
|
|
29
43
|
executeAction(action: IEmailAction, email: Email, context: IEmailContext): Promise<void>;
|
|
44
|
+
executeActionWithResult(action: IEmailAction, email: Email, context: IEmailContext, options?: IActionExecutionOptions): Promise<IActionExecutionResult>;
|
|
30
45
|
private handleForwardAction;
|
|
31
46
|
private handleProcessAction;
|
|
32
47
|
private handleDeliverAction;
|
|
@@ -31,26 +31,26 @@ export class EmailActionExecutor {
|
|
|
31
31
|
}
|
|
32
32
|
}
|
|
33
33
|
async executeAction(action, email, context) {
|
|
34
|
+
await this.executeActionWithResult(action, email, context);
|
|
35
|
+
}
|
|
36
|
+
async executeActionWithResult(action, email, context, options = {}) {
|
|
34
37
|
switch (action.type) {
|
|
35
38
|
case 'forward':
|
|
36
|
-
|
|
37
|
-
break;
|
|
39
|
+
return this.handleForwardAction(action, email, context, options);
|
|
38
40
|
case 'process':
|
|
39
|
-
|
|
40
|
-
break;
|
|
41
|
+
return this.handleProcessAction(action, email, context, options);
|
|
41
42
|
case 'deliver':
|
|
42
|
-
|
|
43
|
-
break;
|
|
43
|
+
return this.handleDeliverAction(action, email, context, options);
|
|
44
44
|
case 'store':
|
|
45
45
|
throw new Error('Store actions require raw SMTP context and an awaited hooks.onAcceptEnvelope handler');
|
|
46
46
|
case 'reject':
|
|
47
47
|
await this.handleRejectAction(action, email, context);
|
|
48
|
-
|
|
48
|
+
throw new Error('Reject action unexpectedly returned');
|
|
49
49
|
default:
|
|
50
50
|
throw new Error(`Unknown action type: ${action.type}`);
|
|
51
51
|
}
|
|
52
52
|
}
|
|
53
|
-
async handleForwardAction(action, email, context) {
|
|
53
|
+
async handleForwardAction(action, email, context, options) {
|
|
54
54
|
if (!action.forward) {
|
|
55
55
|
throw new Error('Forward action requires forward configuration');
|
|
56
56
|
}
|
|
@@ -66,6 +66,14 @@ export class EmailActionExecutor {
|
|
|
66
66
|
email.headers['X-Forwarded-For'] = context.session.remoteAddress || 'unknown';
|
|
67
67
|
email.headers['X-Forwarded-To'] = email.to.join(', ');
|
|
68
68
|
email.headers['X-Forwarded-Date'] = new Date().toISOString();
|
|
69
|
+
if (options.durableQueue) {
|
|
70
|
+
const queueId = await this.deps.deliveryQueue.enqueue(email, 'forward', context.session.matchedRoute, {
|
|
71
|
+
idempotencyKey: options.durableQueue.idempotencyKey,
|
|
72
|
+
idempotencyFingerprint: options.durableQueue.fingerprint,
|
|
73
|
+
requireDurable: true,
|
|
74
|
+
});
|
|
75
|
+
return { outcome: 'queued', queueId };
|
|
76
|
+
}
|
|
69
77
|
try {
|
|
70
78
|
// Send email via Rust SMTP client
|
|
71
79
|
const result = await this.deps.sendOutboundEmail(host, port, email, {
|
|
@@ -87,6 +95,7 @@ export class EmailActionExecutor {
|
|
|
87
95
|
},
|
|
88
96
|
success: true
|
|
89
97
|
}));
|
|
98
|
+
return { outcome: 'completed' };
|
|
90
99
|
}
|
|
91
100
|
catch (error) {
|
|
92
101
|
const errorMessage = getErrorMessage(error);
|
|
@@ -123,7 +132,7 @@ export class EmailActionExecutor {
|
|
|
123
132
|
throw error;
|
|
124
133
|
}
|
|
125
134
|
}
|
|
126
|
-
async handleProcessAction(action, email, context) {
|
|
135
|
+
async handleProcessAction(action, email, context, options) {
|
|
127
136
|
logger.log('info', `Processing email with action options`);
|
|
128
137
|
// Apply scanning if requested
|
|
129
138
|
if (action.process?.scan) {
|
|
@@ -131,14 +140,28 @@ export class EmailActionExecutor {
|
|
|
131
140
|
}
|
|
132
141
|
// Queue for delivery
|
|
133
142
|
const queue = action.process?.queue || 'normal';
|
|
134
|
-
await this.deps.deliveryQueue.enqueue(email, 'process', context.session.matchedRoute
|
|
143
|
+
const queueId = await this.deps.deliveryQueue.enqueue(email, 'process', context.session.matchedRoute, options.durableQueue
|
|
144
|
+
? {
|
|
145
|
+
idempotencyKey: options.durableQueue.idempotencyKey,
|
|
146
|
+
idempotencyFingerprint: options.durableQueue.fingerprint,
|
|
147
|
+
requireDurable: true,
|
|
148
|
+
}
|
|
149
|
+
: {});
|
|
135
150
|
logger.log('info', `Email queued for delivery in ${queue} queue`);
|
|
151
|
+
return { outcome: 'queued', queueId };
|
|
136
152
|
}
|
|
137
|
-
async handleDeliverAction(_action, email, context) {
|
|
153
|
+
async handleDeliverAction(_action, email, context, options) {
|
|
138
154
|
logger.log('info', `Delivering email locally`);
|
|
139
155
|
// Queue for local delivery
|
|
140
|
-
await this.deps.deliveryQueue.enqueue(email, 'mta', context.session.matchedRoute
|
|
156
|
+
const queueId = await this.deps.deliveryQueue.enqueue(email, 'mta', context.session.matchedRoute, options.durableQueue
|
|
157
|
+
? {
|
|
158
|
+
idempotencyKey: options.durableQueue.idempotencyKey,
|
|
159
|
+
idempotencyFingerprint: options.durableQueue.fingerprint,
|
|
160
|
+
requireDurable: true,
|
|
161
|
+
}
|
|
162
|
+
: {});
|
|
141
163
|
logger.log('info', 'Email queued for local delivery');
|
|
164
|
+
return { outcome: 'queued', queueId };
|
|
142
165
|
}
|
|
143
166
|
async handleRejectAction(action, _email, context) {
|
|
144
167
|
const code = action.reject?.code || 550;
|
|
@@ -165,4 +188,4 @@ export class EmailActionExecutor {
|
|
|
165
188
|
throw error;
|
|
166
189
|
}
|
|
167
190
|
}
|
|
168
|
-
//# sourceMappingURL=data:application/json;base64,
|
|
191
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY2xhc3Nlcy5lbWFpbC5hY3Rpb24uZXhlY3V0b3IuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi90cy9tYWlsL3JvdXRpbmcvY2xhc3Nlcy5lbWFpbC5hY3Rpb24uZXhlY3V0b3IudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxFQUFFLE1BQU0sRUFBRSxNQUFNLGlCQUFpQixDQUFDO0FBQ3pDLE9BQU8sRUFBRSxlQUFlLEVBQUUsTUFBTSwyQkFBMkIsQ0FBQztBQUM1RCxPQUFPLEVBQ0wsY0FBYyxFQUNkLGdCQUFnQixFQUNoQixpQkFBaUIsRUFDbEIsTUFBTSx5QkFBeUIsQ0FBQztBQUVqQyxPQUFPLEVBQUUsS0FBSyxFQUFFLE1BQU0sMEJBQTBCLENBQUM7QUFDakQsT0FBTyxFQUFFLGFBQWEsRUFBRSxNQUFNLGtDQUFrQyxDQUFDO0FBQ2pFLE9BQU8sRUFBRSxvQkFBb0IsRUFBRSxNQUFNLHVDQUF1QyxDQUFDO0FBQzdFLE9BQU8sRUFBRSxrQkFBa0IsRUFBRSxNQUFNLHlDQUF5QyxDQUFDO0FBQzdFLE9BQU8sRUFDTCxpQkFBaUIsR0FHbEIsTUFBTSw4Q0FBOEMsQ0FBQztBQWdDdEQ7O0dBRUc7QUFDSCxNQUFNLE9BQU8sbUJBQW1CO0lBQ1Y7SUFBcEIsWUFBb0IsSUFBeUI7UUFBekIsU0FBSSxHQUFKLElBQUksQ0FBcUI7SUFBRyxDQUFDO0lBRXpDLEtBQUssQ0FBQyw2QkFBNkIsQ0FDekMsS0FBWSxFQUNaLE9BQStCO1FBRS9CLEtBQUssTUFBTSxNQUFNLElBQUksT0FBTyxFQUFFLENBQUM7WUFDN0IsSUFBSSxNQUFNLENBQUMsUUFBUSxJQUFJLE1BQU0sQ0FBQyxZQUFZLEdBQUcsR0FBRztnQkFBRSxTQUFTO1lBQzNELElBQUksQ0FBQztnQkFDSCxNQUFNLElBQUksQ0FBQyxJQUFJLENBQUMsYUFBYSxDQUFDLGtCQUFrQixDQUM5QyxNQUFNLENBQUMsU0FBUyxFQUNoQixnQ0FBZ0MsTUFBTSxDQUFDLFlBQVksRUFBRSxFQUNyRDtvQkFDRSxNQUFNLEVBQUUsS0FBSyxDQUFDLElBQUk7b0JBQ2xCLGVBQWUsRUFBRSxLQUFLLENBQUMsT0FBTyxDQUFDLFlBQVksQ0FBVztvQkFDdEQsVUFBVSxFQUFFLE1BQU0sQ0FBQyxNQUFNLENBQUMsWUFBWSxDQUFDO2lCQUN4QyxDQUNGLENBQUM7WUFDSixDQUFDO1lBQUMsT0FBTyxLQUFLLEVBQUUsQ0FBQztnQkFDZixNQUFNLENBQUMsR0FBRyxDQUNSLE9BQU8sRUFDUCxnREFBZ0QsTUFBTSxDQUFDLFNBQVMsS0FBTSxLQUFlLENBQUMsT0FBTyxFQUFFLENBQ2hHLENBQUM7WUFDSixDQUFDO1FBQ0gsQ0FBQztJQUNILENBQUM7SUFFRCxLQUFLLENBQUMsYUFBYSxDQUNqQixNQUFvQixFQUNwQixLQUFZLEVBQ1osT0FBc0I7UUFFdEIsTUFBTSxJQUFJLENBQUMsdUJBQXVCLENBQUMsTUFBTSxFQUFFLEtBQUssRUFBRSxPQUFPLENBQUMsQ0FBQztJQUM3RCxDQUFDO0lBRUQsS0FBSyxDQUFDLHVCQUF1QixDQUMzQixNQUFvQixFQUNwQixLQUFZLEVBQ1osT0FBc0IsRUFDdEIsVUFBbUMsRUFBRTtRQUVyQyxRQUFRLE1BQU0sQ0FBQyxJQUFJLEVBQUUsQ0FBQztZQUNwQixLQUFLLFNBQVM7Z0JBQ1osT0FBTyxJQUFJLENBQUMsbUJBQW1CLENBQUMsTUFBTSxFQUFFLEtBQUssRUFBRSxPQUFPLEVBQUUsT0FBTyxDQUFDLENBQUM7WUFDbkUsS0FBSyxTQUFTO2dCQUNaLE9BQU8sSUFBSSxDQUFDLG1CQUFtQixDQUFDLE1BQU0sRUFBRSxLQUFLLEVBQUUsT0FBTyxFQUFFLE9BQU8sQ0FBQyxDQUFDO1lBQ25FLEtBQUssU0FBUztnQkFDWixPQUFPLElBQUksQ0FBQyxtQkFBbUIsQ0FBQyxNQUFNLEVBQUUsS0FBSyxFQUFFLE9BQU8sRUFBRSxPQUFPLENBQUMsQ0FBQztZQUNuRSxLQUFLLE9BQU87Z0JBQ1YsTUFBTSxJQUFJLEtBQUssQ0FDYixzRkFBc0YsQ0FDdkYsQ0FBQztZQUNKLEtBQUssUUFBUTtnQkFDWCxNQUFNLElBQUksQ0FBQyxrQkFBa0IsQ0FBQyxNQUFNLEVBQUUsS0FBSyxFQUFFLE9BQU8sQ0FBQyxDQUFDO2dCQUN0RCxNQUFNLElBQUksS0FBSyxDQUFDLHFDQUFxQyxDQUFDLENBQUM7WUFDekQ7Z0JBQ0UsTUFBTSxJQUFJLEtBQUssQ0FBQyx3QkFBeUIsTUFBYyxDQUFDLElBQUksRUFBRSxDQUFDLENBQUM7UUFDcEUsQ0FBQztJQUNILENBQUM7SUFFTyxLQUFLLENBQUMsbUJBQW1CLENBQy9CLE1BQW9CLEVBQ3BCLEtBQVksRUFDWixPQUFzQixFQUN0QixPQUFnQztRQUVoQyxJQUFJLENBQUMsTUFBTSxDQUFDLE9BQU8sRUFBRSxDQUFDO1lBQ3BCLE1BQU0sSUFBSSxLQUFLLENBQUMsK0NBQStDLENBQUMsQ0FBQztRQUNuRSxDQUFDO1FBRUQsTUFBTSxFQUFFLElBQUksRUFBRSxJQUFJLEdBQUcsRUFBRSxFQUFFLElBQUksRUFBRSxVQUFVLEVBQUUsR0FBRyxNQUFNLENBQUMsT0FBTyxDQUFDO1FBRTdELE1BQU0sQ0FBQyxHQUFHLENBQUMsTUFBTSxFQUFFLHVCQUF1QixJQUFJLElBQUksSUFBSSxFQUFFLENBQUMsQ0FBQztRQUUxRCx5QkFBeUI7UUFDekIsSUFBSSxVQUFVLEVBQUUsQ0FBQztZQUNmLEtBQUssTUFBTSxDQUFDLEdBQUcsRUFBRSxLQUFLLENBQUMsSUFBSSxNQUFNLENBQUMsT0FBTyxDQUFDLFVBQVUsQ0FBQyxFQUFFLENBQUM7Z0JBQ3RELEtBQUssQ0FBQyxPQUFPLENBQUMsR0FBRyxDQUFDLEdBQUcsS0FBSyxDQUFDO1lBQzdCLENBQUM7UUFDSCxDQUFDO1FBRUQsa0NBQWtDO1FBQ2xDLEtBQUssQ0FBQyxPQUFPLENBQUMsaUJBQWlCLENBQUMsR0FBRyxPQUFPLENBQUMsT0FBTyxDQUFDLGFBQWEsSUFBSSxTQUFTLENBQUM7UUFDOUUsS0FBSyxDQUFDLE9BQU8sQ0FBQyxnQkFBZ0IsQ0FBQyxHQUFHLEtBQUssQ0FBQyxFQUFFLENBQUMsSUFBSSxDQUFDLElBQUksQ0FBQyxDQUFDO1FBQ3RELEtBQUssQ0FBQyxPQUFPLENBQUMsa0JBQWtCLENBQUMsR0FBRyxJQUFJLElBQUksRUFBRSxDQUFDLFdBQVcsRUFBRSxDQUFDO1FBRTdELElBQUksT0FBTyxDQUFDLFlBQVksRUFBRSxDQUFDO1lBQ3pCLE1BQU0sT0FBTyxHQUFHLE1BQU0sSUFBSSxDQUFDLElBQUksQ0FBQyxhQUFhLENBQUMsT0FBTyxDQUNuRCxLQUFLLEVBQ0wsU0FBUyxFQUNULE9BQU8sQ0FBQyxPQUFPLENBQUMsWUFBYSxFQUM3QjtnQkFDRSxjQUFjLEVBQUUsT0FBTyxDQUFDLFlBQVksQ0FBQyxjQUFjO2dCQUNuRCxzQkFBc0IsRUFBRSxPQUFPLENBQUMsWUFBWSxDQUFDLFdBQVc7Z0JBQ3hELGNBQWMsRUFBRSxJQUFJO2FBQ3JCLENBQ0YsQ0FBQztZQUNGLE9BQU8sRUFBRSxPQUFPLEVBQUUsUUFBUSxFQUFFLE9BQU8sRUFBRSxDQUFDO1FBQ3hDLENBQUM7UUFFRCxJQUFJLENBQUM7WUFDSCxrQ0FBa0M7WUFDbEMsTUFBTSxNQUFNLEdBQUcsTUFBTSxJQUFJLENBQUMsSUFBSSxDQUFDLGlCQUFpQixDQUFDLElBQUksRUFBRSxJQUFJLEVBQUUsS0FBSyxFQUFFO2dCQUNsRSxJQUFJLEVBQUUsSUFBa0Q7YUFDekQsQ0FBQyxDQUFDO1lBQ0gsTUFBTSxJQUFJLENBQUMsNkJBQTZCLENBQUMsS0FBSyxFQUFFLE1BQU0sQ0FBQyxnQkFBZ0IsSUFBSSxFQUFFLENBQUMsQ0FBQztZQUUvRSxNQUFNLENBQUMsR0FBRyxDQUFDLE1BQU0sRUFBRSxtQ0FBbUMsSUFBSSxJQUFJLElBQUksRUFBRSxDQUFDLENBQUM7WUFFdEUsTUFBTSxrQkFBa0IsQ0FBQyw4QkFBOEIsRUFBRSxHQUFHLEVBQUUsQ0FBQyxjQUFjLENBQUMsV0FBVyxFQUFFLENBQUMsUUFBUSxDQUFDO2dCQUNuRyxLQUFLLEVBQUUsZ0JBQWdCLENBQUMsSUFBSTtnQkFDNUIsSUFBSSxFQUFFLGlCQUFpQixDQUFDLGdCQUFnQjtnQkFDeEMsT0FBTyxFQUFFLDhCQUE4QjtnQkFDdkMsU0FBUyxFQUFFLE9BQU8sQ0FBQyxPQUFPLENBQUMsYUFBYTtnQkFDeEMsT0FBTyxFQUFFO29CQUNQLFNBQVMsRUFBRSxPQUFPLENBQUMsT0FBTyxDQUFDLEVBQUU7b0JBQzdCLFNBQVMsRUFBRSxPQUFPLENBQUMsT0FBTyxDQUFDLFlBQVksRUFBRSxJQUFJO29CQUM3QyxVQUFVLEVBQUUsSUFBSTtvQkFDaEIsVUFBVSxFQUFFLElBQUk7b0JBQ2hCLFVBQVUsRUFBRSxLQUFLLENBQUMsRUFBRTtpQkFDckI7Z0JBQ0QsT0FBTyxFQUFFLElBQUk7YUFDZCxDQUFDLENBQUMsQ0FBQztZQUNKLE9BQU8sRUFBRSxPQUFPLEVBQUUsV0FBVyxFQUFFLENBQUM7UUFDbEMsQ0FBQztRQUFDLE9BQU8sS0FBSyxFQUFFLENBQUM7WUFDZixNQUFNLFlBQVksR0FBRyxlQUFlLENBQUMsS0FBSyxDQUFDLENBQUM7WUFDNUMsTUFBTSxDQUFDLEdBQUcsQ0FBQyxPQUFPLEVBQUUsNEJBQTRCLFlBQVksRUFBRSxDQUFDLENBQUM7WUFFaEUsTUFBTSxrQkFBa0IsQ0FBQyw4QkFBOEIsRUFBRSxHQUFHLEVBQUUsQ0FBQyxjQUFjLENBQUMsV0FBVyxFQUFFLENBQUMsUUFBUSxDQUFDO2dCQUNuRyxLQUFLLEVBQUUsZ0JBQWdCLENBQUMsS0FBSztnQkFDN0IsSUFBSSxFQUFFLGlCQUFpQixDQUFDLGdCQUFnQjtnQkFDeEMsT0FBTyxFQUFFLHlCQUF5QjtnQkFDbEMsU0FBUyxFQUFFLE9BQU8sQ0FBQyxPQUFPLENBQUMsYUFBYTtnQkFDeEMsT0FBTyxFQUFFO29CQUNQLFNBQVMsRUFBRSxPQUFPLENBQUMsT0FBTyxDQUFDLEVBQUU7b0JBQzdCLFNBQVMsRUFBRSxPQUFPLENBQUMsT0FBTyxDQUFDLFlBQVksRUFBRSxJQUFJO29CQUM3QyxVQUFVLEVBQUUsSUFBSTtvQkFDaEIsVUFBVSxFQUFFLElBQUk7b0JBQ2hCLEtBQUssRUFBRSxZQUFZO2lCQUNwQjtnQkFDRCxPQUFPLEVBQUUsS0FBSzthQUNmLENBQUMsQ0FBQyxDQUFDO1lBRUosTUFBTSxTQUFTLEdBQUcsaUJBQWlCLENBQUMsSUFBSSxDQUFDLEtBQUssQ0FBQyxDQUFDO1lBQ2hELElBQ0UsQ0FBQyxTQUFTLENBQUMsU0FBUzttQkFDakIsU0FBUyxDQUFDLFNBQVMsS0FBSyxVQUFVO21CQUNsQyxTQUFTLENBQUMsS0FBSyxLQUFLLFNBQVM7bUJBQzdCLENBQ0QsQ0FBQyxPQUFPLFNBQVMsQ0FBQyxRQUFRLEtBQUssUUFBUSxJQUFJLFNBQVMsQ0FBQyxRQUFRLElBQUksR0FBRyxDQUFDO3VCQUNsRSxTQUFTLENBQUMsZ0JBQWdCLENBQUMsSUFBSSxDQUNoQyxDQUFDLE1BQU0sRUFBRSxFQUFFLENBQUMsQ0FBQyxNQUFNLENBQUMsUUFBUSxJQUFJLE1BQU0sQ0FBQyxZQUFZLElBQUksR0FBRyxDQUMzRCxDQUNGLEVBQ0QsQ0FBQztnQkFDRCxNQUFNLGdCQUFnQixHQUFHLFNBQVMsQ0FBQyxnQkFBZ0IsQ0FBQyxNQUFNLEdBQUcsQ0FBQztvQkFDNUQsQ0FBQyxDQUFDLFNBQVMsQ0FBQyxnQkFBZ0I7b0JBQzVCLENBQUMsQ0FBQyxTQUFTLENBQUMsVUFBVSxDQUFDLEdBQUcsQ0FBQyxDQUFDLFNBQVMsRUFBRSxFQUFFLENBQUMsQ0FBQzt3QkFDdkMsU0FBUzt3QkFDVCxRQUFRLEVBQUUsS0FBSzt3QkFDZixZQUFZLEVBQUUsU0FBUyxDQUFDLFFBQVM7cUJBQ2xDLENBQUMsQ0FBQyxDQUFDO2dCQUNSLE1BQU0sSUFBSSxDQUFDLDZCQUE2QixDQUFDLEtBQUssRUFBRSxnQkFBZ0IsQ0FBQyxDQUFDO1lBQ3BFLENBQUM7WUFDRCxNQUFNLEtBQUssQ0FBQztRQUNkLENBQUM7SUFDSCxDQUFDO0lBRU8sS0FBSyxDQUFDLG1CQUFtQixDQUMvQixNQUFvQixFQUNwQixLQUFZLEVBQ1osT0FBc0IsRUFDdEIsT0FBZ0M7UUFFaEMsTUFBTSxDQUFDLEdBQUcsQ0FBQyxNQUFNLEVBQUUsc0NBQXNDLENBQUMsQ0FBQztRQUUzRCw4QkFBOEI7UUFDOUIsSUFBSSxNQUFNLENBQUMsT0FBTyxFQUFFLElBQUksRUFBRSxDQUFDO1lBQ3pCLE1BQU0sQ0FBQyxHQUFHLENBQUMsTUFBTSxFQUFFLDRCQUE0QixDQUFDLENBQUM7UUFDbkQsQ0FBQztRQUVELHFCQUFxQjtRQUNyQixNQUFNLEtBQUssR0FBRyxNQUFNLENBQUMsT0FBTyxFQUFFLEtBQUssSUFBSSxRQUFRLENBQUM7UUFDaEQsTUFBTSxPQUFPLEdBQUcsTUFBTSxJQUFJLENBQUMsSUFBSSxDQUFDLGFBQWEsQ0FBQyxPQUFPLENBQ25ELEtBQUssRUFDTCxTQUFTLEVBQ1QsT0FBTyxDQUFDLE9BQU8sQ0FBQyxZQUFhLEVBQzdCLE9BQU8sQ0FBQyxZQUFZO1lBQ2xCLENBQUMsQ0FBQztnQkFDRSxjQUFjLEVBQUUsT0FBTyxDQUFDLFlBQVksQ0FBQyxjQUFjO2dCQUNuRCxzQkFBc0IsRUFBRSxPQUFPLENBQUMsWUFBWSxDQUFDLFdBQVc7Z0JBQ3hELGNBQWMsRUFBRSxJQUFJO2FBQ3JCO1lBQ0gsQ0FBQyxDQUFDLEVBQUUsQ0FDUCxDQUFDO1FBRUYsTUFBTSxDQUFDLEdBQUcsQ0FBQyxNQUFNLEVBQUUsZ0NBQWdDLEtBQUssUUFBUSxDQUFDLENBQUM7UUFDbEUsT0FBTyxFQUFFLE9BQU8sRUFBRSxRQUFRLEVBQUUsT0FBTyxFQUFFLENBQUM7SUFDeEMsQ0FBQztJQUVPLEtBQUssQ0FBQyxtQkFBbUIsQ0FDL0IsT0FBcUIsRUFDckIsS0FBWSxFQUNaLE9BQXNCLEVBQ3RCLE9BQWdDO1FBRWhDLE1BQU0sQ0FBQyxHQUFHLENBQUMsTUFBTSxFQUFFLDBCQUEwQixDQUFDLENBQUM7UUFFL0MsMkJBQTJCO1FBQzNCLE1BQU0sT0FBTyxHQUFHLE1BQU0sSUFBSSxDQUFDLElBQUksQ0FBQyxhQUFhLENBQUMsT0FBTyxDQUNuRCxLQUFLLEVBQ0wsS0FBSyxFQUNMLE9BQU8sQ0FBQyxPQUFPLENBQUMsWUFBYSxFQUM3QixPQUFPLENBQUMsWUFBWTtZQUNsQixDQUFDLENBQUM7Z0JBQ0UsY0FBYyxFQUFFLE9BQU8sQ0FBQyxZQUFZLENBQUMsY0FBYztnQkFDbkQsc0JBQXNCLEVBQUUsT0FBTyxDQUFDLFlBQVksQ0FBQyxXQUFXO2dCQUN4RCxjQUFjLEVBQUUsSUFBSTthQUNyQjtZQUNILENBQUMsQ0FBQyxFQUFFLENBQ1AsQ0FBQztRQUVGLE1BQU0sQ0FBQyxHQUFHLENBQUMsTUFBTSxFQUFFLGlDQUFpQyxDQUFDLENBQUM7UUFDdEQsT0FBTyxFQUFFLE9BQU8sRUFBRSxRQUFRLEVBQUUsT0FBTyxFQUFFLENBQUM7SUFDeEMsQ0FBQztJQUVPLEtBQUssQ0FBQyxrQkFBa0IsQ0FBQyxNQUFvQixFQUFFLE1BQWEsRUFBRSxPQUFzQjtRQUMxRixNQUFNLElBQUksR0FBRyxNQUFNLENBQUMsTUFBTSxFQUFFLElBQUksSUFBSSxHQUFHLENBQUM7UUFDeEMsTUFBTSxPQUFPLEdBQUcsTUFBTSxDQUFDLE1BQU0sRUFBRSxPQUFPLElBQUksa0JBQWtCLENBQUM7UUFFN0QsTUFBTSxDQUFDLEdBQUcsQ0FBQyxNQUFNLEVBQUUsNkJBQTZCLElBQUksS0FBSyxPQUFPLEVBQUUsQ0FBQyxDQUFDO1FBRXBFLE1BQU0sa0JBQWtCLENBQUMsZ0NBQWdDLEVBQUUsR0FBRyxFQUFFLENBQUMsY0FBYyxDQUFDLFdBQVcsRUFBRSxDQUFDLFFBQVEsQ0FBQztZQUNyRyxLQUFLLEVBQUUsZ0JBQWdCLENBQUMsSUFBSTtZQUM1QixJQUFJLEVBQUUsaUJBQWlCLENBQUMsZ0JBQWdCO1lBQ3hDLE9BQU8sRUFBRSxnQ0FBZ0M7WUFDekMsU0FBUyxFQUFFLE9BQU8sQ0FBQyxPQUFPLENBQUMsYUFBYTtZQUN4QyxPQUFPLEVBQUU7Z0JBQ1AsU0FBUyxFQUFFLE9BQU8sQ0FBQyxPQUFPLENBQUMsRUFBRTtnQkFDN0IsU0FBUyxFQUFFLE9BQU8sQ0FBQyxPQUFPLENBQUMsWUFBWSxFQUFFLElBQUk7Z0JBQzdDLFVBQVUsRUFBRSxJQUFJO2dCQUNoQixhQUFhLEVBQUUsT0FBTztnQkFDdEIsSUFBSSxFQUFFLE1BQU0sQ0FBQyxJQUFJO2dCQUNqQixFQUFFLEVBQUUsTUFBTSxDQUFDLEVBQUU7YUFDZDtZQUNELE9BQU8sRUFBRSxLQUFLO1NBQ2YsQ0FBQyxDQUFDLENBQUM7UUFFSix5Q0FBeUM7UUFDekMsTUFBTSxLQUFLLEdBQUcsSUFBSSxLQUFLLENBQUMsT0FBTyxDQUFDLENBQUM7UUFDaEMsS0FBYSxDQUFDLFlBQVksR0FBRyxJQUFJLENBQUM7UUFDbkMsTUFBTSxLQUFLLENBQUM7SUFDZCxDQUFDO0NBQ0YifQ==
|
|
@@ -2,7 +2,7 @@ import { EventEmitter } from 'events';
|
|
|
2
2
|
import { DKIMCreator } from '../security/classes.dkimcreator.js';
|
|
3
3
|
import type { IStorageManager } from '../interfaces.storage.js';
|
|
4
4
|
import type { IInboundEmailSecurityResult } from '../../security/classes.rustsecuritybridge.js';
|
|
5
|
-
import type { IEmailRoute, IEmailDomainConfig } from './interfaces.js';
|
|
5
|
+
import type { IEmailRoute, IEmailAction, IEmailDomainConfig } from './interfaces.js';
|
|
6
6
|
import { Email } from '../core/classes.email.js';
|
|
7
7
|
import { DomainRegistry } from './classes.domain.registry.js';
|
|
8
8
|
import { BounceType, BounceCategory } from '../core/classes.bouncemanager.js';
|
|
@@ -96,6 +96,53 @@ export interface IAcceptEnvelopeContext {
|
|
|
96
96
|
/** Rust-computed inbound verdicts, available before durable acceptance. */
|
|
97
97
|
securityResults: IInboundEmailSecurityResult | null;
|
|
98
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[];
|
|
145
|
+
}
|
|
99
146
|
export interface IMessageAcceptanceDecision extends ISmtpDecision {
|
|
100
147
|
/** Continue SmartMTA's built-in route processing after the consumer accepted responsibility. */
|
|
101
148
|
continueProcessing?: boolean;
|
|
@@ -280,6 +327,9 @@ export declare class UnifiedEmailServer extends EventEmitter {
|
|
|
280
327
|
private bridgeEmailReceivedHandler?;
|
|
281
328
|
private bridgeAuthRequestHandler?;
|
|
282
329
|
private bridgeScramCredentialRequestHandler?;
|
|
330
|
+
private acceptedEnvelopeDispatchCleanupTimer?;
|
|
331
|
+
private acceptedEnvelopeDispatchCleanupPromise?;
|
|
332
|
+
private acceptedEnvelopeDispatchCleanupCursor?;
|
|
283
333
|
constructor(dcRouter: DcRouter, options: IUnifiedEmailServerOptions);
|
|
284
334
|
onRecipientRejected(listener: (event: IRecipientRejectedEvent) => Promise<void> | void): this;
|
|
285
335
|
offRecipientRejected(listener: (event: IRecipientRejectedEvent) => Promise<void> | void): this;
|
|
@@ -346,6 +396,26 @@ export declare class UnifiedEmailServer extends EventEmitter {
|
|
|
346
396
|
private buildDataPolicyContext;
|
|
347
397
|
private resolveDataRecipientRoutes;
|
|
348
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>;
|
|
349
419
|
/**
|
|
350
420
|
* Handle an emailReceived event from the Rust SMTP server.
|
|
351
421
|
*/
|