@serve.zone/dcrouter 16.5.0 → 17.1.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 (153) hide show
  1. package/.smartconfig.json +5 -2
  2. package/deno.json +1 -1
  3. package/dist_serve/bundle.js +1316 -1151
  4. package/dist_ts/00_commitinfo_data.js +1 -1
  5. package/dist_ts/classes.dcrouter.d.ts +6 -1
  6. package/dist_ts/classes.dcrouter.js +119 -41
  7. package/dist_ts/db/classes.cache.cleaner.d.ts +3 -0
  8. package/dist_ts/db/classes.cache.cleaner.js +7 -3
  9. package/dist_ts/db/classes.dcrouter-db.js +1 -2
  10. package/dist_ts/db/documents/classes.cached.email.d.ts +41 -4
  11. package/dist_ts/db/documents/classes.cached.email.js +36 -5
  12. package/dist_ts/db/documents/classes.dns-record.doc.js +3 -1
  13. package/dist_ts/db/documents/classes.smartmta-storage.doc.d.ts +8 -0
  14. package/dist_ts/db/documents/classes.smartmta-storage.doc.js +91 -0
  15. package/dist_ts/db/documents/index.d.ts +1 -0
  16. package/dist_ts/db/documents/index.js +2 -1
  17. package/dist_ts/dns/classes.dns-server-runtime.d.ts +0 -1
  18. package/dist_ts/dns/classes.dns-server-runtime.js +4 -36
  19. package/dist_ts/dns/index.d.ts +1 -0
  20. package/dist_ts/dns/index.js +2 -1
  21. package/dist_ts/dns/manager.dns.d.ts +1 -0
  22. package/dist_ts/dns/manager.dns.js +49 -14
  23. package/dist_ts/dns/providers/cloudflare.provider.js +19 -4
  24. package/dist_ts/dns/txt-record-presentation.d.ts +20 -0
  25. package/dist_ts/dns/txt-record-presentation.js +131 -0
  26. package/dist_ts/email/classes.accepted-email-spool.d.ts +32 -3
  27. package/dist_ts/email/classes.accepted-email-spool.js +174 -13
  28. package/dist_ts/email/classes.email-domain.manager.d.ts +14 -1
  29. package/dist_ts/email/classes.email-domain.manager.js +151 -8
  30. package/dist_ts/email/classes.email-settings.manager.js +2 -1
  31. package/dist_ts/email/classes.mail-dns-sync.d.ts +31 -5
  32. package/dist_ts/email/classes.mail-dns-sync.js +508 -117
  33. package/dist_ts/email/classes.smartmta-blob-storage-manager.d.ts +27 -0
  34. package/dist_ts/email/classes.smartmta-blob-storage-manager.js +110 -0
  35. package/dist_ts/email/classes.smartmta-storage-manager.d.ts +6 -8
  36. package/dist_ts/email/classes.smartmta-storage-manager.js +63 -86
  37. package/dist_ts/email/classes.workapp-mail-manager.d.ts +1 -0
  38. package/dist_ts/email/classes.workapp-mail-manager.js +31 -4
  39. package/dist_ts/email/index.d.ts +4 -0
  40. package/dist_ts/email/index.js +5 -1
  41. package/dist_ts/email/mail-dns-txt-policy.d.ts +18 -0
  42. package/dist_ts/email/mail-dns-txt-policy.js +466 -0
  43. package/dist_ts/email/reconcile-mail-dns-singleton.d.ts +47 -0
  44. package/dist_ts/email/reconcile-mail-dns-singleton.js +159 -0
  45. package/dist_ts/email/smartmta-storage-policy.d.ts +9 -0
  46. package/dist_ts/email/smartmta-storage-policy.js +58 -0
  47. package/dist_ts/opsserver/classes.opsserver.d.ts +8 -0
  48. package/dist_ts/opsserver/classes.opsserver.js +34 -1
  49. package/dist_ts/opsserver/handlers/certificate.handler.js +25 -4
  50. package/dist_ts/opsserver/handlers/config.handler.js +9 -5
  51. package/dist_ts/opsserver/handlers/dns-provider.handler.js +19 -2
  52. package/dist_ts/opsserver/handlers/dns-record.handler.js +25 -4
  53. package/dist_ts/opsserver/handlers/domain.handler.js +37 -3
  54. package/dist_ts/opsserver/handlers/email-domain.handler.js +5 -7
  55. package/dist_ts/opsserver/handlers/email-ops.handler.d.ts +1 -0
  56. package/dist_ts/opsserver/handlers/email-ops.handler.js +26 -7
  57. package/dist_ts/opsserver/handlers/index.d.ts +1 -0
  58. package/dist_ts/opsserver/handlers/index.js +2 -1
  59. package/dist_ts/opsserver/handlers/logs.handler.d.ts +1 -1
  60. package/dist_ts/opsserver/handlers/logs.handler.js +3 -3
  61. package/dist_ts/opsserver/handlers/realtime.handler.d.ts +37 -0
  62. package/dist_ts/opsserver/handlers/realtime.handler.js +154 -0
  63. package/dist_ts/opsserver/handlers/remoteingress.handler.js +22 -1
  64. package/dist_ts/opsserver/handlers/route-management.handler.js +47 -5
  65. package/dist_ts/opsserver/handlers/stats.handler.js +3 -5
  66. package/dist_ts/paths.d.ts +0 -11
  67. package/dist_ts/paths.js +1 -22
  68. package/dist_ts/plugins.d.ts +2 -35
  69. package/dist_ts/plugins.js +3 -63
  70. package/dist_ts_interfaces/data/email-domain.d.ts +14 -0
  71. package/dist_ts_interfaces/requests/config.d.ts +1 -1
  72. package/dist_ts_interfaces/requests/email-ops.d.ts +34 -1
  73. package/dist_ts_interfaces/requests/index.d.ts +1 -0
  74. package/dist_ts_interfaces/requests/index.js +2 -1
  75. package/dist_ts_interfaces/requests/realtime.d.ts +38 -0
  76. package/dist_ts_interfaces/requests/realtime.js +11 -0
  77. package/dist_ts_migrations/index.d.ts +8 -0
  78. package/dist_ts_migrations/index.js +489 -12
  79. package/dist_ts_web/00_commitinfo_data.js +1 -1
  80. package/dist_ts_web/appstate/certificates.js +9 -2
  81. package/dist_ts_web/appstate/domains.js +16 -5
  82. package/dist_ts_web/appstate/email-domains.d.ts +3 -0
  83. package/dist_ts_web/appstate/email-domains.js +47 -14
  84. package/dist_ts_web/appstate/email-ops.d.ts +1 -0
  85. package/dist_ts_web/appstate/email-ops.js +20 -2
  86. package/dist_ts_web/appstate/realtime.d.ts +27 -0
  87. package/dist_ts_web/appstate/realtime.js +71 -0
  88. package/dist_ts_web/appstate/remoteingress.js +9 -3
  89. package/dist_ts_web/appstate/routes.js +16 -5
  90. package/dist_ts_web/appstate/runtime.js +135 -7
  91. package/dist_ts_web/appstate.d.ts +1 -0
  92. package/dist_ts_web/appstate.js +2 -1
  93. package/dist_ts_web/elements/email/email-domain-creation-progress.d.ts +33 -0
  94. package/dist_ts_web/elements/email/email-domain-creation-progress.js +257 -0
  95. package/dist_ts_web/elements/email/ops-view-email-domains.js +233 -68
  96. package/dist_ts_web/elements/email/ops-view-emails.d.ts +4 -2
  97. package/dist_ts_web/elements/email/ops-view-emails.js +25 -13
  98. package/dist_ts_web/elements/overview/ops-view-config.js +5 -5
  99. package/package.json +23 -19
  100. package/ts/00_commitinfo_data.ts +1 -1
  101. package/ts/classes.dcrouter.ts +136 -44
  102. package/ts/db/classes.cache.cleaner.ts +18 -3
  103. package/ts/db/classes.dcrouter-db.ts +0 -2
  104. package/ts/db/documents/classes.cached.email.ts +78 -4
  105. package/ts/db/documents/classes.dns-record.doc.ts +1 -0
  106. package/ts/db/documents/classes.smartmta-storage.doc.ts +25 -0
  107. package/ts/db/documents/index.ts +1 -0
  108. package/ts/dns/classes.dns-server-runtime.ts +9 -45
  109. package/ts/dns/index.ts +1 -0
  110. package/ts/dns/manager.dns.ts +50 -16
  111. package/ts/dns/providers/cloudflare.provider.ts +21 -3
  112. package/ts/dns/txt-record-presentation.ts +134 -0
  113. package/ts/email/classes.accepted-email-spool.ts +220 -14
  114. package/ts/email/classes.email-domain.manager.ts +201 -7
  115. package/ts/email/classes.email-settings.manager.ts +1 -0
  116. package/ts/email/classes.mail-dns-sync.ts +588 -122
  117. package/ts/email/classes.smartmta-blob-storage-manager.ts +139 -0
  118. package/ts/email/classes.smartmta-storage-manager.ts +86 -83
  119. package/ts/email/classes.workapp-mail-manager.ts +47 -4
  120. package/ts/email/index.ts +4 -0
  121. package/ts/email/mail-dns-txt-policy.ts +505 -0
  122. package/ts/email/reconcile-mail-dns-singleton.ts +224 -0
  123. package/ts/email/smartmta-storage-policy.ts +73 -0
  124. package/ts/opsserver/classes.opsserver.ts +42 -0
  125. package/ts/opsserver/handlers/certificate.handler.ts +24 -3
  126. package/ts/opsserver/handlers/config.handler.ts +9 -5
  127. package/ts/opsserver/handlers/dns-provider.handler.ts +18 -1
  128. package/ts/opsserver/handlers/dns-record.handler.ts +24 -3
  129. package/ts/opsserver/handlers/domain.handler.ts +36 -2
  130. package/ts/opsserver/handlers/email-domain.handler.ts +4 -5
  131. package/ts/opsserver/handlers/email-ops.handler.ts +29 -6
  132. package/ts/opsserver/handlers/index.ts +1 -0
  133. package/ts/opsserver/handlers/logs.handler.ts +2 -2
  134. package/ts/opsserver/handlers/realtime.handler.ts +222 -0
  135. package/ts/opsserver/handlers/remoteingress.handler.ts +21 -0
  136. package/ts/opsserver/handlers/route-management.handler.ts +46 -4
  137. package/ts/opsserver/handlers/stats.handler.ts +2 -4
  138. package/ts/paths.ts +0 -25
  139. package/ts/plugins.ts +2 -69
  140. package/ts_web/00_commitinfo_data.ts +1 -1
  141. package/ts_web/appstate/certificates.ts +6 -1
  142. package/ts_web/appstate/domains.ts +11 -4
  143. package/ts_web/appstate/email-domains.ts +68 -14
  144. package/ts_web/appstate/email-ops.ts +20 -1
  145. package/ts_web/appstate/realtime.ts +109 -0
  146. package/ts_web/appstate/remoteingress.ts +6 -2
  147. package/ts_web/appstate/routes.ts +11 -4
  148. package/ts_web/appstate/runtime.ts +176 -7
  149. package/ts_web/appstate.ts +1 -0
  150. package/ts_web/elements/email/email-domain-creation-progress.ts +340 -0
  151. package/ts_web/elements/email/ops-view-email-domains.ts +273 -74
  152. package/ts_web/elements/email/ops-view-emails.ts +23 -16
  153. package/ts_web/elements/overview/ops-view-config.ts +4 -4
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@serve.zone/dcrouter",
3
3
  "private": false,
4
- "version": "16.5.0",
4
+ "version": "17.1.0",
5
5
  "description": "A multifaceted routing service handling mail and SMS delivery functions.",
6
6
  "type": "module",
7
7
  "bin": {
@@ -27,47 +27,48 @@
27
27
  "dependencies": {
28
28
  "@api.global/typedrequest": "^3.3.2",
29
29
  "@api.global/typedrequest-interfaces": "^3.0.19",
30
- "@api.global/typedserver": "^8.5.3",
31
- "@api.global/typedsocket": "^4.1.4",
30
+ "@api.global/typedserver": "^8.6.4",
31
+ "@api.global/typedsocket": "^5.0.0",
32
32
  "@apiclient.xyz/cloudflare": "^7.1.0",
33
33
  "@design.estate/dees-catalog": "^3.96.3",
34
- "@design.estate/dees-element": "^2.2.4",
35
- "@idp.global/sdk": "^1.4.0",
34
+ "@design.estate/dees-element": "^2.4.0",
35
+ "@idp.global/sdk": "^14.3.0",
36
36
  "@push.rocks/lik": "^6.4.1",
37
37
  "@push.rocks/projectinfo": "^5.1.0",
38
38
  "@push.rocks/qenv": "^6.1.4",
39
39
  "@push.rocks/smartacme": "^9.5.0",
40
+ "@push.rocks/smartbucket": "^4.6.1",
40
41
  "@push.rocks/smartchallenge": "^1.1.0",
41
- "@push.rocks/smartdata": "^7.2.0",
42
+ "@push.rocks/smartdata": "^7.3.1",
42
43
  "@push.rocks/smartdb": "^2.12.3",
43
- "@push.rocks/smartdns": "^7.11.3",
44
- "@push.rocks/smartfs": "^1.5.1",
45
- "@push.rocks/smartguard": "^3.1.0",
46
- "@push.rocks/smartjwt": "^2.2.2",
44
+ "@push.rocks/smartdns": "^7.12.0",
45
+ "@push.rocks/smartfs": "^1.6.0",
46
+ "@push.rocks/smartguard": "^3.1.2",
47
+ "@push.rocks/smartjwt": "^2.5.1",
47
48
  "@push.rocks/smartlog": "^3.2.2",
48
49
  "@push.rocks/smartmcp": "^0.2.0",
49
50
  "@push.rocks/smartmetrics": "^3.0.3",
50
51
  "@push.rocks/smartmigration": "1.4.1",
51
- "@push.rocks/smartmta": "^6.5.2",
52
+ "@push.rocks/smartmta": "^8.0.1",
52
53
  "@push.rocks/smartnetwork": "^4.8.0",
53
54
  "@push.rocks/smartpath": "^6.0.0",
54
55
  "@push.rocks/smartpromise": "^4.2.4",
55
56
  "@push.rocks/smartproxy": "^27.17.5",
56
57
  "@push.rocks/smartradius": "^1.3.0",
57
- "@push.rocks/smartrequest": "^5.0.3",
58
- "@push.rocks/smartrx": "^3.0.10",
58
+ "@push.rocks/smartrequest": "^5.0.4",
59
+ "@push.rocks/smartrx": "^3.0.12",
59
60
  "@push.rocks/smartstate": "^2.3.1",
60
- "@push.rocks/smartunique": "^3.0.9",
61
- "@push.rocks/smartvpn": "1.20.0",
61
+ "@push.rocks/smartunique": "^3.0.10",
62
+ "@push.rocks/smartvpn": "1.21.0",
62
63
  "@push.rocks/taskbuffer": "^8.0.2",
63
- "@serve.zone/catalog": "^2.14.0",
64
- "@serve.zone/interfaces": "^11.2.0",
64
+ "@serve.zone/catalog": "^2.15.0",
65
+ "@serve.zone/interfaces": "^16.3.0",
65
66
  "@serve.zone/remoteingress": "^5.0.0",
66
67
  "@tsclass/tsclass": "^9.5.1",
67
68
  "@types/qrcode": "^1.5.6",
68
- "lru-cache": "^11.5.1",
69
+ "lru-cache": "^11.5.2",
69
70
  "qrcode": "^1.5.4",
70
- "uuid": "^14.0.0"
71
+ "uuid": "^14.0.1"
71
72
  },
72
73
  "keywords": [
73
74
  "mail service",
@@ -113,6 +114,9 @@
113
114
  ".smartconfig.json",
114
115
  "readme.md"
115
116
  ],
117
+ "engines": {
118
+ "node": ">=22"
119
+ },
116
120
  "scripts": {
117
121
  "test": "(tstest test/ --verbose --logfile --timeout 60)",
118
122
  "start": "(node ./cli.js)",
@@ -3,6 +3,6 @@
3
3
  */
4
4
  export const commitinfo = {
5
5
  name: '@serve.zone/dcrouter',
6
- version: '16.5.0',
6
+ version: '17.1.0',
7
7
  description: 'A multifaceted routing service handling mail and SMS delivery functions.'
8
8
  }
@@ -37,7 +37,7 @@ import { DnsManager } from './dns/manager.dns.js';
37
37
  import { DnsServerRuntime } from './dns/classes.dns-server-runtime.js';
38
38
  import { AcmeConfigManager } from './acme/manager.acme-config.js';
39
39
  import { SmartAcmeLifecycle } from './acme/classes.smartacme-lifecycle.js';
40
- import { AcceptedEmailSpool, EmailDomainManager, EmailRouteBuilder, EmailSettingsManager, MailDnsSync, MailEdgeEligibility, MailEgressCoordinator, ReleasedRemoteIngressEgressIdentitySource, SmartMtaStorageManager, WorkAppMailManager, type TSmartMtaQueueItemLike } from './email/index.js';
40
+ import { AcceptedEmailSpool, EmailDomainManager, EmailRouteBuilder, EmailSettingsManager, MailDnsSync, MailEdgeEligibility, MailEgressCoordinator, ReleasedRemoteIngressEgressIdentitySource, SmartMtaBlobStorageManager, SmartMtaStorageManager, WorkAppMailManager, type ISmartMtaBlobStorageConfig, type TSmartMtaQueueItemLike } from './email/index.js';
41
41
  import type { IRoute } from '../ts_interfaces/data/route-management.js';
42
42
  import type { IEmailOutboundEgressStatus, IEmailPortConfig, IEmailServerSettings, IEmailServerSettingsSeed, TEmailOutboundMode, TEmailServerSettingsUpdate } from '../ts_interfaces/data/email-settings.js';
43
43
  import type { IDcRouterRouteConfig, IRemoteIngressHubSettings, IRemoteIngressPerformanceConfig, TRemoteIngressHubSettingsUpdate } from '../ts_interfaces/data/remoteingress.js';
@@ -62,6 +62,9 @@ export interface IDcRouterOptions {
62
62
  */
63
63
  emailConfig?: IUnifiedEmailServerOptions;
64
64
 
65
+ /** SmartBucket configuration for durable SmartMTA queue and attachment blobs. */
66
+ emailBlobStorage?: ISmartMtaBlobStorageConfig;
67
+
65
68
  /** Outbound SMTP connection mode. Defaults to direct unless DB settings override it. */
66
69
  emailOutboundMode?: TEmailOutboundMode;
67
70
 
@@ -278,7 +281,13 @@ export class DcRouter {
278
281
  listener: (...args: any[]) => void;
279
282
  }> = [];
280
283
 
281
- public storageManager: IStorageManagerLike;
284
+ public storageManager!: IStorageManagerLike;
285
+ private smartMtaBlobStorageManager?: SmartMtaBlobStorageManager;
286
+ private databaseMigrationsReady = false;
287
+
288
+ public getSmartMtaBlobStorageManager(): SmartMtaBlobStorageManager | undefined {
289
+ return this.smartMtaBlobStorageManager;
290
+ }
282
291
 
283
292
  // Unified database (smartdata + LocalSmartDb or external MongoDB)
284
293
  public dcRouterDb?: DcRouterDb;
@@ -372,10 +381,6 @@ export class DcRouter {
372
381
 
373
382
  // Resolve all data paths from baseDir
374
383
  this.resolvedPaths = paths.resolvePaths(this.options.baseDir);
375
- paths.ensureDataDirectories(this.resolvedPaths);
376
- this.storageManager = new SmartMtaStorageManager(
377
- plugins.path.join(this.resolvedPaths.dataDir, 'smartmta-storage')
378
- );
379
384
  this.workAppMailManager = new WorkAppMailManager(this);
380
385
  this.acceptedEmailSpool = new AcceptedEmailSpool(this);
381
386
  const mailEgressIdentitySource = new ReleasedRemoteIngressEgressIdentitySource(this);
@@ -426,6 +431,7 @@ export class DcRouter {
426
431
  await this.setupDcRouterDb();
427
432
  })
428
433
  .withStop(async () => {
434
+ this.databaseMigrationsReady = false;
429
435
  if (this.cacheCleaner) {
430
436
  this.cacheCleaner.stop();
431
437
  this.cacheCleaner = undefined;
@@ -1012,6 +1018,7 @@ export class DcRouter {
1012
1018
  */
1013
1019
  private async setupDcRouterDb(): Promise<void> {
1014
1020
  logger.log('info', 'Setting up DcRouterDb...');
1021
+ this.databaseMigrationsReady = false;
1015
1022
 
1016
1023
  const dbConfig = this.options.dbConfig || {};
1017
1024
 
@@ -1023,33 +1030,70 @@ export class DcRouter {
1023
1030
  debug: false,
1024
1031
  });
1025
1032
 
1026
- await this.dcRouterDb.start();
1033
+ try {
1034
+ await this.dcRouterDb.start();
1035
+ if (this.options.emailBlobStorage && !this.smartMtaBlobStorageManager) {
1036
+ this.smartMtaBlobStorageManager = await SmartMtaBlobStorageManager.create(
1037
+ this.options.emailBlobStorage,
1038
+ );
1039
+ }
1027
1040
 
1028
- // Run any pending data migrations before anything else reads from the DB.
1029
- // This must complete before ConfigManagers loads profiles.
1030
- const migration = await createMigrationRunner(this.dcRouterDb.getDb(), commitinfo.version, {
1031
- remoteIngressHubSettings: this.getRemoteIngressHubSettingsMigrationSeed(),
1032
- emailServerSettings: this.getEmailSettingsMigrationSeed(),
1033
- });
1034
- const migrationResult = await migration.run();
1035
- if (migrationResult.stepsApplied.length > 0) {
1036
- logger.log('info',
1037
- `smartmigration: ${migrationResult.currentVersionBefore ?? 'fresh'} → ${migrationResult.currentVersionAfter} ` +
1038
- `(${migrationResult.stepsApplied.length} step(s) applied in ${migrationResult.totalDurationMs}ms)`,
1039
- );
1040
- } else if (migrationResult.wasFreshInstall) {
1041
- logger.log('info', `smartmigration: fresh install stamped to ${migrationResult.currentVersionAfter}`);
1042
- }
1041
+ // Run any pending data migrations before anything else reads from the DB.
1042
+ // This must complete before ConfigManagers loads profiles.
1043
+ const migration = await createMigrationRunner(this.dcRouterDb.getDb(), commitinfo.version, {
1044
+ remoteIngressHubSettings: this.getRemoteIngressHubSettingsMigrationSeed(),
1045
+ emailServerSettings: this.getEmailSettingsMigrationSeed(),
1046
+ legacySmartMtaStoragePath: plugins.path.join(
1047
+ this.resolvedPaths.dataDir,
1048
+ 'smartmta-storage',
1049
+ ),
1050
+ legacyRawMessageBlobStorage: this.smartMtaBlobStorageManager,
1051
+ });
1052
+ const migrationResult = await migration.run();
1053
+ if (migrationResult.stepsApplied.length > 0) {
1054
+ logger.log('info',
1055
+ `smartmigration: ${migrationResult.currentVersionBefore ?? 'fresh'} → ${migrationResult.currentVersionAfter} ` +
1056
+ `(${migrationResult.stepsApplied.length} step(s) applied in ${migrationResult.totalDurationMs}ms)`,
1057
+ );
1058
+ } else if (migrationResult.wasFreshInstall) {
1059
+ logger.log('info', `smartmigration: fresh install stamped to ${migrationResult.currentVersionAfter}`);
1060
+ }
1043
1061
 
1044
- // Start the cache cleaner for TTL-based document cleanup
1045
- const cleanupIntervalMs = (dbConfig.cleanupIntervalHours || 1) * 60 * 60 * 1000;
1046
- this.cacheCleaner = new CacheCleaner(this.dcRouterDb, {
1047
- intervalMs: cleanupIntervalMs,
1048
- verbose: false,
1049
- });
1050
- this.cacheCleaner.start();
1062
+ this.storageManager = new SmartMtaStorageManager(this.dcRouterDb.getDb());
1051
1063
 
1052
- logger.log('info', `DcRouterDb ready (${this.dcRouterDb.isEmbedded() ? 'embedded' : 'external'})`);
1064
+ // Start the cache cleaner for TTL-based document cleanup
1065
+ const cleanupIntervalMs = (dbConfig.cleanupIntervalHours || 1) * 60 * 60 * 1000;
1066
+ this.cacheCleaner = new CacheCleaner(this.dcRouterDb, {
1067
+ intervalMs: cleanupIntervalMs,
1068
+ verbose: false,
1069
+ beforeDeleteCachedEmail: async (cachedEmail) => {
1070
+ await this.acceptedEmailSpool.deleteRawMessage(cachedEmail);
1071
+ },
1072
+ });
1073
+ this.cacheCleaner.start();
1074
+
1075
+ this.databaseMigrationsReady = true;
1076
+ logger.log('info', `DcRouterDb ready (${this.dcRouterDb.isEmbedded() ? 'embedded' : 'external'})`);
1077
+ } catch (error) {
1078
+ this.databaseMigrationsReady = false;
1079
+ if (this.cacheCleaner) {
1080
+ this.cacheCleaner.stop();
1081
+ this.cacheCleaner = undefined;
1082
+ }
1083
+ const failedDb = this.dcRouterDb;
1084
+ this.dcRouterDb = undefined;
1085
+ try {
1086
+ await failedDb?.stop();
1087
+ } catch (stopError: unknown) {
1088
+ logger.log(
1089
+ 'warn',
1090
+ `Failed to stop DcRouterDb after startup migration failure: ${(stopError as Error).message}`,
1091
+ );
1092
+ } finally {
1093
+ DcRouterDb.resetInstance();
1094
+ }
1095
+ throw error;
1096
+ }
1053
1097
  }
1054
1098
 
1055
1099
  /**
@@ -1632,6 +1676,9 @@ export class DcRouter {
1632
1676
  }
1633
1677
 
1634
1678
  // Apply port mapping if behind SmartProxy
1679
+ if (!this.databaseMigrationsReady) {
1680
+ throw new Error('DcRouterDb migrations must complete before email acceptance');
1681
+ }
1635
1682
  const portMapping = this.options.emailPortConfig?.portMapping || {
1636
1683
  25: 10025, // SMTP
1637
1684
  587: 10587, // Submission
@@ -1648,14 +1695,28 @@ export class DcRouter {
1648
1695
  : mappedEmailPorts.includes(configuredSecurePort) && !baseEmailConfig.ports.includes(configuredSecurePort)
1649
1696
  ? configuredSecurePort
1650
1697
  : portMapping[configuredSecurePort] || configuredSecurePort + 10000;
1651
- const emailConfig: IUnifiedEmailServerOptions = await this.workAppMailManager.applyStoredIdentitiesToEmailConfig({
1698
+ const queueBehavior = { ...baseEmailConfig.queue } as Record<string, unknown>;
1699
+ delete queueBehavior.storageMode;
1700
+ delete queueBehavior.storageManager;
1701
+ delete queueBehavior.storageType;
1702
+ delete queueBehavior.persistentPath;
1703
+ if (!this.smartMtaBlobStorageManager) {
1704
+ throw new Error(
1705
+ 'emailBlobStorage is required for durable SmartMTA queue and raw RFC822 persistence',
1706
+ );
1707
+ }
1708
+ const queueOptions: IUnifiedEmailServerOptions['queue'] = {
1709
+ ...queueBehavior,
1710
+ storageMode: 'managed',
1711
+ storageManager: this.smartMtaBlobStorageManager,
1712
+ };
1713
+
1714
+ let emailConfig: IUnifiedEmailServerOptions = await this.workAppMailManager.applyStoredIdentitiesToEmailConfig({
1652
1715
  ...this.options.emailConfig,
1653
1716
  ports: mappedEmailPorts,
1717
+ dkimKeyProvisioning: 'caller-managed',
1654
1718
  persistRoutes: this.options.emailConfig.persistRoutes ?? false,
1655
- queue: {
1656
- ...this.options.emailConfig.queue,
1657
- storageType: 'memory',
1658
- },
1719
+ queue: queueOptions,
1659
1720
  outbound: {
1660
1721
  ...baseEmailConfig.outbound,
1661
1722
  connectionProxyProvider: (context) => this.mailEgressCoordinator.provideConnectionProxy(context),
@@ -1699,9 +1760,28 @@ export class DcRouter {
1699
1760
  });
1700
1761
 
1701
1762
  // Create unified email server
1702
- const emailServer = new UnifiedEmailServer(this, emailConfig);
1763
+ let emailServer = new UnifiedEmailServer(this, emailConfig);
1703
1764
  this.emailServer = emailServer;
1704
1765
  this.clearEmailEventSubscriptions();
1766
+ try {
1767
+ const repairResult = await this.emailDomainManager?.repairManagedDkimBeforeEmailStart();
1768
+ if (repairResult && repairResult.failedDomainIds.length > 0) {
1769
+ emailConfig = await this.workAppMailManager.applyStoredIdentitiesToEmailConfig({
1770
+ ...emailConfig,
1771
+ domains: [...(this.options.emailConfig?.domains || [])],
1772
+ });
1773
+ // DkimManager captures its DomainRegistry at construction. Rebuild the
1774
+ // unstarted server so per-domain repair failures are absent from
1775
+ // caller-managed DKIM startup validation.
1776
+ emailServer = new UnifiedEmailServer(this, emailConfig);
1777
+ this.emailServer = emailServer;
1778
+ }
1779
+ } catch (error) {
1780
+ if (this.emailServer === emailServer) {
1781
+ this.emailServer = undefined;
1782
+ }
1783
+ throw error;
1784
+ }
1705
1785
 
1706
1786
  // Set up error handling
1707
1787
  this.addEmailEventSubscription(emailServer, 'error', (err: Error) => {
@@ -1724,6 +1804,10 @@ export class DcRouter {
1724
1804
  // Start the server
1725
1805
  try {
1726
1806
  await emailServer.start();
1807
+ // Restore failed managed domains as inbound-only after DKIM validation.
1808
+ // Outbound routes and SMTP users stay absent through readiness gating.
1809
+ await this.emailDomainManager?.syncManagedDomainsToRuntime();
1810
+ await this.workAppMailManager.applyStoredIdentitiesToRuntime();
1727
1811
  } catch (error: unknown) {
1728
1812
  this.clearEmailEventSubscriptions();
1729
1813
  try {
@@ -1738,18 +1822,25 @@ export class DcRouter {
1738
1822
  }
1739
1823
 
1740
1824
  try {
1741
- this.addEmailEventSubscription(emailServer.deliveryQueue, 'itemEnqueued', (item: TSmartMtaQueueItemLike) => {
1742
- this.acceptedEmailSpool.trackQueueUpdate(item, 'queued', 'Unable to update accepted email after queue enqueue');
1825
+ this.addEmailEventSubscription(emailServer.deliveryQueue, 'itemEnqueued', async (item: TSmartMtaQueueItemLike) => {
1826
+ await this.acceptedEmailSpool.trackQueueUpdate(item, 'queued', 'Unable to update accepted email after queue enqueue');
1743
1827
  });
1744
- this.addEmailEventSubscription(emailServer.deliveryQueue, 'itemDelivered', (item: TSmartMtaQueueItemLike) => {
1745
- this.acceptedEmailSpool.trackQueueUpdate(item, 'delivered', 'Unable to mark accepted email delivered');
1828
+ this.addEmailEventSubscription(emailServer.deliveryQueue, 'itemDelivered', async (item: TSmartMtaQueueItemLike) => {
1829
+ await this.acceptedEmailSpool.trackQueueUpdate(item, 'delivered', 'Unable to mark accepted email delivered');
1746
1830
  });
1747
- this.addEmailEventSubscription(emailServer.deliveryQueue, 'itemDeferred', (item: TSmartMtaQueueItemLike) => {
1748
- this.acceptedEmailSpool.trackQueueUpdate(item, 'deferred', 'Unable to defer accepted email');
1831
+ this.addEmailEventSubscription(emailServer.deliveryQueue, 'itemDeferred', async (item: TSmartMtaQueueItemLike) => {
1832
+ await this.acceptedEmailSpool.trackQueueUpdate(item, 'deferred', 'Unable to defer accepted email');
1749
1833
  });
1750
- this.addEmailEventSubscription(emailServer.deliveryQueue, 'itemFailed', (item: TSmartMtaQueueItemLike) => {
1751
- this.acceptedEmailSpool.trackQueueUpdate(item, 'failed', 'Unable to mark accepted email failed');
1834
+ this.addEmailEventSubscription(emailServer.deliveryQueue, 'itemFailed', async (item: TSmartMtaQueueItemLike) => {
1835
+ await this.acceptedEmailSpool.trackQueueUpdate(item, 'failed', 'Unable to mark accepted email failed');
1752
1836
  });
1837
+ this.addEmailEventSubscription(
1838
+ emailServer.deliverySystem,
1839
+ 'smtpTransactionCompleted',
1840
+ async (transaction: import('./db/documents/classes.cached.email.js').ICachedEmailSmtpTransaction) => {
1841
+ await this.acceptedEmailSpool.trackSmtpTransaction(transaction, emailServer);
1842
+ },
1843
+ );
1753
1844
 
1754
1845
  // Wire delivery events to MetricsManager and logger using smartmta's public queue APIs.
1755
1846
  if (this.metricsManager) {
@@ -1801,6 +1892,7 @@ export class DcRouter {
1801
1892
  updateQueueSize();
1802
1893
  }
1803
1894
 
1895
+ await this.acceptedEmailSpool.recoverSmtpTransactionHistory(emailServer);
1804
1896
  await this.acceptedEmailSpool.recoverQueuedEmails();
1805
1897
  this.acceptedEmailSpool.start();
1806
1898
  } catch (error: unknown) {
@@ -14,6 +14,8 @@ export interface ICacheCleanerOptions {
14
14
  intervalMs?: number;
15
15
  /** Enable verbose logging */
16
16
  verbose?: boolean;
17
+ /** Delete externally stored raw payloads before removing a CachedEmail row. */
18
+ beforeDeleteCachedEmail?: (cachedEmailArg: CachedEmail) => Promise<void>;
17
19
  }
18
20
 
19
21
  /**
@@ -25,7 +27,11 @@ export interface ICacheCleanerOptions {
25
27
  export class CacheCleaner {
26
28
  private cleanupInterval: ReturnType<typeof setInterval> | null = null;
27
29
  private isRunning: boolean = false;
28
- private options: Required<ICacheCleanerOptions>;
30
+ private options: {
31
+ intervalMs: number;
32
+ verbose: boolean;
33
+ beforeDeleteCachedEmail?: (cachedEmailArg: CachedEmail) => Promise<void>;
34
+ };
29
35
  private dcRouterDb: DcRouterDb;
30
36
 
31
37
  constructor(dcRouterDb: DcRouterDb, options: ICacheCleanerOptions = {}) {
@@ -33,6 +39,7 @@ export class CacheCleaner {
33
39
  this.options = {
34
40
  intervalMs: options.intervalMs || 60 * 60 * 1000, // 1 hour default
35
41
  verbose: options.verbose || false,
42
+ beforeDeleteCachedEmail: options.beforeDeleteCachedEmail,
36
43
  };
37
44
  }
38
45
 
@@ -95,7 +102,11 @@ export class CacheCleaner {
95
102
  const results: { collection: string; deleted: number }[] = [];
96
103
 
97
104
  try {
98
- const emailsDeleted = await this.cleanExpiredDocuments(CachedEmail, now);
105
+ const emailsDeleted = await this.cleanExpiredDocuments(
106
+ CachedEmail,
107
+ now,
108
+ this.options.beforeDeleteCachedEmail,
109
+ );
99
110
  results.push({ collection: 'CachedEmail', deleted: emailsDeleted });
100
111
 
101
112
  const ipReputationDeleted = await this.cleanExpiredDocuments(CachedIPReputation, now);
@@ -124,7 +135,8 @@ export class CacheCleaner {
124
135
  */
125
136
  private async cleanExpiredDocuments<T extends { delete: () => Promise<void> }>(
126
137
  documentClass: { getInstances: (filter: any) => Promise<T[]> },
127
- now: Date
138
+ now: Date,
139
+ beforeDeleteArg?: (documentArg: T) => Promise<void>,
128
140
  ): Promise<number> {
129
141
  try {
130
142
  // Find all expired documents
@@ -136,6 +148,9 @@ export class CacheCleaner {
136
148
  let deletedCount = 0;
137
149
  for (const doc of expiredDocs) {
138
150
  try {
151
+ if (beforeDeleteArg) {
152
+ await beforeDeleteArg(doc);
153
+ }
139
154
  await doc.delete();
140
155
  deletedCount++;
141
156
  } catch (deleteError: unknown) {
@@ -80,8 +80,6 @@ export class DcRouterDb {
80
80
  logger.log('info', `DcRouterDb connecting to external MongoDB`);
81
81
  } else {
82
82
  // Embedded LocalSmartDb mode
83
- await plugins.fsUtils.ensureDir(this.options.storagePath);
84
-
85
83
  this.localSmartDb = new plugins.smartdb.LocalSmartDb({
86
84
  folderPath: this.options.storagePath,
87
85
  });
@@ -14,6 +14,54 @@ export type TCachedEmailStatus = 'pending' | 'processing' | 'queued' | 'delivere
14
14
  */
15
15
  export type TCachedEmailDirection = 'inbound' | 'outbound';
16
16
 
17
+ export type TCachedEmailSmtpDeliveryPhase =
18
+ | 'connect'
19
+ | 'greeting'
20
+ | 'ehlo'
21
+ | 'starttls'
22
+ | 'tls_handshake'
23
+ | 'post_tls_ehlo'
24
+ | 'auth'
25
+ | 'mail_from'
26
+ | 'rcpt_to'
27
+ | 'data_command'
28
+ | 'message_body'
29
+ | 'final_response'
30
+ | 'rset'
31
+ | 'quit'
32
+ | 'unknown';
33
+
34
+ export interface ICachedEmailSmtpTransaction {
35
+ id: string;
36
+ queueItemId: string;
37
+ queueAttempt: number;
38
+ targetHost: string;
39
+ targetPort: number;
40
+ recipientDomain?: string;
41
+ recipients: string[];
42
+ startedAt: string;
43
+ completedAt: string;
44
+ durationMs: number;
45
+ outcome: 'succeeded' | 'failed';
46
+ retryable: boolean;
47
+ errorType?: string;
48
+ smtpCode?: number;
49
+ recipientResults?: Array<{
50
+ recipient: string;
51
+ accepted: boolean;
52
+ responseCode: number;
53
+ }>;
54
+ failurePhase?: TCachedEmailSmtpDeliveryPhase;
55
+ error?: string;
56
+ transcript: Array<{
57
+ timestampMs: number;
58
+ phase: TCachedEmailSmtpDeliveryPhase;
59
+ direction: 'client' | 'server' | 'system';
60
+ text: string;
61
+ responseCode?: number;
62
+ }>;
63
+ }
64
+
17
65
  /**
18
66
  * Helper to get the smartdata database instance
19
67
  */
@@ -70,11 +118,21 @@ export class CachedEmail extends plugins.smartdata.SmartdataCachedDocument<Cache
70
118
  @plugins.smartdata.svDb()
71
119
  public subject!: string;
72
120
 
73
- /**
74
- * Raw RFC822 email content
75
- */
121
+ /** Legacy inline RFC822 content. New messages use rawContentObjectKey. */
122
+ @plugins.smartdata.svDb()
123
+ public rawContent?: string;
124
+
125
+ /** SmartBucket object key containing the raw RFC822 message. */
126
+ @plugins.smartdata.svDb()
127
+ public rawContentObjectKey?: string;
128
+
129
+ /** Raw RFC822 message size in bytes. */
76
130
  @plugins.smartdata.svDb()
77
- public rawContent!: string;
131
+ public rawContentSize: number = 0;
132
+
133
+ /** Bounded durable SMTP transaction history copied from SmartMTA. */
134
+ @plugins.smartdata.svDb()
135
+ public smtpTransactions: ICachedEmailSmtpTransaction[] = [];
78
136
 
79
137
  /**
80
138
  * Current status of the email
@@ -164,6 +222,14 @@ export class CachedEmail extends plugins.smartdata.SmartdataCachedDocument<Cache
164
222
  this.bcc = [];
165
223
  }
166
224
 
225
+ public override async createSavableObject(): Promise<CachedEmail> {
226
+ const savableObject = await super.createSavableObject();
227
+ if (this.rawContentObjectKey) {
228
+ delete savableObject.rawContent;
229
+ }
230
+ return savableObject;
231
+ }
232
+
167
233
  /**
168
234
  * Create a new CachedEmail with a unique ID
169
235
  */
@@ -286,6 +352,14 @@ export class CachedEmail extends plugins.smartdata.SmartdataCachedDocument<Cache
286
352
  this.lastError = error;
287
353
  }
288
354
 
355
+ public appendSmtpTransaction(transactionArg: ICachedEmailSmtpTransaction): void {
356
+ const transactions = (this.smtpTransactions || []).filter(
357
+ (transaction) => transaction.id !== transactionArg.id,
358
+ );
359
+ transactions.push(structuredClone(transactionArg));
360
+ this.smtpTransactions = transactions.slice(-20);
361
+ }
362
+
289
363
  /**
290
364
  * Increment attempt counter and schedule next attempt
291
365
  */
@@ -61,6 +61,7 @@ export class DnsRecordDoc extends plugins.smartdata.SmartDataDbDoc<DnsRecordDoc,
61
61
  if (!this.managedBy) delete (savable as any).managedBy;
62
62
  if (!this.managedOwnerId) delete (savable as any).managedOwnerId;
63
63
  if (!this.managedRecordKey) delete (savable as any).managedRecordKey;
64
+ if (!this.providerRecordId?.trim()) delete (savable as any).providerRecordId;
64
65
  return savable;
65
66
  }
66
67
 
@@ -0,0 +1,25 @@
1
+ import * as plugins from '../../plugins.js';
2
+ import { DcRouterDb } from '../classes.dcrouter-db.js';
3
+
4
+ const getDb = () => DcRouterDb.getInstance().getDb();
5
+
6
+ @plugins.smartdata.Collection(() => getDb())
7
+ export class SmartMtaStorageDoc extends plugins.smartdata.SmartDataDbDoc<
8
+ SmartMtaStorageDoc,
9
+ SmartMtaStorageDoc
10
+ > {
11
+ @plugins.smartdata.svDb()
12
+ public id!: string;
13
+
14
+ @plugins.smartdata.svDb()
15
+ public key!: string;
16
+
17
+ @plugins.smartdata.svDb()
18
+ public value!: string;
19
+
20
+ @plugins.smartdata.svDb()
21
+ public createdAt!: string;
22
+
23
+ @plugins.smartdata.svDb()
24
+ public updatedAt!: string;
25
+ }
@@ -41,3 +41,4 @@ export * from './classes.acme-config.doc.js';
41
41
  // Email domain management
42
42
  export * from './classes.email-domain.doc.js';
43
43
  export * from './classes.email-server-settings.doc.js';
44
+ export * from './classes.smartmta-storage.doc.js';