@push.rocks/smartmta 5.1.2 → 5.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.
Files changed (99) hide show
  1. package/changelog.md +14 -0
  2. package/dist_ts/00_commitinfo_data.d.ts +8 -0
  3. package/dist_ts/00_commitinfo_data.js +9 -0
  4. package/dist_ts/index.d.ts +3 -0
  5. package/dist_ts/index.js +4 -0
  6. package/dist_ts/logger.d.ts +17 -0
  7. package/dist_ts/logger.js +76 -0
  8. package/dist_ts/mail/core/classes.bouncemanager.d.ts +185 -0
  9. package/dist_ts/mail/core/classes.bouncemanager.js +569 -0
  10. package/dist_ts/mail/core/classes.email.d.ts +291 -0
  11. package/dist_ts/mail/core/classes.email.js +802 -0
  12. package/dist_ts/mail/core/classes.emailvalidator.d.ts +61 -0
  13. package/dist_ts/mail/core/classes.emailvalidator.js +184 -0
  14. package/dist_ts/mail/core/classes.templatemanager.d.ts +95 -0
  15. package/dist_ts/mail/core/classes.templatemanager.js +240 -0
  16. package/dist_ts/mail/core/index.d.ts +4 -0
  17. package/dist_ts/mail/core/index.js +6 -0
  18. package/dist_ts/mail/delivery/classes.delivery.queue.d.ts +163 -0
  19. package/dist_ts/mail/delivery/classes.delivery.queue.js +488 -0
  20. package/dist_ts/mail/delivery/classes.delivery.system.d.ts +160 -0
  21. package/dist_ts/mail/delivery/classes.delivery.system.js +630 -0
  22. package/dist_ts/mail/delivery/classes.unified.rate.limiter.d.ts +200 -0
  23. package/dist_ts/mail/delivery/classes.unified.rate.limiter.js +820 -0
  24. package/dist_ts/mail/delivery/index.d.ts +4 -0
  25. package/dist_ts/mail/delivery/index.js +6 -0
  26. package/dist_ts/mail/delivery/interfaces.d.ts +140 -0
  27. package/dist_ts/mail/delivery/interfaces.js +17 -0
  28. package/dist_ts/mail/index.d.ts +7 -0
  29. package/dist_ts/mail/index.js +12 -0
  30. package/dist_ts/mail/routing/classes.dkim.manager.d.ts +25 -0
  31. package/dist_ts/mail/routing/classes.dkim.manager.js +127 -0
  32. package/dist_ts/mail/routing/classes.dns.manager.d.ts +79 -0
  33. package/dist_ts/mail/routing/classes.dns.manager.js +415 -0
  34. package/dist_ts/mail/routing/classes.domain.registry.d.ts +54 -0
  35. package/dist_ts/mail/routing/classes.domain.registry.js +119 -0
  36. package/dist_ts/mail/routing/classes.email.action.executor.d.ts +33 -0
  37. package/dist_ts/mail/routing/classes.email.action.executor.js +137 -0
  38. package/dist_ts/mail/routing/classes.email.router.d.ts +171 -0
  39. package/dist_ts/mail/routing/classes.email.router.js +494 -0
  40. package/dist_ts/mail/routing/classes.unified.email.server.d.ts +241 -0
  41. package/dist_ts/mail/routing/classes.unified.email.server.js +935 -0
  42. package/dist_ts/mail/routing/index.d.ts +7 -0
  43. package/dist_ts/mail/routing/index.js +9 -0
  44. package/dist_ts/mail/routing/interfaces.d.ts +187 -0
  45. package/dist_ts/mail/routing/interfaces.js +2 -0
  46. package/dist_ts/mail/security/classes.dkimcreator.d.ts +72 -0
  47. package/dist_ts/mail/security/classes.dkimcreator.js +360 -0
  48. package/dist_ts/mail/security/classes.spfverifier.d.ts +62 -0
  49. package/dist_ts/mail/security/classes.spfverifier.js +87 -0
  50. package/dist_ts/mail/security/index.d.ts +2 -0
  51. package/dist_ts/mail/security/index.js +4 -0
  52. package/dist_ts/paths.d.ts +14 -0
  53. package/dist_ts/paths.js +39 -0
  54. package/dist_ts/plugins.d.ts +24 -0
  55. package/dist_ts/plugins.js +28 -0
  56. package/dist_ts/security/classes.contentscanner.d.ts +130 -0
  57. package/dist_ts/security/classes.contentscanner.js +338 -0
  58. package/dist_ts/security/classes.ipreputationchecker.d.ts +73 -0
  59. package/dist_ts/security/classes.ipreputationchecker.js +263 -0
  60. package/dist_ts/security/classes.rustsecuritybridge.d.ts +398 -0
  61. package/dist_ts/security/classes.rustsecuritybridge.js +484 -0
  62. package/dist_ts/security/classes.securitylogger.d.ts +140 -0
  63. package/dist_ts/security/classes.securitylogger.js +235 -0
  64. package/dist_ts/security/index.d.ts +4 -0
  65. package/dist_ts/security/index.js +5 -0
  66. package/package.json +6 -1
  67. package/readme.md +52 -9
  68. package/ts/00_commitinfo_data.ts +8 -0
  69. package/ts/index.ts +3 -0
  70. package/ts/logger.ts +91 -0
  71. package/ts/mail/core/classes.bouncemanager.ts +731 -0
  72. package/ts/mail/core/classes.email.ts +942 -0
  73. package/ts/mail/core/classes.emailvalidator.ts +239 -0
  74. package/ts/mail/core/classes.templatemanager.ts +320 -0
  75. package/ts/mail/core/index.ts +5 -0
  76. package/ts/mail/delivery/classes.delivery.queue.ts +645 -0
  77. package/ts/mail/delivery/classes.delivery.system.ts +816 -0
  78. package/ts/mail/delivery/classes.unified.rate.limiter.ts +1053 -0
  79. package/ts/mail/delivery/index.ts +5 -0
  80. package/ts/mail/delivery/interfaces.ts +167 -0
  81. package/ts/mail/index.ts +17 -0
  82. package/ts/mail/routing/classes.dkim.manager.ts +157 -0
  83. package/ts/mail/routing/classes.dns.manager.ts +573 -0
  84. package/ts/mail/routing/classes.domain.registry.ts +139 -0
  85. package/ts/mail/routing/classes.email.action.executor.ts +175 -0
  86. package/ts/mail/routing/classes.email.router.ts +575 -0
  87. package/ts/mail/routing/classes.unified.email.server.ts +1207 -0
  88. package/ts/mail/routing/index.ts +9 -0
  89. package/ts/mail/routing/interfaces.ts +202 -0
  90. package/ts/mail/security/classes.dkimcreator.ts +447 -0
  91. package/ts/mail/security/classes.spfverifier.ts +126 -0
  92. package/ts/mail/security/index.ts +3 -0
  93. package/ts/paths.ts +48 -0
  94. package/ts/plugins.ts +53 -0
  95. package/ts/security/classes.contentscanner.ts +400 -0
  96. package/ts/security/classes.ipreputationchecker.ts +315 -0
  97. package/ts/security/classes.rustsecuritybridge.ts +943 -0
  98. package/ts/security/classes.securitylogger.ts +299 -0
  99. package/ts/security/index.ts +40 -0
@@ -0,0 +1,573 @@
1
+ import * as plugins from '../../plugins.js';
2
+ import type { IEmailDomainConfig } from './interfaces.js';
3
+ import { logger } from '../../logger.js';
4
+ /** External DcRouter interface shape used by DnsManager */
5
+ interface IDcRouterLike {
6
+ storageManager: IStorageManagerLike;
7
+ dnsServer?: any;
8
+ options?: { dnsNsDomains?: string[]; dnsScopes?: string[] };
9
+ }
10
+
11
+ /** External StorageManager interface shape used by DnsManager */
12
+ interface IStorageManagerLike {
13
+ get(key: string): Promise<string | null>;
14
+ set(key: string, value: string): Promise<void>;
15
+ }
16
+
17
+ /**
18
+ * DNS validation result
19
+ */
20
+ export interface IDnsValidationResult {
21
+ valid: boolean;
22
+ errors: string[];
23
+ warnings: string[];
24
+ requiredChanges: string[];
25
+ }
26
+
27
+ /**
28
+ * DNS records found for a domain
29
+ */
30
+ interface IDnsRecords {
31
+ mx?: string[];
32
+ spf?: string;
33
+ dkim?: string;
34
+ dmarc?: string;
35
+ ns?: string[];
36
+ }
37
+
38
+ /**
39
+ * Manages DNS configuration for email domains
40
+ * Handles both validation and creation of DNS records
41
+ */
42
+ export class DnsManager {
43
+ private dcRouter: IDcRouterLike;
44
+ private storageManager: IStorageManagerLike;
45
+
46
+ constructor(dcRouter: IDcRouterLike) {
47
+ this.dcRouter = dcRouter;
48
+ this.storageManager = dcRouter.storageManager;
49
+ }
50
+
51
+ /**
52
+ * Validate all domain configurations
53
+ */
54
+ async validateAllDomains(domainConfigs: IEmailDomainConfig[]): Promise<Map<string, IDnsValidationResult>> {
55
+ const results = new Map<string, IDnsValidationResult>();
56
+
57
+ for (const config of domainConfigs) {
58
+ const result = await this.validateDomain(config);
59
+ results.set(config.domain, result);
60
+ }
61
+
62
+ return results;
63
+ }
64
+
65
+ /**
66
+ * Validate a single domain configuration
67
+ */
68
+ async validateDomain(config: IEmailDomainConfig): Promise<IDnsValidationResult> {
69
+ switch (config.dnsMode) {
70
+ case 'forward':
71
+ return this.validateForwardMode(config);
72
+ case 'internal-dns':
73
+ return this.validateInternalDnsMode(config);
74
+ case 'external-dns':
75
+ return this.validateExternalDnsMode(config);
76
+ default:
77
+ return {
78
+ valid: false,
79
+ errors: [`Unknown DNS mode: ${config.dnsMode}`],
80
+ warnings: [],
81
+ requiredChanges: []
82
+ };
83
+ }
84
+ }
85
+
86
+ /**
87
+ * Validate forward mode configuration
88
+ */
89
+ private async validateForwardMode(config: IEmailDomainConfig): Promise<IDnsValidationResult> {
90
+ const result: IDnsValidationResult = {
91
+ valid: true,
92
+ errors: [],
93
+ warnings: [],
94
+ requiredChanges: []
95
+ };
96
+
97
+ // Forward mode doesn't require DNS validation by default
98
+ if (!config.dns?.forward?.skipDnsValidation) {
99
+ logger.log('info', `DNS validation skipped for forward mode domain: ${config.domain}`);
100
+ }
101
+
102
+ // DKIM keys are still generated for consistency
103
+ result.warnings.push(
104
+ `Domain "${config.domain}" uses forward mode. DKIM keys will be generated but signing only happens if email is processed.`
105
+ );
106
+
107
+ return result;
108
+ }
109
+
110
+ /**
111
+ * Validate internal DNS mode configuration
112
+ */
113
+ private async validateInternalDnsMode(config: IEmailDomainConfig): Promise<IDnsValidationResult> {
114
+ const result: IDnsValidationResult = {
115
+ valid: true,
116
+ errors: [],
117
+ warnings: [],
118
+ requiredChanges: []
119
+ };
120
+
121
+ // Check if DNS configuration is set up
122
+ const dnsNsDomains = this.dcRouter.options?.dnsNsDomains;
123
+ const dnsScopes = this.dcRouter.options?.dnsScopes;
124
+
125
+ if (!dnsNsDomains || dnsNsDomains.length === 0) {
126
+ result.valid = false;
127
+ result.errors.push(
128
+ `Domain "${config.domain}" is configured to use internal DNS, but dnsNsDomains is not set in DcRouter configuration.`
129
+ );
130
+ console.error(
131
+ `❌ ERROR: Domain "${config.domain}" is configured to use internal DNS,\n` +
132
+ ' but dnsNsDomains is not set in DcRouter configuration.\n' +
133
+ ' Please configure dnsNsDomains to enable the DNS server.\n' +
134
+ ' Example: dnsNsDomains: ["ns1.myservice.com", "ns2.myservice.com"]'
135
+ );
136
+ return result;
137
+ }
138
+
139
+ if (!dnsScopes || dnsScopes.length === 0) {
140
+ result.valid = false;
141
+ result.errors.push(
142
+ `Domain "${config.domain}" is configured to use internal DNS, but dnsScopes is not set in DcRouter configuration.`
143
+ );
144
+ console.error(
145
+ `❌ ERROR: Domain "${config.domain}" is configured to use internal DNS,\n` +
146
+ ' but dnsScopes is not set in DcRouter configuration.\n' +
147
+ ' Please configure dnsScopes to define authoritative domains.\n' +
148
+ ' Example: dnsScopes: ["myservice.com", "mail.myservice.com"]'
149
+ );
150
+ return result;
151
+ }
152
+
153
+ // Check if the email domain is in dnsScopes
154
+ if (!dnsScopes.includes(config.domain)) {
155
+ result.valid = false;
156
+ result.errors.push(
157
+ `Domain "${config.domain}" is configured to use internal DNS, but is not included in dnsScopes.`
158
+ );
159
+ console.error(
160
+ `❌ ERROR: Domain "${config.domain}" is configured to use internal DNS,\n` +
161
+ ` but is not included in dnsScopes: [${dnsScopes.join(', ')}].\n` +
162
+ ' Please add this domain to dnsScopes to enable internal DNS.\n' +
163
+ ` Example: dnsScopes: [..., "${config.domain}"]`
164
+ );
165
+ return result;
166
+ }
167
+
168
+ const primaryNameserver = dnsNsDomains[0];
169
+
170
+ // Check NS delegation
171
+ try {
172
+ const nsRecords = await this.resolveNs(config.domain);
173
+ const delegatedNameservers = dnsNsDomains.filter(ns => nsRecords.includes(ns));
174
+ const isDelegated = delegatedNameservers.length > 0;
175
+
176
+ if (!isDelegated) {
177
+ result.warnings.push(
178
+ `NS delegation not found for ${config.domain}. Please add NS records at your registrar.`
179
+ );
180
+ dnsNsDomains.forEach(ns => {
181
+ result.requiredChanges.push(
182
+ `Add NS record: ${config.domain}. NS ${ns}.`
183
+ );
184
+ });
185
+
186
+ console.log(
187
+ `📋 DNS Delegation Required for ${config.domain}:\n` +
188
+ '━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n' +
189
+ 'Please add these NS records at your domain registrar:\n' +
190
+ dnsNsDomains.map(ns => ` ${config.domain}. NS ${ns}.`).join('\n') + '\n' +
191
+ '━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n' +
192
+ 'This delegation is required for internal DNS mode to work.'
193
+ );
194
+ } else {
195
+ console.log(
196
+ `✅ NS delegation verified: ${config.domain} -> [${delegatedNameservers.join(', ')}]`
197
+ );
198
+ }
199
+ } catch (error) {
200
+ result.warnings.push(
201
+ `Could not verify NS delegation for ${config.domain}: ${error.message}`
202
+ );
203
+ }
204
+
205
+ return result;
206
+ }
207
+
208
+ /**
209
+ * Validate external DNS mode configuration
210
+ */
211
+ private async validateExternalDnsMode(config: IEmailDomainConfig): Promise<IDnsValidationResult> {
212
+ const result: IDnsValidationResult = {
213
+ valid: true,
214
+ errors: [],
215
+ warnings: [],
216
+ requiredChanges: []
217
+ };
218
+
219
+ try {
220
+ // Get current DNS records
221
+ const records = await this.checkDnsRecords(config);
222
+ const requiredRecords = config.dns?.external?.requiredRecords || ['MX', 'SPF', 'DKIM', 'DMARC'];
223
+
224
+ // Check MX record
225
+ if (requiredRecords.includes('MX') && !records.mx?.length) {
226
+ result.requiredChanges.push(
227
+ `Add MX record: ${this.getBaseDomain(config.domain)} -> ${config.domain} (priority 10)`
228
+ );
229
+ }
230
+
231
+ // Check SPF record
232
+ if (requiredRecords.includes('SPF') && !records.spf) {
233
+ result.requiredChanges.push(
234
+ `Add TXT record: ${this.getBaseDomain(config.domain)} -> "v=spf1 a mx ~all"`
235
+ );
236
+ }
237
+
238
+ // Check DKIM record
239
+ if (requiredRecords.includes('DKIM') && !records.dkim) {
240
+ const selector = config.dkim?.selector || 'default';
241
+ const dkimPublicKey = await this.storageManager.get(`/email/dkim/${config.domain}/public.key`);
242
+
243
+ if (dkimPublicKey) {
244
+ const publicKeyBase64 = dkimPublicKey
245
+ .replace(/-----BEGIN PUBLIC KEY-----/g, '')
246
+ .replace(/-----END PUBLIC KEY-----/g, '')
247
+ .replace(/\s/g, '');
248
+
249
+ result.requiredChanges.push(
250
+ `Add TXT record: ${selector}._domainkey.${config.domain} -> "v=DKIM1; k=rsa; p=${publicKeyBase64}"`
251
+ );
252
+ } else {
253
+ result.warnings.push(
254
+ `DKIM public key not found for ${config.domain}. It will be generated on first use.`
255
+ );
256
+ }
257
+ }
258
+
259
+ // Check DMARC record
260
+ if (requiredRecords.includes('DMARC') && !records.dmarc) {
261
+ result.requiredChanges.push(
262
+ `Add TXT record: _dmarc.${this.getBaseDomain(config.domain)} -> "v=DMARC1; p=none; rua=mailto:dmarc@${config.domain}"`
263
+ );
264
+ }
265
+
266
+ // Show setup instructions if needed
267
+ if (result.requiredChanges.length > 0) {
268
+ console.log(
269
+ `📋 DNS Configuration Required for ${config.domain}:\n` +
270
+ '━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n' +
271
+ result.requiredChanges.map((change, i) => `${i + 1}. ${change}`).join('\n') +
272
+ '\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'
273
+ );
274
+ }
275
+
276
+ } catch (error) {
277
+ result.errors.push(`DNS validation failed: ${error.message}`);
278
+ result.valid = false;
279
+ }
280
+
281
+ return result;
282
+ }
283
+
284
+ /**
285
+ * Check DNS records for a domain
286
+ */
287
+ private async checkDnsRecords(config: IEmailDomainConfig): Promise<IDnsRecords> {
288
+ const records: IDnsRecords = {};
289
+ const baseDomain = this.getBaseDomain(config.domain);
290
+ const selector = config.dkim?.selector || 'default';
291
+
292
+ // Use custom DNS servers if specified
293
+ const resolver = new plugins.dns.promises.Resolver();
294
+ if (config.dns?.external?.servers?.length) {
295
+ resolver.setServers(config.dns.external.servers);
296
+ }
297
+
298
+ // Check MX records
299
+ try {
300
+ const mxRecords = await resolver.resolveMx(baseDomain);
301
+ records.mx = mxRecords.map(mx => mx.exchange);
302
+ } catch (error) {
303
+ logger.log('debug', `No MX records found for ${baseDomain}`);
304
+ }
305
+
306
+ // Check SPF record
307
+ try {
308
+ const txtRecords = await resolver.resolveTxt(baseDomain);
309
+ const spfRecord = txtRecords.find(records =>
310
+ records.some(record => record.startsWith('v=spf1'))
311
+ );
312
+ if (spfRecord) {
313
+ records.spf = spfRecord.join('');
314
+ }
315
+ } catch (error) {
316
+ logger.log('debug', `No SPF record found for ${baseDomain}`);
317
+ }
318
+
319
+ // Check DKIM record
320
+ try {
321
+ const dkimRecords = await resolver.resolveTxt(`${selector}._domainkey.${config.domain}`);
322
+ const dkimRecord = dkimRecords.find(records =>
323
+ records.some(record => record.includes('v=DKIM1'))
324
+ );
325
+ if (dkimRecord) {
326
+ records.dkim = dkimRecord.join('');
327
+ }
328
+ } catch (error) {
329
+ logger.log('debug', `No DKIM record found for ${selector}._domainkey.${config.domain}`);
330
+ }
331
+
332
+ // Check DMARC record
333
+ try {
334
+ const dmarcRecords = await resolver.resolveTxt(`_dmarc.${baseDomain}`);
335
+ const dmarcRecord = dmarcRecords.find(records =>
336
+ records.some(record => record.startsWith('v=DMARC1'))
337
+ );
338
+ if (dmarcRecord) {
339
+ records.dmarc = dmarcRecord.join('');
340
+ }
341
+ } catch (error) {
342
+ logger.log('debug', `No DMARC record found for _dmarc.${baseDomain}`);
343
+ }
344
+
345
+ return records;
346
+ }
347
+
348
+ /**
349
+ * Resolve NS records for a domain
350
+ */
351
+ private async resolveNs(domain: string): Promise<string[]> {
352
+ try {
353
+ const resolver = new plugins.dns.promises.Resolver();
354
+ const nsRecords = await resolver.resolveNs(domain);
355
+ return nsRecords;
356
+ } catch (error) {
357
+ logger.log('warn', `Failed to resolve NS records for ${domain}: ${error.message}`);
358
+ return [];
359
+ }
360
+ }
361
+
362
+ /**
363
+ * Get base domain from email domain (e.g., mail.example.com -> example.com)
364
+ */
365
+ private getBaseDomain(domain: string): string {
366
+ const parts = domain.split('.');
367
+ if (parts.length <= 2) {
368
+ return domain;
369
+ }
370
+
371
+ // For subdomains like mail.example.com, return example.com
372
+ // But preserve domain structure for longer TLDs like .co.uk
373
+ if (parts[parts.length - 2].length <= 3 && parts[parts.length - 1].length === 2) {
374
+ // Likely a country code TLD like .co.uk
375
+ return parts.slice(-3).join('.');
376
+ }
377
+
378
+ return parts.slice(-2).join('.');
379
+ }
380
+
381
+ /**
382
+ * Ensure all DNS records are created for configured domains
383
+ * This is the main entry point for DNS record management
384
+ */
385
+ async ensureDnsRecords(domainConfigs: IEmailDomainConfig[], dkimCreator?: any): Promise<void> {
386
+ logger.log('info', `Ensuring DNS records for ${domainConfigs.length} domains`);
387
+
388
+ // First, validate all domains
389
+ const validationResults = await this.validateAllDomains(domainConfigs);
390
+
391
+ // Then create records for internal-dns domains
392
+ const internalDnsDomains = domainConfigs.filter(config => config.dnsMode === 'internal-dns');
393
+ if (internalDnsDomains.length > 0) {
394
+ await this.createInternalDnsRecords(internalDnsDomains);
395
+
396
+ // Create DKIM records if DKIMCreator is provided
397
+ if (dkimCreator) {
398
+ await this.createDkimRecords(domainConfigs, dkimCreator);
399
+ }
400
+ }
401
+
402
+ // Log validation results for external-dns domains
403
+ for (const [domain, result] of validationResults) {
404
+ const config = domainConfigs.find(c => c.domain === domain);
405
+ if (config?.dnsMode === 'external-dns' && result.requiredChanges.length > 0) {
406
+ logger.log('warn', `External DNS configuration required for ${domain}`);
407
+ }
408
+ }
409
+ }
410
+
411
+ /**
412
+ * Create DNS records for internal-dns mode domains
413
+ */
414
+ private async createInternalDnsRecords(domainConfigs: IEmailDomainConfig[]): Promise<void> {
415
+ // Check if DNS server is available
416
+ if (!this.dcRouter.dnsServer) {
417
+ logger.log('warn', 'DNS server not available, skipping internal DNS record creation');
418
+ return;
419
+ }
420
+
421
+ logger.log('info', `Creating DNS records for ${domainConfigs.length} internal-dns domains`);
422
+
423
+ for (const domainConfig of domainConfigs) {
424
+ const domain = domainConfig.domain;
425
+ const ttl = domainConfig.dns?.internal?.ttl || 3600;
426
+ const mxPriority = domainConfig.dns?.internal?.mxPriority || 10;
427
+
428
+ try {
429
+ // 1. Register MX record - points to the email domain itself
430
+ this.dcRouter.dnsServer.registerHandler(
431
+ domain,
432
+ ['MX'],
433
+ () => ({
434
+ name: domain,
435
+ type: 'MX',
436
+ class: 'IN',
437
+ ttl: ttl,
438
+ data: {
439
+ priority: mxPriority,
440
+ exchange: domain
441
+ }
442
+ })
443
+ );
444
+ logger.log('info', `MX record registered for ${domain} -> ${domain} (priority ${mxPriority})`);
445
+
446
+ // Store MX record in StorageManager
447
+ await this.storageManager.set(
448
+ `/email/dns/${domain}/mx`,
449
+ JSON.stringify({
450
+ type: 'MX',
451
+ priority: mxPriority,
452
+ exchange: domain,
453
+ ttl: ttl
454
+ })
455
+ );
456
+
457
+ // 2. Register SPF record - allows the domain to send emails
458
+ const spfRecord = `v=spf1 a mx ~all`;
459
+ this.dcRouter.dnsServer.registerHandler(
460
+ domain,
461
+ ['TXT'],
462
+ () => ({
463
+ name: domain,
464
+ type: 'TXT',
465
+ class: 'IN',
466
+ ttl: ttl,
467
+ data: spfRecord
468
+ })
469
+ );
470
+ logger.log('info', `SPF record registered for ${domain}: "${spfRecord}"`);
471
+
472
+ // Store SPF record in StorageManager
473
+ await this.storageManager.set(
474
+ `/email/dns/${domain}/spf`,
475
+ JSON.stringify({
476
+ type: 'TXT',
477
+ data: spfRecord,
478
+ ttl: ttl
479
+ })
480
+ );
481
+
482
+ // 3. Register DMARC record - policy for handling email authentication
483
+ const dmarcRecord = `v=DMARC1; p=none; rua=mailto:dmarc@${domain}`;
484
+ this.dcRouter.dnsServer.registerHandler(
485
+ `_dmarc.${domain}`,
486
+ ['TXT'],
487
+ () => ({
488
+ name: `_dmarc.${domain}`,
489
+ type: 'TXT',
490
+ class: 'IN',
491
+ ttl: ttl,
492
+ data: dmarcRecord
493
+ })
494
+ );
495
+ logger.log('info', `DMARC record registered for _dmarc.${domain}: "${dmarcRecord}"`);
496
+
497
+ // Store DMARC record in StorageManager
498
+ await this.storageManager.set(
499
+ `/email/dns/${domain}/dmarc`,
500
+ JSON.stringify({
501
+ type: 'TXT',
502
+ name: `_dmarc.${domain}`,
503
+ data: dmarcRecord,
504
+ ttl: ttl
505
+ })
506
+ );
507
+
508
+ // Log summary of DNS records created
509
+ logger.log('info', `✅ DNS records created for ${domain}:
510
+ - MX: ${domain} (priority ${mxPriority})
511
+ - SPF: ${spfRecord}
512
+ - DMARC: ${dmarcRecord}
513
+ - DKIM: Will be created when keys are generated`);
514
+
515
+ } catch (error) {
516
+ logger.log('error', `Failed to create DNS records for ${domain}: ${error.message}`);
517
+ }
518
+ }
519
+ }
520
+
521
+ /**
522
+ * Create DKIM DNS records for all domains
523
+ */
524
+ private async createDkimRecords(domainConfigs: IEmailDomainConfig[], dkimCreator: any): Promise<void> {
525
+ for (const domainConfig of domainConfigs) {
526
+ const domain = domainConfig.domain;
527
+ const selector = domainConfig.dkim?.selector || 'default';
528
+
529
+ try {
530
+ // Get DKIM DNS record from DKIMCreator
531
+ const dnsRecord = await dkimCreator.getDNSRecordForDomain(domain);
532
+
533
+ // For internal-dns domains, register the DNS handler
534
+ if (domainConfig.dnsMode === 'internal-dns' && this.dcRouter.dnsServer) {
535
+ const ttl = domainConfig.dns?.internal?.ttl || 3600;
536
+
537
+ this.dcRouter.dnsServer.registerHandler(
538
+ `${selector}._domainkey.${domain}`,
539
+ ['TXT'],
540
+ () => ({
541
+ name: `${selector}._domainkey.${domain}`,
542
+ type: 'TXT',
543
+ class: 'IN',
544
+ ttl: ttl,
545
+ data: dnsRecord.value
546
+ })
547
+ );
548
+
549
+ logger.log('info', `DKIM DNS record registered for ${selector}._domainkey.${domain}`);
550
+
551
+ // Store DKIM record in StorageManager
552
+ await this.storageManager.set(
553
+ `/email/dns/${domain}/dkim`,
554
+ JSON.stringify({
555
+ type: 'TXT',
556
+ name: `${selector}._domainkey.${domain}`,
557
+ data: dnsRecord.value,
558
+ ttl: ttl
559
+ })
560
+ );
561
+ }
562
+
563
+ // For external-dns domains, just log what should be configured
564
+ if (domainConfig.dnsMode === 'external-dns') {
565
+ logger.log('info', `DKIM record for external DNS: ${dnsRecord.name} -> "${dnsRecord.value}"`);
566
+ }
567
+
568
+ } catch (error) {
569
+ logger.log('warn', `Could not create DKIM DNS record for ${domain}: ${error.message}`);
570
+ }
571
+ }
572
+ }
573
+ }
@@ -0,0 +1,139 @@
1
+ import type { IEmailDomainConfig } from './interfaces.js';
2
+ import { logger } from '../../logger.js';
3
+
4
+ /**
5
+ * Registry for email domain configurations
6
+ * Provides fast lookups and validation for domains
7
+ */
8
+ export class DomainRegistry {
9
+ private domains: Map<string, IEmailDomainConfig> = new Map();
10
+ private defaults: IEmailDomainConfig['dkim'] & {
11
+ dnsMode?: 'forward' | 'internal-dns' | 'external-dns';
12
+ rateLimits?: IEmailDomainConfig['rateLimits'];
13
+ };
14
+
15
+ constructor(
16
+ domainConfigs: IEmailDomainConfig[],
17
+ defaults?: {
18
+ dnsMode?: 'forward' | 'internal-dns' | 'external-dns';
19
+ dkim?: IEmailDomainConfig['dkim'];
20
+ rateLimits?: IEmailDomainConfig['rateLimits'];
21
+ }
22
+ ) {
23
+ // Set defaults
24
+ this.defaults = {
25
+ dnsMode: defaults?.dnsMode || 'external-dns',
26
+ ...this.getDefaultDkimConfig(),
27
+ ...defaults?.dkim,
28
+ rateLimits: defaults?.rateLimits
29
+ };
30
+
31
+ // Process and store domain configurations
32
+ for (const config of domainConfigs) {
33
+ const processedConfig = this.applyDefaults(config);
34
+ this.domains.set(config.domain.toLowerCase(), processedConfig);
35
+ logger.log('info', `Registered domain: ${config.domain} with DNS mode: ${processedConfig.dnsMode}`);
36
+ }
37
+ }
38
+
39
+ /**
40
+ * Get default DKIM configuration
41
+ */
42
+ private getDefaultDkimConfig(): IEmailDomainConfig['dkim'] {
43
+ return {
44
+ selector: 'default',
45
+ keySize: 2048,
46
+ rotateKeys: false,
47
+ rotationInterval: 90
48
+ };
49
+ }
50
+
51
+ /**
52
+ * Apply defaults to a domain configuration
53
+ */
54
+ private applyDefaults(config: IEmailDomainConfig): IEmailDomainConfig {
55
+ return {
56
+ ...config,
57
+ dnsMode: config.dnsMode || this.defaults.dnsMode!,
58
+ dkim: {
59
+ ...this.getDefaultDkimConfig(),
60
+ ...this.defaults,
61
+ ...config.dkim
62
+ },
63
+ rateLimits: {
64
+ ...this.defaults.rateLimits,
65
+ ...config.rateLimits,
66
+ outbound: {
67
+ ...this.defaults.rateLimits?.outbound,
68
+ ...config.rateLimits?.outbound
69
+ },
70
+ inbound: {
71
+ ...this.defaults.rateLimits?.inbound,
72
+ ...config.rateLimits?.inbound
73
+ }
74
+ }
75
+ };
76
+ }
77
+
78
+ /**
79
+ * Check if a domain is registered
80
+ */
81
+ isDomainRegistered(domain: string): boolean {
82
+ return this.domains.has(domain.toLowerCase());
83
+ }
84
+
85
+ /**
86
+ * Check if an email address belongs to a registered domain
87
+ */
88
+ isEmailRegistered(email: string): boolean {
89
+ const domain = this.extractDomain(email);
90
+ if (!domain) return false;
91
+ return this.isDomainRegistered(domain);
92
+ }
93
+
94
+ /**
95
+ * Get domain configuration
96
+ */
97
+ getDomainConfig(domain: string): IEmailDomainConfig | undefined {
98
+ return this.domains.get(domain.toLowerCase());
99
+ }
100
+
101
+ /**
102
+ * Get domain configuration for an email address
103
+ */
104
+ getEmailDomainConfig(email: string): IEmailDomainConfig | undefined {
105
+ const domain = this.extractDomain(email);
106
+ if (!domain) return undefined;
107
+ return this.getDomainConfig(domain);
108
+ }
109
+
110
+ /**
111
+ * Extract domain from email address
112
+ */
113
+ private extractDomain(email: string): string | null {
114
+ const parts = email.toLowerCase().split('@');
115
+ if (parts.length !== 2) return null;
116
+ return parts[1];
117
+ }
118
+
119
+ /**
120
+ * Get all registered domains
121
+ */
122
+ getAllDomains(): string[] {
123
+ return Array.from(this.domains.keys());
124
+ }
125
+
126
+ /**
127
+ * Get all domain configurations
128
+ */
129
+ getAllConfigs(): IEmailDomainConfig[] {
130
+ return Array.from(this.domains.values());
131
+ }
132
+
133
+ /**
134
+ * Get domains by DNS mode
135
+ */
136
+ getDomainsByMode(mode: 'forward' | 'internal-dns' | 'external-dns'): IEmailDomainConfig[] {
137
+ return Array.from(this.domains.values()).filter(config => config.dnsMode === mode);
138
+ }
139
+ }