@serve.zone/coremail 1.0.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 (75) hide show
  1. package/.smartconfig.json +50 -0
  2. package/changelog.md +10 -0
  3. package/cli.js +4 -0
  4. package/dist_ts/00_commitinfo_data.d.ts +8 -0
  5. package/dist_ts/00_commitinfo_data.js +9 -0
  6. package/dist_ts/classes.auth.d.ts +45 -0
  7. package/dist_ts/classes.auth.js +204 -0
  8. package/dist_ts/classes.config.d.ts +2 -0
  9. package/dist_ts/classes.config.js +72 -0
  10. package/dist_ts/classes.coremail.d.ts +41 -0
  11. package/dist_ts/classes.coremail.js +155 -0
  12. package/dist_ts/classes.desiredstate.d.ts +40 -0
  13. package/dist_ts/classes.desiredstate.js +382 -0
  14. package/dist_ts/classes.gateway.d.ts +62 -0
  15. package/dist_ts/classes.gateway.js +669 -0
  16. package/dist_ts/classes.inbound.d.ts +49 -0
  17. package/dist_ts/classes.inbound.js +702 -0
  18. package/dist_ts/classes.maintenance.d.ts +16 -0
  19. package/dist_ts/classes.maintenance.js +339 -0
  20. package/dist_ts/classes.models.d.ts +161 -0
  21. package/dist_ts/classes.models.js +595 -0
  22. package/dist_ts/classes.server.d.ts +40 -0
  23. package/dist_ts/classes.server.js +309 -0
  24. package/dist_ts/classes.storage.d.ts +32 -0
  25. package/dist_ts/classes.storage.js +317 -0
  26. package/dist_ts/classes.submissions.d.ts +33 -0
  27. package/dist_ts/classes.submissions.js +385 -0
  28. package/dist_ts/classes.transfer.d.ts +41 -0
  29. package/dist_ts/classes.transfer.js +289 -0
  30. package/dist_ts/coremail.cursor.d.ts +20 -0
  31. package/dist_ts/coremail.cursor.js +191 -0
  32. package/dist_ts/coremail.log.d.ts +3 -0
  33. package/dist_ts/coremail.log.js +11 -0
  34. package/dist_ts/coremail.mime.d.ts +8 -0
  35. package/dist_ts/coremail.mime.js +63 -0
  36. package/dist_ts/coremail.persistence.d.ts +148 -0
  37. package/dist_ts/coremail.persistence.js +695 -0
  38. package/dist_ts/coremail.quota.d.ts +29 -0
  39. package/dist_ts/coremail.quota.js +149 -0
  40. package/dist_ts/coremail.selectors.d.ts +3 -0
  41. package/dist_ts/coremail.selectors.js +21 -0
  42. package/dist_ts/coremail.validation.d.ts +9 -0
  43. package/dist_ts/coremail.validation.js +325 -0
  44. package/dist_ts/index.d.ts +4 -0
  45. package/dist_ts/index.js +39 -0
  46. package/dist_ts/interfaces.d.ts +92 -0
  47. package/dist_ts/interfaces.js +2 -0
  48. package/dist_ts/plugins.d.ts +11 -0
  49. package/dist_ts/plugins.js +12 -0
  50. package/license.md +19 -0
  51. package/package.json +48 -0
  52. package/readme.md +199 -0
  53. package/ts/00_commitinfo_data.ts +8 -0
  54. package/ts/classes.auth.ts +321 -0
  55. package/ts/classes.config.ts +104 -0
  56. package/ts/classes.coremail.ts +249 -0
  57. package/ts/classes.desiredstate.ts +508 -0
  58. package/ts/classes.gateway.ts +811 -0
  59. package/ts/classes.inbound.ts +898 -0
  60. package/ts/classes.maintenance.ts +335 -0
  61. package/ts/classes.models.ts +530 -0
  62. package/ts/classes.server.ts +444 -0
  63. package/ts/classes.storage.ts +448 -0
  64. package/ts/classes.submissions.ts +554 -0
  65. package/ts/classes.transfer.ts +379 -0
  66. package/ts/coremail.cursor.ts +328 -0
  67. package/ts/coremail.log.ts +21 -0
  68. package/ts/coremail.mime.ts +94 -0
  69. package/ts/coremail.persistence.ts +1156 -0
  70. package/ts/coremail.quota.ts +214 -0
  71. package/ts/coremail.selectors.ts +35 -0
  72. package/ts/coremail.validation.ts +446 -0
  73. package/ts/index.ts +38 -0
  74. package/ts/interfaces.ts +109 -0
  75. package/ts/plugins.ts +11 -0
@@ -0,0 +1,898 @@
1
+ import * as plugins from './plugins.js';
2
+ import { type ICoreMailModels } from './classes.models.js';
3
+ import {
4
+ createOpaqueToken,
5
+ createSha256,
6
+ } from './classes.storage.js';
7
+ import { CoreMailTransferService } from './classes.transfer.js';
8
+ import {
9
+ normalizeCoreMailConnectionInfo,
10
+ normalizeCoreMailEnvelope,
11
+ normalizeCoreMailGatewayMessage,
12
+ normalizeCoreMailMailboxAddress,
13
+ } from './coremail.validation.js';
14
+ import { CoreMailInboundCursorCodec } from './coremail.cursor.js';
15
+ import {
16
+ createCoreMailPendingQuotaDescriptor,
17
+ decrementCoreMailQuotaCounter,
18
+ ensureCoreMailQuotaCounter,
19
+ incrementCoreMailQuotaCounter,
20
+ withCoreMailTransaction,
21
+ } from './coremail.quota.js';
22
+ import {
23
+ requireCoreMailIdentifier,
24
+ requireCoreMailUuid,
25
+ } from './coremail.selectors.js';
26
+ import type {
27
+ ICoreMailInboundDeliveryRecord,
28
+ ICoreMailInboundHandoffRecord,
29
+ ICoreMailRecipientHandleRecord,
30
+ } from './coremail.persistence.js';
31
+ import type {
32
+ ICoreMailWorkloadSession,
33
+ ITransferAuthority,
34
+ } from './interfaces.js';
35
+
36
+ type TDesiredState = plugins.serveZoneInterfaces.data.ICoreMailDesiredState;
37
+ type TInboundDelivery = plugins.serveZoneInterfaces.data.ICoreMailInboundDelivery;
38
+ type TStoredInboundDelivery =
39
+ plugins.smartdata.TStoredDocument<ICoreMailInboundDeliveryRecord>;
40
+ type TStoredInboundHandoff =
41
+ plugins.smartdata.TStoredDocument<ICoreMailInboundHandoffRecord>;
42
+ type TStoredRecipientHandle =
43
+ plugins.smartdata.TStoredDocument<ICoreMailRecipientHandleRecord>;
44
+
45
+ const routingHandleTtlMs =
46
+ plugins.serveZoneInterfaces.data.coreMailLimits.routingHandleTtlMs;
47
+ const listLimitMaximum =
48
+ plugins.serveZoneInterfaces.data.coreMailLimits.inboundPageSize;
49
+
50
+ const gatewayAuthority = (serviceIdArg: string): ITransferAuthority => ({
51
+ kind: 'gateway',
52
+ tenantId: '-',
53
+ serviceId: serviceIdArg,
54
+ bindingId: '-',
55
+ });
56
+
57
+ const workloadAuthority = (
58
+ sessionArg: ICoreMailWorkloadSession,
59
+ ): ITransferAuthority => ({
60
+ kind: 'workload',
61
+ tenantId: sessionArg.tenantId,
62
+ serviceId: sessionArg.serviceId,
63
+ bindingId: sessionArg.bindingId,
64
+ });
65
+
66
+ const workloadFilter = (
67
+ sessionArg: ICoreMailWorkloadSession,
68
+ deliveryIdArg: string | undefined,
69
+ ) => ({
70
+ tenantId: requireCoreMailIdentifier(sessionArg.tenantId, 'session.tenantId'),
71
+ serviceId: requireCoreMailIdentifier(sessionArg.serviceId, 'session.serviceId'),
72
+ bindingId: requireCoreMailIdentifier(sessionArg.bindingId, 'session.bindingId'),
73
+ bindingRevision: sessionArg.bindingRevision,
74
+ ...(deliveryIdArg === undefined
75
+ ? {}
76
+ : { deliveryId: requireCoreMailUuid(deliveryIdArg, 'deliveryId') }),
77
+ });
78
+
79
+ const toPublicDelivery = (
80
+ deliveryArg: ICoreMailInboundDeliveryRecord,
81
+ ): TInboundDelivery => ({
82
+ deliveryId: deliveryArg.deliveryId,
83
+ transportDeliveryId: deliveryArg.transportDeliveryId,
84
+ state: deliveryArg.state,
85
+ envelope: deliveryArg.envelope,
86
+ rawMime: {
87
+ sha256: deliveryArg.rawMimeSha256,
88
+ lengthBytes: deliveryArg.rawMimeLengthBytes,
89
+ contentType: 'message/rfc822',
90
+ },
91
+ ...(deliveryArg.messageId ? { messageId: deliveryArg.messageId } : {}),
92
+ ...(deliveryArg.subject ? { subject: deliveryArg.subject } : {}),
93
+ receivedAt: deliveryArg.receivedAt,
94
+ updatedAt: deliveryArg.updatedAt,
95
+ ...(deliveryArg.state === 'acknowledged'
96
+ ? {
97
+ acknowledgedAt: deliveryArg.updatedAt,
98
+ acknowledgedOutcome: deliveryArg.acknowledgedOutcome!,
99
+ }
100
+ : {}),
101
+ });
102
+
103
+ const createEnvelopeDigest = (
104
+ envelopeArg: plugins.serveZoneInterfaces.data.ICoreMailEnvelope,
105
+ sourceArg: plugins.serveZoneInterfaces.data.IMailConnectionInfo,
106
+ ) => createSha256(JSON.stringify({
107
+ envelope: envelopeArg,
108
+ source: sourceArg,
109
+ }));
110
+
111
+ const canonicalJson = (valueArg: unknown): string => {
112
+ const visit = (value: unknown): unknown => {
113
+ if (Array.isArray(value)) return value.map(visit);
114
+ if (value && typeof value === 'object') {
115
+ return Object.fromEntries(
116
+ Object.keys(value as Record<string, unknown>)
117
+ .sort()
118
+ .map((keyArg) => [
119
+ keyArg,
120
+ visit((value as Record<string, unknown>)[keyArg]),
121
+ ]),
122
+ );
123
+ }
124
+ return value;
125
+ };
126
+ return JSON.stringify(visit(valueArg));
127
+ };
128
+
129
+ const isUniqueConflict = (errorArg: unknown): boolean =>
130
+ errorArg instanceof plugins.smartdata.SmartdataExactPersistenceError
131
+ && errorArg.code === 'unique_conflict';
132
+
133
+ export class CoreMailInboundService {
134
+ private readonly cursorCodec: CoreMailInboundCursorCodec;
135
+
136
+ public constructor(
137
+ private readonly database: plugins.smartdata.SmartdataDb,
138
+ private readonly models: ICoreMailModels,
139
+ private readonly transfers: CoreMailTransferService,
140
+ private readonly serviceId: string,
141
+ private readonly getDesiredState: () => Promise<TDesiredState | null>,
142
+ resolveRuntimeSecretArg: (secretKeyArg: string) => string,
143
+ private readonly now: () => number = Date.now,
144
+ ) {
145
+ this.cursorCodec = new CoreMailInboundCursorCodec(resolveRuntimeSecretArg);
146
+ }
147
+
148
+ public async resolveRecipients(
149
+ requestArg:
150
+ plugins.serveZoneInterfaces.requests.coremail.IReq_CoreMailGatewayResolveRecipients['request'],
151
+ ): Promise<{
152
+ resolutions: plugins.serveZoneInterfaces.data.ICoreMailRecipientResolution[];
153
+ }> {
154
+ const desiredState = await this.getDesiredState();
155
+ if (!desiredState) {
156
+ throw new Error('CoreMail desired state is unavailable.');
157
+ }
158
+ if (
159
+ !Array.isArray(requestArg.recipients)
160
+ || requestArg.recipients.length === 0
161
+ || requestArg.recipients.length > 100
162
+ ) {
163
+ throw new Error('CoreMail gateway recipients are invalid.');
164
+ }
165
+ const envelope = normalizeCoreMailEnvelope(requestArg.envelope);
166
+ const source = normalizeCoreMailConnectionInfo(requestArg.source);
167
+ const envelopeDigest = createEnvelopeDigest(envelope, source);
168
+ const recipients = requestArg.recipients.map(normalizeCoreMailMailboxAddress);
169
+ if (new Set(recipients).size !== recipients.length) {
170
+ throw new Error('CoreMail gateway recipients contain duplicates.');
171
+ }
172
+ const resolutions:
173
+ plugins.serveZoneInterfaces.data.ICoreMailRecipientResolution[] = [];
174
+ const pendingCounts = new Map<string, number>();
175
+ for (const recipient of recipients) {
176
+ const binding = desiredState.bindings.find((bindingArg) =>
177
+ bindingArg.state === 'active'
178
+ && bindingArg.capabilities.includes('inbound')
179
+ && bindingArg.inboundRecipients.includes(recipient)
180
+ );
181
+ if (!binding) {
182
+ resolutions.push({
183
+ recipient,
184
+ action: 'reject',
185
+ smtpCode: 550,
186
+ message: 'Recipient unavailable',
187
+ });
188
+ continue;
189
+ }
190
+ let pendingCount = pendingCounts.get(binding.bindingId);
191
+ if (pendingCount === undefined) {
192
+ pendingCount = await this.models.InboundDelivery.exact.count({
193
+ tenantId: binding.tenantId,
194
+ serviceId: binding.serviceId,
195
+ bindingId: binding.bindingId,
196
+ bindingRevision: binding.revision,
197
+ state: {
198
+ $in: [...plugins.serveZoneInterfaces.data.coreMailQuotaPolicy
199
+ .pendingInboundStates],
200
+ },
201
+ });
202
+ pendingCounts.set(binding.bindingId, pendingCount);
203
+ }
204
+ if (pendingCount >= binding.limits.maxPendingInbound) {
205
+ resolutions.push({
206
+ recipient,
207
+ action: 'defer',
208
+ smtpCode: 452,
209
+ message: 'Recipient temporarily unavailable',
210
+ });
211
+ continue;
212
+ }
213
+ const routingHandle = createOpaqueToken();
214
+ const createdAt = this.now();
215
+ const inserted = await this.models.RecipientHandle.exact.insert({
216
+ routingHandle: createSha256(routingHandle),
217
+ tenantId: binding.tenantId,
218
+ serviceId: binding.serviceId,
219
+ bindingId: binding.bindingId,
220
+ bindingRevision: binding.revision,
221
+ maxPendingInbound: binding.limits.maxPendingInbound,
222
+ recipient,
223
+ envelopeDigest,
224
+ state: 'issued',
225
+ expiresAt: createdAt + routingHandleTtlMs,
226
+ createdAt,
227
+ });
228
+ if (inserted.status === 'conflict') {
229
+ throw new Error('CoreMail routing handle identity conflict.');
230
+ }
231
+ resolutions.push({
232
+ recipient,
233
+ action: 'accept',
234
+ routingHandle,
235
+ });
236
+ }
237
+ return { resolutions };
238
+ }
239
+
240
+ public async prepareInboundHandoff(
241
+ requestArg:
242
+ plugins.serveZoneInterfaces.requests.coremail.IReq_CoreMailGatewayPrepareInboundHandoff['request'],
243
+ ): Promise<{
244
+ handoffId: string;
245
+ grant?: plugins.serveZoneInterfaces.data.ICoreMailUploadGrant;
246
+ replayed: boolean;
247
+ }> {
248
+ if (
249
+ !Array.isArray(requestArg.routingHandles)
250
+ || requestArg.routingHandles.length === 0
251
+ || requestArg.routingHandles.length > 100
252
+ ) {
253
+ throw new Error('CoreMail inbound handoff identity is invalid.');
254
+ }
255
+ const transportDeliveryId = requireCoreMailIdentifier(
256
+ requestArg.transportDeliveryId,
257
+ 'transportDeliveryId',
258
+ );
259
+ const routingHandles = requestArg.routingHandles.map((handleArg) => {
260
+ if (typeof handleArg !== 'string' || handleArg.length < 32 || handleArg.length > 512) {
261
+ throw new Error('CoreMail routing handle is invalid.');
262
+ }
263
+ return createSha256(handleArg);
264
+ });
265
+ if (new Set(routingHandles).size !== routingHandles.length) {
266
+ throw new Error('CoreMail inbound handoff contains duplicate routing handles.');
267
+ }
268
+ const message = normalizeCoreMailGatewayMessage(requestArg.message);
269
+ const source = normalizeCoreMailConnectionInfo(requestArg.source);
270
+ const envelopeDigest = createEnvelopeDigest(message.envelope, source);
271
+ const existing = await this.models.InboundHandoff.exact.findStoredOne({
272
+ transportDeliveryId,
273
+ });
274
+ if (existing) {
275
+ if (
276
+ canonicalJson(existing.routingHandles) !== canonicalJson(routingHandles)
277
+ || canonicalJson(existing.message) !== canonicalJson(message)
278
+ || canonicalJson(existing.source) !== canonicalJson(source)
279
+ || existing.state === 'failed'
280
+ ) {
281
+ throw new Error('CoreMail inbound handoff replay conflicts with persisted intent.');
282
+ }
283
+ if (existing.state === 'completed') {
284
+ return {
285
+ handoffId: existing.handoffId,
286
+ replayed: true,
287
+ };
288
+ }
289
+ const grant = await this.issueInboundGrant(existing);
290
+ return {
291
+ handoffId: existing.handoffId,
292
+ grant,
293
+ replayed: true,
294
+ };
295
+ }
296
+ const now = this.now();
297
+ for (const routingHandle of routingHandles) {
298
+ const handle = await this.models.RecipientHandle.exact.findStoredOne({
299
+ routingHandle,
300
+ state: 'issued',
301
+ expiresAt: { $gte: now },
302
+ });
303
+ if (!handle) {
304
+ throw new Error('CoreMail routing authority is unavailable.');
305
+ }
306
+ if (handle.envelopeDigest !== envelopeDigest) {
307
+ throw new Error('CoreMail routing authority is bound to another envelope.');
308
+ }
309
+ }
310
+ const handoffId = plugins.nodeCrypto.randomUUID();
311
+ const handoff: ICoreMailInboundHandoffRecord = {
312
+ handoffId,
313
+ revision: 0,
314
+ transportDeliveryId,
315
+ routingHandles,
316
+ message,
317
+ source,
318
+ state: 'prepared',
319
+ objectKey: `inbound-mime/${handoffId}/${message.rawMime.sha256.slice(7)}`,
320
+ deliveryIds: [],
321
+ createdAt: now,
322
+ updatedAt: now,
323
+ };
324
+ let storedHandoff: TStoredInboundHandoff;
325
+ try {
326
+ const inserted = await this.models.InboundHandoff.exact.insert(handoff);
327
+ if (inserted.status === 'conflict') {
328
+ throw new Error('CoreMail inbound handoff identity conflict.');
329
+ }
330
+ storedHandoff = inserted.document;
331
+ } catch (error) {
332
+ if (!isUniqueConflict(error)) {
333
+ throw error;
334
+ }
335
+ const concurrent =
336
+ await this.models.InboundHandoff.exact.findStoredOne({
337
+ transportDeliveryId,
338
+ });
339
+ if (
340
+ !concurrent
341
+ || canonicalJson(concurrent.routingHandles)
342
+ !== canonicalJson(routingHandles)
343
+ || canonicalJson(concurrent.message) !== canonicalJson(message)
344
+ || canonicalJson(concurrent.source) !== canonicalJson(source)
345
+ || concurrent.state === 'failed'
346
+ ) {
347
+ throw new Error(
348
+ 'CoreMail inbound handoff conflicts with persisted intent.',
349
+ );
350
+ }
351
+ if (concurrent.state === 'completed') {
352
+ return {
353
+ handoffId: concurrent.handoffId,
354
+ replayed: true,
355
+ };
356
+ }
357
+ const concurrentGrant = await this.issueInboundGrant(concurrent);
358
+ return {
359
+ handoffId: concurrent.handoffId,
360
+ grant: concurrentGrant,
361
+ replayed: true,
362
+ };
363
+ }
364
+ const grant = await this.issueInboundGrant(storedHandoff);
365
+ return { handoffId, grant, replayed: false };
366
+ }
367
+
368
+ private async issueInboundGrant(
369
+ handoffArg: TStoredInboundHandoff,
370
+ ): Promise<plugins.serveZoneInterfaces.data.ICoreMailUploadGrant> {
371
+ if (handoffArg.state !== 'prepared' && handoffArg.state !== 'uploaded') {
372
+ throw new Error('CoreMail inbound handoff cannot issue an upload grant.');
373
+ }
374
+ const grant = await this.transfers.issueGrant({
375
+ authority: gatewayAuthority(this.serviceId),
376
+ purpose: 'inbound-mime-upload',
377
+ method: 'PUT',
378
+ expectedSha256: handoffArg.message.rawMime.sha256,
379
+ lengthBytes: handoffArg.message.rawMime.lengthBytes,
380
+ contentType: 'message/rfc822',
381
+ objectKey: handoffArg.objectKey,
382
+ });
383
+ const update = await this.models.InboundHandoff.exact.transition({
384
+ current: handoffArg,
385
+ change: (modelArg) => {
386
+ modelArg.grantId = grant.grantId;
387
+ modelArg.state = 'uploaded';
388
+ modelArg.updatedAt = this.now();
389
+ modelArg.revision += 1;
390
+ },
391
+ });
392
+ if (update.status !== 'transitioned') {
393
+ throw new Error('CoreMail inbound handoff upload fence changed.');
394
+ }
395
+ return grant as plugins.serveZoneInterfaces.data.ICoreMailUploadGrant;
396
+ }
397
+
398
+ public async completeInboundHandoff(
399
+ requestArg:
400
+ plugins.serveZoneInterfaces.requests.coremail.IReq_CoreMailGatewayCompleteInboundHandoff['request'],
401
+ ): Promise<{ accepted: true; deliveryIds: string[]; replayed: boolean }> {
402
+ const handoff = await this.models.InboundHandoff.exact.findStoredOne({
403
+ handoffId: requireCoreMailUuid(requestArg.handoffId, 'handoffId'),
404
+ });
405
+ if (!handoff || handoff.state === 'failed') {
406
+ throw new Error('CoreMail inbound handoff is unavailable.');
407
+ }
408
+ const sha256 = plugins.serveZoneInterfaces.data.normalizeCoreMailSha256(
409
+ requestArg.sha256,
410
+ );
411
+ if (
412
+ (handoff.state !== 'uploaded' && handoff.state !== 'completed')
413
+ || handoff.message.rawMime.sha256 !== sha256
414
+ || handoff.message.rawMime.lengthBytes !== requestArg.lengthBytes
415
+ ) {
416
+ throw new Error('CoreMail inbound handoff completion is invalid.');
417
+ }
418
+ await this.transfers.assertConsumed({
419
+ grantId: requestArg.grantId,
420
+ authority: gatewayAuthority(this.serviceId),
421
+ purpose: 'inbound-mime-upload',
422
+ expectedSha256: handoff.message.rawMime.sha256,
423
+ lengthBytes: handoff.message.rawMime.lengthBytes,
424
+ objectKey: handoff.objectKey,
425
+ });
426
+ if (handoff.state === 'completed') {
427
+ return {
428
+ accepted: true,
429
+ deliveryIds: [...handoff.deliveryIds],
430
+ replayed: true,
431
+ };
432
+ }
433
+ const handles: TStoredRecipientHandle[] = [];
434
+ for (const routingHandle of handoff.routingHandles) {
435
+ const handle = await this.models.RecipientHandle.exact.findStoredOne({
436
+ routingHandle,
437
+ });
438
+ if (
439
+ !handle
440
+ || (
441
+ handle.state === 'consumed'
442
+ && handle.handoffId !== handoff.handoffId
443
+ )
444
+ || (
445
+ handle.state === 'issued'
446
+ && handle.expiresAt < this.now()
447
+ )
448
+ || handle.envelopeDigest !== createEnvelopeDigest(
449
+ handoff.message.envelope,
450
+ handoff.source,
451
+ )
452
+ ) {
453
+ throw new Error('CoreMail routing handle fence changed.');
454
+ }
455
+ handles.push(handle);
456
+ }
457
+ const grouped = new Map<string, TStoredRecipientHandle[]>();
458
+ for (const handle of handles) {
459
+ const key = JSON.stringify([
460
+ handle.tenantId,
461
+ handle.serviceId,
462
+ handle.bindingId,
463
+ handle.bindingRevision,
464
+ ]);
465
+ const group = grouped.get(key) ?? [];
466
+ group.push(handle);
467
+ grouped.set(key, group);
468
+ }
469
+ const groups = [...grouped.values()];
470
+ const candidates: ICoreMailInboundDeliveryRecord[] = [];
471
+ for (const group of groups) {
472
+ const authority = group[0];
473
+ if (group.some((handleArg) =>
474
+ handleArg.maxPendingInbound !== authority.maxPendingInbound
475
+ )) {
476
+ throw new Error('CoreMail routing handles disagree on pending quota.');
477
+ }
478
+ const quota = createCoreMailPendingQuotaDescriptor(authority);
479
+ await ensureCoreMailQuotaCounter(this.models, quota);
480
+ const receivedAt = this.now();
481
+ candidates.push({
482
+ deliveryId: plugins.nodeCrypto.randomUUID(),
483
+ tenantId: authority.tenantId,
484
+ serviceId: authority.serviceId,
485
+ bindingId: authority.bindingId,
486
+ bindingRevision: authority.bindingRevision,
487
+ revision: 0,
488
+ transportDeliveryId: handoff.transportDeliveryId,
489
+ state: 'pending',
490
+ envelope: {
491
+ mailFrom: handoff.message.envelope.mailFrom,
492
+ rcptTo: group.map((handleArg) => handleArg.recipient),
493
+ },
494
+ rawMimeSha256: handoff.message.rawMime.sha256,
495
+ rawMimeLengthBytes: handoff.message.rawMime.lengthBytes,
496
+ rawMimeObjectKey: handoff.objectKey,
497
+ receivedAt,
498
+ updatedAt: receivedAt,
499
+ });
500
+ }
501
+ try {
502
+ const deliveryIds = await withCoreMailTransaction(
503
+ this.database,
504
+ async (transactionArg) => {
505
+ const transactionHandoff =
506
+ await this.models.InboundHandoff.exact.findStoredOne({
507
+ handoffId: handoff.handoffId,
508
+ state: 'uploaded',
509
+ revision: handoff.revision,
510
+ }, { session: transactionArg });
511
+ if (!transactionHandoff) {
512
+ throw new Error('CoreMail inbound handoff completion fence changed.');
513
+ }
514
+ const committedIds: string[] = [];
515
+ for (const [index, group] of groups.entries()) {
516
+ const authority = group[0];
517
+ const candidate = candidates[index];
518
+ const quota = createCoreMailPendingQuotaDescriptor(authority);
519
+ const existing = await this.models.InboundDelivery.exact.findStoredOne({
520
+ transportDeliveryId: handoff.transportDeliveryId,
521
+ bindingId: authority.bindingId,
522
+ bindingRevision: authority.bindingRevision,
523
+ }, { session: transactionArg });
524
+ if (!existing) {
525
+ await incrementCoreMailQuotaCounter(
526
+ this.models,
527
+ quota,
528
+ authority.maxPendingInbound,
529
+ candidate.receivedAt,
530
+ transactionArg,
531
+ );
532
+ const inserted = await this.models.InboundDelivery.exact.insert(
533
+ candidate,
534
+ { session: transactionArg },
535
+ );
536
+ if (inserted.status === 'conflict') {
537
+ throw new Error('CoreMail inbound delivery identity conflict.');
538
+ }
539
+ }
540
+ for (const originalHandle of group) {
541
+ const handle = await this.models.RecipientHandle.exact.findStoredOne(
542
+ { routingHandle: originalHandle.routingHandle },
543
+ { session: transactionArg },
544
+ );
545
+ if (
546
+ !handle
547
+ || handle.tenantId !== authority.tenantId
548
+ || handle.serviceId !== authority.serviceId
549
+ || handle.bindingId !== authority.bindingId
550
+ || handle.bindingRevision !== authority.bindingRevision
551
+ || handle.maxPendingInbound !== authority.maxPendingInbound
552
+ || (
553
+ handle.state === 'consumed'
554
+ && handle.handoffId !== handoff.handoffId
555
+ )
556
+ || (
557
+ handle.state === 'issued'
558
+ && handle.expiresAt < candidate.receivedAt
559
+ )
560
+ ) {
561
+ throw new Error('CoreMail routing handle fence changed.');
562
+ }
563
+ if (handle.state === 'issued') {
564
+ const claim = await this.models.RecipientHandle.exact.transition({
565
+ current: handle,
566
+ change: (modelArg) => {
567
+ modelArg.state = 'consumed';
568
+ modelArg.handoffId = handoff.handoffId;
569
+ },
570
+ }, { session: transactionArg });
571
+ if (claim.status !== 'transitioned') {
572
+ throw new Error('CoreMail routing handle claim fence changed.');
573
+ }
574
+ }
575
+ }
576
+ committedIds.push(existing?.deliveryId ?? candidate.deliveryId);
577
+ }
578
+ committedIds.sort();
579
+ const completion = await this.models.InboundHandoff.exact.transition({
580
+ current: transactionHandoff,
581
+ change: (modelArg) => {
582
+ modelArg.state = 'completed';
583
+ modelArg.grantId = requestArg.grantId;
584
+ modelArg.deliveryIds = committedIds;
585
+ modelArg.completedAt = this.now();
586
+ modelArg.updatedAt = modelArg.completedAt;
587
+ modelArg.revision += 1;
588
+ },
589
+ }, { session: transactionArg });
590
+ if (completion.status !== 'transitioned') {
591
+ throw new Error('CoreMail inbound handoff completion fence changed.');
592
+ }
593
+ return committedIds;
594
+ },
595
+ );
596
+ return { accepted: true, deliveryIds, replayed: false };
597
+ } catch (error) {
598
+ const replay = await this.models.InboundHandoff.exact.findStoredOne({
599
+ handoffId: handoff.handoffId,
600
+ state: 'completed',
601
+ });
602
+ if (replay) {
603
+ return {
604
+ accepted: true,
605
+ deliveryIds: [...replay.deliveryIds],
606
+ replayed: true,
607
+ };
608
+ }
609
+ throw error;
610
+ }
611
+ }
612
+
613
+ public async listDeliveries(
614
+ sessionArg: ICoreMailWorkloadSession,
615
+ requestArg: {
616
+ cursor?: string;
617
+ limit: number;
618
+ },
619
+ ): Promise<{
620
+ deliveries: TInboundDelivery[];
621
+ nextCursor?: string;
622
+ }> {
623
+ if (
624
+ !Number.isSafeInteger(requestArg.limit)
625
+ || requestArg.limit < 1
626
+ || requestArg.limit > listLimitMaximum
627
+ || (
628
+ requestArg.cursor !== undefined
629
+ && (
630
+ typeof requestArg.cursor !== 'string'
631
+ || requestArg.cursor.length >
632
+ plugins.serveZoneInterfaces.data.coreMailLimits.cursorBytes
633
+ )
634
+ )
635
+ ) {
636
+ throw new Error('CoreMail inbound list window is invalid.');
637
+ }
638
+ const authority = {
639
+ tenantId: sessionArg.tenantId,
640
+ serviceId: sessionArg.serviceId,
641
+ bindingId: sessionArg.bindingId,
642
+ bindingRevision: sessionArg.bindingRevision,
643
+ };
644
+ const now = this.now();
645
+ const desiredState = await this.getDesiredState();
646
+ if (!desiredState) {
647
+ throw new Error('CoreMail desired state is unavailable.');
648
+ }
649
+ const position = requestArg.cursor
650
+ ? this.cursorCodec.decode(
651
+ requestArg.cursor,
652
+ authority,
653
+ now,
654
+ desiredState.cursorKeys,
655
+ )
656
+ : null;
657
+ const snapshotAt = position?.snapshotAt ?? now;
658
+ const deliveries = await this.models.InboundDelivery.exact.findStored({
659
+ filter: {
660
+ ...workloadFilter(sessionArg, undefined),
661
+ receivedAt: { $lt: snapshotAt },
662
+ ...(position
663
+ ? {
664
+ $or: [{
665
+ receivedAt: { $gt: position.receivedAt },
666
+ }, {
667
+ receivedAt: position.receivedAt,
668
+ deliveryId: { $gt: position.deliveryId },
669
+ }],
670
+ }
671
+ : {}),
672
+ },
673
+ sort: { receivedAt: 1, deliveryId: 1 },
674
+ limit: requestArg.limit + 1,
675
+ });
676
+ const page: TInboundDelivery[] = [];
677
+ for (const delivery of deliveries.slice(0, requestArg.limit)) {
678
+ const publicDelivery = toPublicDelivery(delivery);
679
+ const cursor = this.cursorCodec.encode(
680
+ authority,
681
+ {
682
+ snapshotAt,
683
+ receivedAt: delivery.receivedAt,
684
+ deliveryId: delivery.deliveryId,
685
+ },
686
+ desiredState.cursorKeys,
687
+ );
688
+ const candidate = {
689
+ deliveries: [...page, publicDelivery],
690
+ nextCursor: cursor,
691
+ };
692
+ if (
693
+ Buffer.byteLength(JSON.stringify(candidate), 'utf8')
694
+ > plugins.serveZoneInterfaces.data.coreMailLimits.inboundPageBytes
695
+ ) {
696
+ break;
697
+ }
698
+ page.push(publicDelivery);
699
+ }
700
+ if (page.length === 0 && deliveries.length > 0) {
701
+ throw new Error('CoreMail inbound delivery exceeds the page byte budget.');
702
+ }
703
+ const hasMore = page.length < deliveries.length;
704
+ const last = page.at(-1);
705
+ const response = {
706
+ deliveries: page,
707
+ ...(hasMore && last
708
+ ? {
709
+ nextCursor: this.cursorCodec.encode(
710
+ authority,
711
+ {
712
+ snapshotAt,
713
+ receivedAt: last.receivedAt,
714
+ deliveryId: last.deliveryId,
715
+ },
716
+ desiredState.cursorKeys,
717
+ ),
718
+ }
719
+ : {}),
720
+ };
721
+ return plugins.serveZoneInterfaces.data
722
+ .normalizeCoreMailInboundDeliveryPage(response);
723
+ }
724
+
725
+ public async prepareFetch(
726
+ sessionArg: ICoreMailWorkloadSession,
727
+ requestArg:
728
+ plugins.serveZoneInterfaces.requests.coremail.IReq_CoreMailPrepareInboundFetch['request'],
729
+ ): Promise<{ grant: plugins.serveZoneInterfaces.data.ICoreMailDownloadGrant }> {
730
+ const delivery = await this.requireDelivery(sessionArg, requestArg.deliveryId);
731
+ if (delivery.state === 'acknowledged') {
732
+ throw new Error('CoreMail inbound delivery is already acknowledged.');
733
+ }
734
+ const grant = await this.transfers.issueGrant({
735
+ authority: workloadAuthority(sessionArg),
736
+ purpose: 'inbound-mime-download',
737
+ method: 'GET',
738
+ expectedSha256: delivery.rawMimeSha256,
739
+ lengthBytes: delivery.rawMimeLengthBytes,
740
+ contentType: 'message/rfc822',
741
+ objectKey: delivery.rawMimeObjectKey,
742
+ });
743
+ const update = await this.models.InboundDelivery.exact.transition({
744
+ current: delivery,
745
+ change: (modelArg) => {
746
+ modelArg.state = 'fetching';
747
+ modelArg.fetchGrantId = grant.grantId;
748
+ modelArg.updatedAt = this.now();
749
+ modelArg.revision += 1;
750
+ },
751
+ });
752
+ if (update.status !== 'transitioned') {
753
+ throw new Error('CoreMail inbound fetch preparation fence changed.');
754
+ }
755
+ return {
756
+ grant: grant as plugins.serveZoneInterfaces.data.ICoreMailDownloadGrant,
757
+ };
758
+ }
759
+
760
+ public async completeFetch(
761
+ sessionArg: ICoreMailWorkloadSession,
762
+ requestArg:
763
+ plugins.serveZoneInterfaces.requests.coremail.IReq_CoreMailCompleteInboundFetch['request'],
764
+ ): Promise<{ delivery: TInboundDelivery }> {
765
+ const delivery = await this.requireDelivery(sessionArg, requestArg.deliveryId);
766
+ const sha256 = plugins.serveZoneInterfaces.data.normalizeCoreMailSha256(
767
+ requestArg.sha256,
768
+ );
769
+ if (
770
+ (delivery.state !== 'fetching' && delivery.state !== 'fetched')
771
+ || delivery.rawMimeSha256 !== sha256
772
+ || delivery.rawMimeLengthBytes !== requestArg.lengthBytes
773
+ ) {
774
+ throw new Error('CoreMail inbound fetch completion is invalid.');
775
+ }
776
+ await this.transfers.assertConsumed({
777
+ grantId: requestArg.grantId,
778
+ authority: workloadAuthority(sessionArg),
779
+ purpose: 'inbound-mime-download',
780
+ expectedSha256: delivery.rawMimeSha256,
781
+ lengthBytes: delivery.rawMimeLengthBytes,
782
+ objectKey: delivery.rawMimeObjectKey,
783
+ });
784
+ if (delivery.state === 'fetched') {
785
+ return { delivery: toPublicDelivery(delivery) };
786
+ }
787
+ const updatedAt = this.now();
788
+ const update = await this.models.InboundDelivery.exact.transition({
789
+ current: delivery,
790
+ change: (modelArg) => {
791
+ modelArg.state = 'fetched';
792
+ modelArg.fetchGrantId = requestArg.grantId;
793
+ modelArg.updatedAt = updatedAt;
794
+ modelArg.revision += 1;
795
+ },
796
+ });
797
+ if (update.status !== 'transitioned') {
798
+ throw new Error('CoreMail inbound fetch completion fence changed.');
799
+ }
800
+ return {
801
+ delivery: toPublicDelivery(update.document),
802
+ };
803
+ }
804
+
805
+ public async acknowledge(
806
+ sessionArg: ICoreMailWorkloadSession,
807
+ requestArg:
808
+ plugins.serveZoneInterfaces.requests.coremail.IReq_CoreMailAcknowledgeInboundDelivery['request'],
809
+ ): Promise<{ delivery: TInboundDelivery; replayed: boolean }> {
810
+ if (requestArg.outcome !== 'processed' && requestArg.outcome !== 'discarded') {
811
+ throw new Error('CoreMail inbound acknowledgement outcome is invalid.');
812
+ }
813
+ const delivery = await this.requireDelivery(sessionArg, requestArg.deliveryId);
814
+ if (delivery.state === 'acknowledged') {
815
+ if (delivery.acknowledgedOutcome !== requestArg.outcome) {
816
+ throw new Error('CoreMail inbound acknowledgement conflicts with persisted intent.');
817
+ }
818
+ return { delivery: toPublicDelivery(delivery), replayed: true };
819
+ }
820
+ if (delivery.state !== 'fetched') {
821
+ throw new Error('CoreMail inbound delivery must be fetched before acknowledgement.');
822
+ }
823
+ const updatedAt = this.now();
824
+ const quota = createCoreMailPendingQuotaDescriptor({
825
+ tenantId: sessionArg.tenantId,
826
+ serviceId: sessionArg.serviceId,
827
+ bindingId: sessionArg.bindingId,
828
+ bindingRevision: sessionArg.bindingRevision,
829
+ });
830
+ await ensureCoreMailQuotaCounter(this.models, quota);
831
+ const transactionResult = await withCoreMailTransaction(
832
+ this.database,
833
+ async (transactionArg) => {
834
+ const current = await this.models.InboundDelivery.exact.findStoredOne(
835
+ workloadFilter(sessionArg, requestArg.deliveryId),
836
+ { session: transactionArg },
837
+ );
838
+ if (!current) {
839
+ throw new Error('CoreMail inbound delivery is unavailable.');
840
+ }
841
+ if (current.state === 'acknowledged') {
842
+ if (current.acknowledgedOutcome !== requestArg.outcome) {
843
+ throw new Error(
844
+ 'CoreMail inbound acknowledgement conflicts with persisted intent.',
845
+ );
846
+ }
847
+ return { replayed: true };
848
+ }
849
+ if (current.state !== 'fetched') {
850
+ throw new Error('CoreMail inbound delivery must be fetched before acknowledgement.');
851
+ }
852
+ await decrementCoreMailQuotaCounter(
853
+ this.models,
854
+ quota,
855
+ updatedAt,
856
+ transactionArg,
857
+ );
858
+ const update = await this.models.InboundDelivery.exact.transition({
859
+ current,
860
+ change: (modelArg) => {
861
+ modelArg.state = 'acknowledged';
862
+ modelArg.acknowledgedOutcome = requestArg.outcome;
863
+ modelArg.updatedAt = updatedAt;
864
+ modelArg.revision += 1;
865
+ },
866
+ }, { session: transactionArg });
867
+ if (update.status !== 'transitioned') {
868
+ throw new Error('CoreMail inbound acknowledgement fence changed.');
869
+ }
870
+ return { replayed: false };
871
+ },
872
+ );
873
+ const committed = await this.requireDelivery(sessionArg, requestArg.deliveryId);
874
+ if (
875
+ committed.state !== 'acknowledged'
876
+ || committed.acknowledgedOutcome !== requestArg.outcome
877
+ ) {
878
+ throw new Error('CoreMail committed inbound acknowledgement is unavailable.');
879
+ }
880
+ return {
881
+ delivery: toPublicDelivery(committed),
882
+ replayed: transactionResult.replayed,
883
+ };
884
+ }
885
+
886
+ private async requireDelivery(
887
+ sessionArg: ICoreMailWorkloadSession,
888
+ deliveryIdArg: string,
889
+ ): Promise<TStoredInboundDelivery> {
890
+ const delivery = await this.models.InboundDelivery.exact.findStoredOne({
891
+ ...workloadFilter(sessionArg, deliveryIdArg),
892
+ });
893
+ if (!delivery) {
894
+ throw new Error('CoreMail inbound delivery is unavailable.');
895
+ }
896
+ return delivery;
897
+ }
898
+ }