@push.rocks/smartmta 8.1.0 → 8.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 +24 -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 +81 -1
- package/dist_ts/mail/routing/classes.unified.email.server.js +515 -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 +697 -5
|
@@ -30,6 +30,22 @@ export interface IActionExecutorDeps {
|
|
|
30
30
|
deliveryQueue: UnifiedDeliveryQueue;
|
|
31
31
|
}
|
|
32
32
|
|
|
33
|
+
export interface IActionExecutionOptions {
|
|
34
|
+
/**
|
|
35
|
+
* Queue the action with a deterministic durable id instead of performing
|
|
36
|
+
* direct delivery. Used when replaying an already-accepted envelope.
|
|
37
|
+
*/
|
|
38
|
+
durableQueue?: {
|
|
39
|
+
idempotencyKey: string;
|
|
40
|
+
fingerprint: string;
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export interface IActionExecutionResult {
|
|
45
|
+
outcome: 'completed' | 'queued';
|
|
46
|
+
queueId?: string;
|
|
47
|
+
}
|
|
48
|
+
|
|
33
49
|
/**
|
|
34
50
|
* Executes email routing actions (forward, process, deliver, reject)
|
|
35
51
|
*/
|
|
@@ -61,30 +77,45 @@ export class EmailActionExecutor {
|
|
|
61
77
|
}
|
|
62
78
|
}
|
|
63
79
|
|
|
64
|
-
async executeAction(
|
|
80
|
+
async executeAction(
|
|
81
|
+
action: IEmailAction,
|
|
82
|
+
email: Email,
|
|
83
|
+
context: IEmailContext,
|
|
84
|
+
): Promise<void> {
|
|
85
|
+
await this.executeActionWithResult(action, email, context);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
async executeActionWithResult(
|
|
89
|
+
action: IEmailAction,
|
|
90
|
+
email: Email,
|
|
91
|
+
context: IEmailContext,
|
|
92
|
+
options: IActionExecutionOptions = {},
|
|
93
|
+
): Promise<IActionExecutionResult> {
|
|
65
94
|
switch (action.type) {
|
|
66
95
|
case 'forward':
|
|
67
|
-
|
|
68
|
-
break;
|
|
96
|
+
return this.handleForwardAction(action, email, context, options);
|
|
69
97
|
case 'process':
|
|
70
|
-
|
|
71
|
-
break;
|
|
98
|
+
return this.handleProcessAction(action, email, context, options);
|
|
72
99
|
case 'deliver':
|
|
73
|
-
|
|
74
|
-
break;
|
|
100
|
+
return this.handleDeliverAction(action, email, context, options);
|
|
75
101
|
case 'store':
|
|
76
102
|
throw new Error(
|
|
77
103
|
'Store actions require raw SMTP context and an awaited hooks.onAcceptEnvelope handler',
|
|
78
104
|
);
|
|
79
105
|
case 'reject':
|
|
80
106
|
await this.handleRejectAction(action, email, context);
|
|
81
|
-
|
|
107
|
+
throw new Error('Reject action unexpectedly returned');
|
|
82
108
|
default:
|
|
83
109
|
throw new Error(`Unknown action type: ${(action as any).type}`);
|
|
84
110
|
}
|
|
85
111
|
}
|
|
86
112
|
|
|
87
|
-
private async handleForwardAction(
|
|
113
|
+
private async handleForwardAction(
|
|
114
|
+
action: IEmailAction,
|
|
115
|
+
email: Email,
|
|
116
|
+
context: IEmailContext,
|
|
117
|
+
options: IActionExecutionOptions,
|
|
118
|
+
): Promise<IActionExecutionResult> {
|
|
88
119
|
if (!action.forward) {
|
|
89
120
|
throw new Error('Forward action requires forward configuration');
|
|
90
121
|
}
|
|
@@ -105,6 +136,20 @@ export class EmailActionExecutor {
|
|
|
105
136
|
email.headers['X-Forwarded-To'] = email.to.join(', ');
|
|
106
137
|
email.headers['X-Forwarded-Date'] = new Date().toISOString();
|
|
107
138
|
|
|
139
|
+
if (options.durableQueue) {
|
|
140
|
+
const queueId = await this.deps.deliveryQueue.enqueue(
|
|
141
|
+
email,
|
|
142
|
+
'forward',
|
|
143
|
+
context.session.matchedRoute!,
|
|
144
|
+
{
|
|
145
|
+
idempotencyKey: options.durableQueue.idempotencyKey,
|
|
146
|
+
idempotencyFingerprint: options.durableQueue.fingerprint,
|
|
147
|
+
requireDurable: true,
|
|
148
|
+
},
|
|
149
|
+
);
|
|
150
|
+
return { outcome: 'queued', queueId };
|
|
151
|
+
}
|
|
152
|
+
|
|
108
153
|
try {
|
|
109
154
|
// Send email via Rust SMTP client
|
|
110
155
|
const result = await this.deps.sendOutboundEmail(host, port, email, {
|
|
@@ -128,6 +173,7 @@ export class EmailActionExecutor {
|
|
|
128
173
|
},
|
|
129
174
|
success: true
|
|
130
175
|
}));
|
|
176
|
+
return { outcome: 'completed' };
|
|
131
177
|
} catch (error) {
|
|
132
178
|
const errorMessage = getErrorMessage(error);
|
|
133
179
|
logger.log('error', `Failed to forward email: ${errorMessage}`);
|
|
@@ -172,7 +218,12 @@ export class EmailActionExecutor {
|
|
|
172
218
|
}
|
|
173
219
|
}
|
|
174
220
|
|
|
175
|
-
private async handleProcessAction(
|
|
221
|
+
private async handleProcessAction(
|
|
222
|
+
action: IEmailAction,
|
|
223
|
+
email: Email,
|
|
224
|
+
context: IEmailContext,
|
|
225
|
+
options: IActionExecutionOptions,
|
|
226
|
+
): Promise<IActionExecutionResult> {
|
|
176
227
|
logger.log('info', `Processing email with action options`);
|
|
177
228
|
|
|
178
229
|
// Apply scanning if requested
|
|
@@ -182,18 +233,47 @@ export class EmailActionExecutor {
|
|
|
182
233
|
|
|
183
234
|
// Queue for delivery
|
|
184
235
|
const queue = action.process?.queue || 'normal';
|
|
185
|
-
await this.deps.deliveryQueue.enqueue(
|
|
236
|
+
const queueId = await this.deps.deliveryQueue.enqueue(
|
|
237
|
+
email,
|
|
238
|
+
'process',
|
|
239
|
+
context.session.matchedRoute!,
|
|
240
|
+
options.durableQueue
|
|
241
|
+
? {
|
|
242
|
+
idempotencyKey: options.durableQueue.idempotencyKey,
|
|
243
|
+
idempotencyFingerprint: options.durableQueue.fingerprint,
|
|
244
|
+
requireDurable: true,
|
|
245
|
+
}
|
|
246
|
+
: {},
|
|
247
|
+
);
|
|
186
248
|
|
|
187
249
|
logger.log('info', `Email queued for delivery in ${queue} queue`);
|
|
250
|
+
return { outcome: 'queued', queueId };
|
|
188
251
|
}
|
|
189
252
|
|
|
190
|
-
private async handleDeliverAction(
|
|
253
|
+
private async handleDeliverAction(
|
|
254
|
+
_action: IEmailAction,
|
|
255
|
+
email: Email,
|
|
256
|
+
context: IEmailContext,
|
|
257
|
+
options: IActionExecutionOptions,
|
|
258
|
+
): Promise<IActionExecutionResult> {
|
|
191
259
|
logger.log('info', `Delivering email locally`);
|
|
192
260
|
|
|
193
261
|
// Queue for local delivery
|
|
194
|
-
await this.deps.deliveryQueue.enqueue(
|
|
262
|
+
const queueId = await this.deps.deliveryQueue.enqueue(
|
|
263
|
+
email,
|
|
264
|
+
'mta',
|
|
265
|
+
context.session.matchedRoute!,
|
|
266
|
+
options.durableQueue
|
|
267
|
+
? {
|
|
268
|
+
idempotencyKey: options.durableQueue.idempotencyKey,
|
|
269
|
+
idempotencyFingerprint: options.durableQueue.fingerprint,
|
|
270
|
+
requireDurable: true,
|
|
271
|
+
}
|
|
272
|
+
: {},
|
|
273
|
+
);
|
|
195
274
|
|
|
196
275
|
logger.log('info', 'Email queued for local delivery');
|
|
276
|
+
return { outcome: 'queued', queueId };
|
|
197
277
|
}
|
|
198
278
|
|
|
199
279
|
private async handleRejectAction(action: IEmailAction, _email: Email, context: IEmailContext): Promise<void> {
|