563 lines
		
	
	
		
			19 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
			
		
		
	
	
			563 lines
		
	
	
		
			19 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
| import * as plugins from '../../plugins.ts';
 | |
| import type { IEmailDomainConfig } from './interfaces.ts';
 | |
| import { logger } from '../../logger.ts';
 | |
| import type { DcRouter } from '../../classes.mailer.ts';
 | |
| import type { StorageManager } from '../../storage/index.ts';
 | |
| 
 | |
| /**
 | |
|  * DNS validation result
 | |
|  */
 | |
| export interface IDnsValidationResult {
 | |
|   valid: boolean;
 | |
|   errors: string[];
 | |
|   warnings: string[];
 | |
|   requiredChanges: string[];
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * DNS records found for a domain
 | |
|  */
 | |
| interface IDnsRecords {
 | |
|   mx?: string[];
 | |
|   spf?: string;
 | |
|   dkim?: string;
 | |
|   dmarc?: string;
 | |
|   ns?: string[];
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Manages DNS configuration for email domains
 | |
|  * Handles both validation and creation of DNS records
 | |
|  */
 | |
| export class DnsManager {
 | |
|   private dcRouter: DcRouter;
 | |
|   private storageManager: StorageManager;
 | |
|   
 | |
|   constructor(dcRouter: DcRouter) {
 | |
|     this.dcRouter = dcRouter;
 | |
|     this.storageManager = dcRouter.storageManager;
 | |
|   }
 | |
|   
 | |
|   /**
 | |
|    * Validate all domain configurations
 | |
|    */
 | |
|   async validateAllDomains(domainConfigs: IEmailDomainConfig[]): Promise<Map<string, IDnsValidationResult>> {
 | |
|     const results = new Map<string, IDnsValidationResult>();
 | |
|     
 | |
|     for (const config of domainConfigs) {
 | |
|       const result = await this.validateDomain(config);
 | |
|       results.set(config.domain, result);
 | |
|     }
 | |
|     
 | |
|     return results;
 | |
|   }
 | |
|   
 | |
|   /**
 | |
|    * Validate a single domain configuration
 | |
|    */
 | |
|   async validateDomain(config: IEmailDomainConfig): Promise<IDnsValidationResult> {
 | |
|     switch (config.dnsMode) {
 | |
|       case 'forward':
 | |
|         return this.validateForwardMode(config);
 | |
|       case 'internal-dns':
 | |
|         return this.validateInternalDnsMode(config);
 | |
|       case 'external-dns':
 | |
|         return this.validateExternalDnsMode(config);
 | |
|       default:
 | |
|         return {
 | |
|           valid: false,
 | |
|           errors: [`Unknown DNS mode: ${config.dnsMode}`],
 | |
|           warnings: [],
 | |
|           requiredChanges: []
 | |
|         };
 | |
|     }
 | |
|   }
 | |
|   
 | |
|   /**
 | |
|    * Validate forward mode configuration
 | |
|    */
 | |
|   private async validateForwardMode(config: IEmailDomainConfig): Promise<IDnsValidationResult> {
 | |
|     const result: IDnsValidationResult = {
 | |
|       valid: true,
 | |
|       errors: [],
 | |
|       warnings: [],
 | |
|       requiredChanges: []
 | |
|     };
 | |
|     
 | |
|     // Forward mode doesn't require DNS validation by default
 | |
|     if (!config.dns?.forward?.skipDnsValidation) {
 | |
|       logger.log('info', `DNS validation skipped for forward mode domain: ${config.domain}`);
 | |
|     }
 | |
|     
 | |
|     // DKIM keys are still generated for consistency
 | |
|     result.warnings.push(
 | |
|       `Domain "${config.domain}" uses forward mode. DKIM keys will be generated but signing only happens if email is processed.`
 | |
|     );
 | |
|     
 | |
|     return result;
 | |
|   }
 | |
|   
 | |
|   /**
 | |
|    * Validate internal DNS mode configuration
 | |
|    */
 | |
|   private async validateInternalDnsMode(config: IEmailDomainConfig): Promise<IDnsValidationResult> {
 | |
|     const result: IDnsValidationResult = {
 | |
|       valid: true,
 | |
|       errors: [],
 | |
|       warnings: [],
 | |
|       requiredChanges: []
 | |
|     };
 | |
|     
 | |
|     // Check if DNS configuration is set up
 | |
|     const dnsNsDomains = this.dcRouter.options?.dnsNsDomains;
 | |
|     const dnsScopes = this.dcRouter.options?.dnsScopes;
 | |
|     
 | |
|     if (!dnsNsDomains || dnsNsDomains.length === 0) {
 | |
|       result.valid = false;
 | |
|       result.errors.push(
 | |
|         `Domain "${config.domain}" is configured to use internal DNS, but dnsNsDomains is not set in DcRouter configuration.`
 | |
|       );
 | |
|       console.error(
 | |
|         `❌ ERROR: Domain "${config.domain}" is configured to use internal DNS,\n` +
 | |
|         '   but dnsNsDomains is not set in DcRouter configuration.\n' +
 | |
|         '   Please configure dnsNsDomains to enable the DNS server.\n' +
 | |
|         '   Example: dnsNsDomains: ["ns1.myservice.com", "ns2.myservice.com"]'
 | |
|       );
 | |
|       return result;
 | |
|     }
 | |
|     
 | |
|     if (!dnsScopes || dnsScopes.length === 0) {
 | |
|       result.valid = false;
 | |
|       result.errors.push(
 | |
|         `Domain "${config.domain}" is configured to use internal DNS, but dnsScopes is not set in DcRouter configuration.`
 | |
|       );
 | |
|       console.error(
 | |
|         `❌ ERROR: Domain "${config.domain}" is configured to use internal DNS,\n` +
 | |
|         '   but dnsScopes is not set in DcRouter configuration.\n' +
 | |
|         '   Please configure dnsScopes to define authoritative domains.\n' +
 | |
|         '   Example: dnsScopes: ["myservice.com", "mail.myservice.com"]'
 | |
|       );
 | |
|       return result;
 | |
|     }
 | |
|     
 | |
|     // Check if the email domain is in dnsScopes
 | |
|     if (!dnsScopes.includes(config.domain)) {
 | |
|       result.valid = false;
 | |
|       result.errors.push(
 | |
|         `Domain "${config.domain}" is configured to use internal DNS, but is not included in dnsScopes.`
 | |
|       );
 | |
|       console.error(
 | |
|         `❌ ERROR: Domain "${config.domain}" is configured to use internal DNS,\n` +
 | |
|         `   but is not included in dnsScopes: [${dnsScopes.join(', ')}].\n` +
 | |
|         '   Please add this domain to dnsScopes to enable internal DNS.\n' +
 | |
|         `   Example: dnsScopes: [..., "${config.domain}"]`
 | |
|       );
 | |
|       return result;
 | |
|     }
 | |
|     
 | |
|     const primaryNameserver = dnsNsDomains[0];
 | |
|     
 | |
|     // Check NS delegation
 | |
|     try {
 | |
|       const nsRecords = await this.resolveNs(config.domain);
 | |
|       const delegatedNameservers = dnsNsDomains.filter(ns => nsRecords.includes(ns));
 | |
|       const isDelegated = delegatedNameservers.length > 0;
 | |
|       
 | |
|       if (!isDelegated) {
 | |
|         result.warnings.push(
 | |
|           `NS delegation not found for ${config.domain}. Please add NS records at your registrar.`
 | |
|         );
 | |
|         dnsNsDomains.forEach(ns => {
 | |
|           result.requiredChanges.push(
 | |
|             `Add NS record: ${config.domain}. NS ${ns}.`
 | |
|           );
 | |
|         });
 | |
|         
 | |
|         console.log(
 | |
|           `📋 DNS Delegation Required for ${config.domain}:\n` +
 | |
|           '━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n' +
 | |
|           'Please add these NS records at your domain registrar:\n' +
 | |
|           dnsNsDomains.map(ns => `  ${config.domain}. NS ${ns}.`).join('\n') + '\n' +
 | |
|           '━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n' +
 | |
|           'This delegation is required for internal DNS mode to work.'
 | |
|         );
 | |
|       } else {
 | |
|         console.log(
 | |
|           `✅ NS delegation verified: ${config.domain} -> [${delegatedNameservers.join(', ')}]`
 | |
|         );
 | |
|       }
 | |
|     } catch (error) {
 | |
|       result.warnings.push(
 | |
|         `Could not verify NS delegation for ${config.domain}: ${error.message}`
 | |
|       );
 | |
|     }
 | |
|     
 | |
|     return result;
 | |
|   }
 | |
|   
 | |
|   /**
 | |
|    * Validate external DNS mode configuration
 | |
|    */
 | |
|   private async validateExternalDnsMode(config: IEmailDomainConfig): Promise<IDnsValidationResult> {
 | |
|     const result: IDnsValidationResult = {
 | |
|       valid: true,
 | |
|       errors: [],
 | |
|       warnings: [],
 | |
|       requiredChanges: []
 | |
|     };
 | |
|     
 | |
|     try {
 | |
|       // Get current DNS records
 | |
|       const records = await this.checkDnsRecords(config);
 | |
|       const requiredRecords = config.dns?.external?.requiredRecords || ['MX', 'SPF', 'DKIM', 'DMARC'];
 | |
|       
 | |
|       // Check MX record
 | |
|       if (requiredRecords.includes('MX') && !records.mx?.length) {
 | |
|         result.requiredChanges.push(
 | |
|           `Add MX record: ${this.getBaseDomain(config.domain)} -> ${config.domain} (priority 10)`
 | |
|         );
 | |
|       }
 | |
|       
 | |
|       // Check SPF record
 | |
|       if (requiredRecords.includes('SPF') && !records.spf) {
 | |
|         result.requiredChanges.push(
 | |
|           `Add TXT record: ${this.getBaseDomain(config.domain)} -> "v=spf1 a mx ~all"`
 | |
|         );
 | |
|       }
 | |
|       
 | |
|       // Check DKIM record
 | |
|       if (requiredRecords.includes('DKIM') && !records.dkim) {
 | |
|         const selector = config.dkim?.selector || 'default';
 | |
|         const dkimPublicKey = await this.storageManager.get(`/email/dkim/${config.domain}/public.key`);
 | |
|         
 | |
|         if (dkimPublicKey) {
 | |
|           const publicKeyBase64 = dkimPublicKey
 | |
|             .replace(/-----BEGIN PUBLIC KEY-----/g, '')
 | |
|             .replace(/-----END PUBLIC KEY-----/g, '')
 | |
|             .replace(/\s/g, '');
 | |
|             
 | |
|           result.requiredChanges.push(
 | |
|             `Add TXT record: ${selector}._domainkey.${config.domain} -> "v=DKIM1; k=rsa; p=${publicKeyBase64}"`
 | |
|           );
 | |
|         } else {
 | |
|           result.warnings.push(
 | |
|             `DKIM public key not found for ${config.domain}. It will be generated on first use.`
 | |
|           );
 | |
|         }
 | |
|       }
 | |
|       
 | |
|       // Check DMARC record
 | |
|       if (requiredRecords.includes('DMARC') && !records.dmarc) {
 | |
|         result.requiredChanges.push(
 | |
|           `Add TXT record: _dmarc.${this.getBaseDomain(config.domain)} -> "v=DMARC1; p=none; rua=mailto:dmarc@${config.domain}"`
 | |
|         );
 | |
|       }
 | |
|       
 | |
|       // Show setup instructions if needed
 | |
|       if (result.requiredChanges.length > 0) {
 | |
|         console.log(
 | |
|           `📋 DNS Configuration Required for ${config.domain}:\n` +
 | |
|           '━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n' +
 | |
|           result.requiredChanges.map((change, i) => `${i + 1}. ${change}`).join('\n') +
 | |
|           '\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'
 | |
|         );
 | |
|       }
 | |
|       
 | |
|     } catch (error) {
 | |
|       result.errors.push(`DNS validation failed: ${error.message}`);
 | |
|       result.valid = false;
 | |
|     }
 | |
|     
 | |
|     return result;
 | |
|   }
 | |
|   
 | |
|   /**
 | |
|    * Check DNS records for a domain
 | |
|    */
 | |
|   private async checkDnsRecords(config: IEmailDomainConfig): Promise<IDnsRecords> {
 | |
|     const records: IDnsRecords = {};
 | |
|     const baseDomain = this.getBaseDomain(config.domain);
 | |
|     const selector = config.dkim?.selector || 'default';
 | |
|     
 | |
|     // Use custom DNS servers if specified
 | |
|     const resolver = new plugins.dns.promises.Resolver();
 | |
|     if (config.dns?.external?.servers?.length) {
 | |
|       resolver.setServers(config.dns.external.servers);
 | |
|     }
 | |
|     
 | |
|     // Check MX records
 | |
|     try {
 | |
|       const mxRecords = await resolver.resolveMx(baseDomain);
 | |
|       records.mx = mxRecords.map(mx => mx.exchange);
 | |
|     } catch (error) {
 | |
|       logger.log('debug', `No MX records found for ${baseDomain}`);
 | |
|     }
 | |
|     
 | |
|     // Check SPF record
 | |
|     try {
 | |
|       const txtRecords = await resolver.resolveTxt(baseDomain);
 | |
|       const spfRecord = txtRecords.find(records => 
 | |
|         records.some(record => record.startsWith('v=spf1'))
 | |
|       );
 | |
|       if (spfRecord) {
 | |
|         records.spf = spfRecord.join('');
 | |
|       }
 | |
|     } catch (error) {
 | |
|       logger.log('debug', `No SPF record found for ${baseDomain}`);
 | |
|     }
 | |
|     
 | |
|     // Check DKIM record
 | |
|     try {
 | |
|       const dkimRecords = await resolver.resolveTxt(`${selector}._domainkey.${config.domain}`);
 | |
|       const dkimRecord = dkimRecords.find(records => 
 | |
|         records.some(record => record.includes('v=DKIM1'))
 | |
|       );
 | |
|       if (dkimRecord) {
 | |
|         records.dkim = dkimRecord.join('');
 | |
|       }
 | |
|     } catch (error) {
 | |
|       logger.log('debug', `No DKIM record found for ${selector}._domainkey.${config.domain}`);
 | |
|     }
 | |
|     
 | |
|     // Check DMARC record
 | |
|     try {
 | |
|       const dmarcRecords = await resolver.resolveTxt(`_dmarc.${baseDomain}`);
 | |
|       const dmarcRecord = dmarcRecords.find(records => 
 | |
|         records.some(record => record.startsWith('v=DMARC1'))
 | |
|       );
 | |
|       if (dmarcRecord) {
 | |
|         records.dmarc = dmarcRecord.join('');
 | |
|       }
 | |
|     } catch (error) {
 | |
|       logger.log('debug', `No DMARC record found for _dmarc.${baseDomain}`);
 | |
|     }
 | |
|     
 | |
|     return records;
 | |
|   }
 | |
|   
 | |
|   /**
 | |
|    * Resolve NS records for a domain
 | |
|    */
 | |
|   private async resolveNs(domain: string): Promise<string[]> {
 | |
|     try {
 | |
|       const resolver = new plugins.dns.promises.Resolver();
 | |
|       const nsRecords = await resolver.resolveNs(domain);
 | |
|       return nsRecords;
 | |
|     } catch (error) {
 | |
|       logger.log('warn', `Failed to resolve NS records for ${domain}: ${error.message}`);
 | |
|       return [];
 | |
|     }
 | |
|   }
 | |
|   
 | |
|   /**
 | |
|    * Get base domain from email domain (e.g., mail.example.com -> example.com)
 | |
|    */
 | |
|   private getBaseDomain(domain: string): string {
 | |
|     const parts = domain.split('.');
 | |
|     if (parts.length <= 2) {
 | |
|       return domain;
 | |
|     }
 | |
|     
 | |
|     // For subdomains like mail.example.com, return example.com
 | |
|     // But preserve domain structure for longer TLDs like .co.uk
 | |
|     if (parts[parts.length - 2].length <= 3 && parts[parts.length - 1].length === 2) {
 | |
|       // Likely a country code TLD like .co.uk
 | |
|       return parts.slice(-3).join('.');
 | |
|     }
 | |
|     
 | |
|     return parts.slice(-2).join('.');
 | |
|   }
 | |
|   
 | |
|   /**
 | |
|    * Ensure all DNS records are created for configured domains
 | |
|    * This is the main entry point for DNS record management
 | |
|    */
 | |
|   async ensureDnsRecords(domainConfigs: IEmailDomainConfig[], dkimCreator?: any): Promise<void> {
 | |
|     logger.log('info', `Ensuring DNS records for ${domainConfigs.length} domains`);
 | |
|     
 | |
|     // First, validate all domains
 | |
|     const validationResults = await this.validateAllDomains(domainConfigs);
 | |
|     
 | |
|     // Then create records for internal-dns domains
 | |
|     const internalDnsDomains = domainConfigs.filter(config => config.dnsMode === 'internal-dns');
 | |
|     if (internalDnsDomains.length > 0) {
 | |
|       await this.createInternalDnsRecords(internalDnsDomains);
 | |
|       
 | |
|       // Create DKIM records if DKIMCreator is provided
 | |
|       if (dkimCreator) {
 | |
|         await this.createDkimRecords(domainConfigs, dkimCreator);
 | |
|       }
 | |
|     }
 | |
|     
 | |
|     // Log validation results for external-dns domains
 | |
|     for (const [domain, result] of validationResults) {
 | |
|       const config = domainConfigs.find(c => c.domain === domain);
 | |
|       if (config?.dnsMode === 'external-dns' && result.requiredChanges.length > 0) {
 | |
|         logger.log('warn', `External DNS configuration required for ${domain}`);
 | |
|       }
 | |
|     }
 | |
|   }
 | |
|   
 | |
|   /**
 | |
|    * Create DNS records for internal-dns mode domains
 | |
|    */
 | |
|   private async createInternalDnsRecords(domainConfigs: IEmailDomainConfig[]): Promise<void> {
 | |
|     // Check if DNS server is available
 | |
|     if (!this.dcRouter.dnsServer) {
 | |
|       logger.log('warn', 'DNS server not available, skipping internal DNS record creation');
 | |
|       return;
 | |
|     }
 | |
|     
 | |
|     logger.log('info', `Creating DNS records for ${domainConfigs.length} internal-dns domains`);
 | |
|     
 | |
|     for (const domainConfig of domainConfigs) {
 | |
|       const domain = domainConfig.domain;
 | |
|       const ttl = domainConfig.dns?.internal?.ttl || 3600;
 | |
|       const mxPriority = domainConfig.dns?.internal?.mxPriority || 10;
 | |
|       
 | |
|       try {
 | |
|         // 1. Register MX record - points to the email domain itself
 | |
|         this.dcRouter.dnsServer.registerHandler(
 | |
|           domain,
 | |
|           ['MX'],
 | |
|           () => ({
 | |
|             name: domain,
 | |
|             type: 'MX',
 | |
|             class: 'IN',
 | |
|             ttl: ttl,
 | |
|             data: {
 | |
|               priority: mxPriority,
 | |
|               exchange: domain
 | |
|             }
 | |
|           })
 | |
|         );
 | |
|         logger.log('info', `MX record registered for ${domain} -> ${domain} (priority ${mxPriority})`);
 | |
|         
 | |
|         // Store MX record in StorageManager
 | |
|         await this.storageManager.set(
 | |
|           `/email/dns/${domain}/mx`,
 | |
|           JSON.stringify({
 | |
|             type: 'MX',
 | |
|             priority: mxPriority,
 | |
|             exchange: domain,
 | |
|             ttl: ttl
 | |
|           })
 | |
|         );
 | |
|         
 | |
|         // 2. Register SPF record - allows the domain to send emails
 | |
|         const spfRecord = `v=spf1 a mx ~all`;
 | |
|         this.dcRouter.dnsServer.registerHandler(
 | |
|           domain,
 | |
|           ['TXT'],
 | |
|           () => ({
 | |
|             name: domain,
 | |
|             type: 'TXT',
 | |
|             class: 'IN',
 | |
|             ttl: ttl,
 | |
|             data: spfRecord
 | |
|           })
 | |
|         );
 | |
|         logger.log('info', `SPF record registered for ${domain}: "${spfRecord}"`);
 | |
|         
 | |
|         // Store SPF record in StorageManager
 | |
|         await this.storageManager.set(
 | |
|           `/email/dns/${domain}/spf`,
 | |
|           JSON.stringify({
 | |
|             type: 'TXT',
 | |
|             data: spfRecord,
 | |
|             ttl: ttl
 | |
|           })
 | |
|         );
 | |
|         
 | |
|         // 3. Register DMARC record - policy for handling email authentication
 | |
|         const dmarcRecord = `v=DMARC1; p=none; rua=mailto:dmarc@${domain}`;
 | |
|         this.dcRouter.dnsServer.registerHandler(
 | |
|           `_dmarc.${domain}`,
 | |
|           ['TXT'],
 | |
|           () => ({
 | |
|             name: `_dmarc.${domain}`,
 | |
|             type: 'TXT',
 | |
|             class: 'IN',
 | |
|             ttl: ttl,
 | |
|             data: dmarcRecord
 | |
|           })
 | |
|         );
 | |
|         logger.log('info', `DMARC record registered for _dmarc.${domain}: "${dmarcRecord}"`);
 | |
|         
 | |
|         // Store DMARC record in StorageManager
 | |
|         await this.storageManager.set(
 | |
|           `/email/dns/${domain}/dmarc`,
 | |
|           JSON.stringify({
 | |
|             type: 'TXT',
 | |
|             name: `_dmarc.${domain}`,
 | |
|             data: dmarcRecord,
 | |
|             ttl: ttl
 | |
|           })
 | |
|         );
 | |
|         
 | |
|         // Log summary of DNS records created
 | |
|         logger.log('info', `✅ DNS records created for ${domain}:
 | |
|   - MX: ${domain} (priority ${mxPriority})
 | |
|   - SPF: ${spfRecord}
 | |
|   - DMARC: ${dmarcRecord}
 | |
|   - DKIM: Will be created when keys are generated`);
 | |
|         
 | |
|       } catch (error) {
 | |
|         logger.log('error', `Failed to create DNS records for ${domain}: ${error.message}`);
 | |
|       }
 | |
|     }
 | |
|   }
 | |
|   
 | |
|   /**
 | |
|    * Create DKIM DNS records for all domains
 | |
|    */
 | |
|   private async createDkimRecords(domainConfigs: IEmailDomainConfig[], dkimCreator: any): Promise<void> {
 | |
|     for (const domainConfig of domainConfigs) {
 | |
|       const domain = domainConfig.domain;
 | |
|       const selector = domainConfig.dkim?.selector || 'default';
 | |
|       
 | |
|       try {
 | |
|         // Get DKIM DNS record from DKIMCreator
 | |
|         const dnsRecord = await dkimCreator.getDNSRecordForDomain(domain);
 | |
|         
 | |
|         // For internal-dns domains, register the DNS handler
 | |
|         if (domainConfig.dnsMode === 'internal-dns' && this.dcRouter.dnsServer) {
 | |
|           const ttl = domainConfig.dns?.internal?.ttl || 3600;
 | |
|           
 | |
|           this.dcRouter.dnsServer.registerHandler(
 | |
|             `${selector}._domainkey.${domain}`,
 | |
|             ['TXT'],
 | |
|             () => ({
 | |
|               name: `${selector}._domainkey.${domain}`,
 | |
|               type: 'TXT',
 | |
|               class: 'IN',
 | |
|               ttl: ttl,
 | |
|               data: dnsRecord.value
 | |
|             })
 | |
|           );
 | |
|           
 | |
|           logger.log('info', `DKIM DNS record registered for ${selector}._domainkey.${domain}`);
 | |
|           
 | |
|           // Store DKIM record in StorageManager
 | |
|           await this.storageManager.set(
 | |
|             `/email/dns/${domain}/dkim`,
 | |
|             JSON.stringify({
 | |
|               type: 'TXT',
 | |
|               name: `${selector}._domainkey.${domain}`,
 | |
|               data: dnsRecord.value,
 | |
|               ttl: ttl
 | |
|             })
 | |
|           );
 | |
|         }
 | |
|         
 | |
|         // For external-dns domains, just log what should be configured
 | |
|         if (domainConfig.dnsMode === 'external-dns') {
 | |
|           logger.log('info', `DKIM record for external DNS: ${dnsRecord.name} -> "${dnsRecord.value}"`);
 | |
|         }
 | |
|         
 | |
|       } catch (error) {
 | |
|         logger.log('warn', `Could not create DKIM DNS record for ${domain}: ${error.message}`);
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| } |