| 
									
										
										
										
											2025-10-24 08:09:29 +00:00
										 |  |  | import * as plugins from '../../plugins.ts'; | 
					
						
							|  |  |  | import * as paths from '../../paths.ts'; | 
					
						
							|  |  |  | import { logger } from '../../logger.ts'; | 
					
						
							|  |  |  | import {  | 
					
						
							|  |  |  |   SecurityLogger,  | 
					
						
							|  |  |  |   SecurityLogLevel,  | 
					
						
							|  |  |  |   SecurityEventType  | 
					
						
							|  |  |  | } from '../../security/index.ts'; | 
					
						
							|  |  |  | import { DKIMCreator } from '../security/classes.dkimcreator.ts'; | 
					
						
							|  |  |  | import { IPReputationChecker } from '../../security/classes.ipreputationchecker.ts'; | 
					
						
							|  |  |  | import {  | 
					
						
							|  |  |  |   IPWarmupManager,  | 
					
						
							|  |  |  |   type IIPWarmupConfig, | 
					
						
							|  |  |  |   SenderReputationMonitor, | 
					
						
							|  |  |  |   type IReputationMonitorConfig | 
					
						
							|  |  |  | } from '../../deliverability/index.ts'; | 
					
						
							|  |  |  | import { EmailRouter } from './classes.email.router.ts'; | 
					
						
							|  |  |  | import type { IEmailRoute, IEmailAction, IEmailContext, IEmailDomainConfig } from './interfaces.ts'; | 
					
						
							|  |  |  | import { Email } from '../core/classes.email.ts'; | 
					
						
							|  |  |  | import { DomainRegistry } from './classes.domain.registry.ts'; | 
					
						
							|  |  |  | import { DnsManager } from './classes.dns.manager.ts'; | 
					
						
							|  |  |  | import { BounceManager, BounceType, BounceCategory } from '../core/classes.bouncemanager.ts'; | 
					
						
							|  |  |  | import { createSmtpServer } from '../delivery/smtpserver/index.ts'; | 
					
						
							|  |  |  | import { createPooledSmtpClient } from '../delivery/smtpclient/create-client.ts'; | 
					
						
							|  |  |  | import type { SmtpClient } from '../delivery/smtpclient/smtp-client.ts'; | 
					
						
							|  |  |  | import { MultiModeDeliverySystem, type IMultiModeDeliveryOptions } from '../delivery/classes.delivery.system.ts'; | 
					
						
							|  |  |  | import { UnifiedDeliveryQueue, type IQueueOptions } from '../delivery/classes.delivery.queue.ts'; | 
					
						
							|  |  |  | import { UnifiedRateLimiter, type IHierarchicalRateLimits } from '../delivery/classes.unified.rate.limiter.ts'; | 
					
						
							|  |  |  | import { SmtpState } from '../delivery/interfaces.ts'; | 
					
						
							|  |  |  | import type { EmailProcessingMode, ISmtpSession as IBaseSmtpSession } from '../delivery/interfaces.ts'; | 
					
						
							|  |  |  | import type { DcRouter } from '../../classes.mailer.ts'; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /** | 
					
						
							|  |  |  |  * Extended SMTP session interface with route information | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | export interface IExtendedSmtpSession extends ISmtpSession { | 
					
						
							|  |  |  |   /** | 
					
						
							|  |  |  |    * Matched route for this session | 
					
						
							|  |  |  |    */ | 
					
						
							|  |  |  |   matchedRoute?: IEmailRoute; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /** | 
					
						
							|  |  |  |  * Options for the unified email server | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | export interface IUnifiedEmailServerOptions { | 
					
						
							|  |  |  |   // Base server options
 | 
					
						
							|  |  |  |   ports: number[]; | 
					
						
							|  |  |  |   hostname: string; | 
					
						
							|  |  |  |   domains: IEmailDomainConfig[];  // Domain configurations
 | 
					
						
							|  |  |  |   banner?: string; | 
					
						
							|  |  |  |   debug?: boolean; | 
					
						
							|  |  |  |   useSocketHandler?: boolean;  // Use socket-handler mode instead of port listening
 | 
					
						
							|  |  |  |    | 
					
						
							|  |  |  |   // Authentication options
 | 
					
						
							|  |  |  |   auth?: { | 
					
						
							|  |  |  |     required?: boolean; | 
					
						
							|  |  |  |     methods?: ('PLAIN' | 'LOGIN' | 'OAUTH2')[]; | 
					
						
							|  |  |  |     users?: Array<{username: string, password: string}>; | 
					
						
							|  |  |  |   }; | 
					
						
							|  |  |  |    | 
					
						
							|  |  |  |   // TLS options
 | 
					
						
							|  |  |  |   tls?: { | 
					
						
							|  |  |  |     certPath?: string; | 
					
						
							|  |  |  |     keyPath?: string; | 
					
						
							|  |  |  |     caPath?: string; | 
					
						
							|  |  |  |     minVersion?: string; | 
					
						
							|  |  |  |     ciphers?: string; | 
					
						
							|  |  |  |   }; | 
					
						
							|  |  |  |    | 
					
						
							|  |  |  |   // Limits
 | 
					
						
							|  |  |  |   maxMessageSize?: number; | 
					
						
							|  |  |  |   maxClients?: number; | 
					
						
							|  |  |  |   maxConnections?: number; | 
					
						
							|  |  |  |    | 
					
						
							|  |  |  |   // Connection options
 | 
					
						
							|  |  |  |   connectionTimeout?: number; | 
					
						
							|  |  |  |   socketTimeout?: number; | 
					
						
							|  |  |  |    | 
					
						
							|  |  |  |   // Email routing rules
 | 
					
						
							|  |  |  |   routes: IEmailRoute[]; | 
					
						
							|  |  |  |    | 
					
						
							|  |  |  |   // Global defaults for all domains
 | 
					
						
							|  |  |  |   defaults?: { | 
					
						
							|  |  |  |     dnsMode?: 'forward' | 'internal-dns' | 'external-dns'; | 
					
						
							|  |  |  |     dkim?: IEmailDomainConfig['dkim']; | 
					
						
							|  |  |  |     rateLimits?: IEmailDomainConfig['rateLimits']; | 
					
						
							|  |  |  |   }; | 
					
						
							|  |  |  |    | 
					
						
							|  |  |  |   // Outbound settings
 | 
					
						
							|  |  |  |   outbound?: { | 
					
						
							|  |  |  |     maxConnections?: number; | 
					
						
							|  |  |  |     connectionTimeout?: number; | 
					
						
							|  |  |  |     socketTimeout?: number; | 
					
						
							|  |  |  |     retryAttempts?: number; | 
					
						
							|  |  |  |     defaultFrom?: string; | 
					
						
							|  |  |  |   }; | 
					
						
							|  |  |  |    | 
					
						
							|  |  |  |   // Rate limiting (global limits, can be overridden per domain)
 | 
					
						
							|  |  |  |   rateLimits?: IHierarchicalRateLimits; | 
					
						
							|  |  |  |    | 
					
						
							|  |  |  |   // Deliverability options
 | 
					
						
							|  |  |  |   ipWarmupConfig?: IIPWarmupConfig; | 
					
						
							|  |  |  |   reputationMonitorConfig?: IReputationMonitorConfig; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /** | 
					
						
							|  |  |  |  * Extended SMTP session interface for UnifiedEmailServer | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | export interface ISmtpSession extends IBaseSmtpSession { | 
					
						
							|  |  |  |   /** | 
					
						
							|  |  |  |    * User information if authenticated | 
					
						
							|  |  |  |    */ | 
					
						
							|  |  |  |   user?: { | 
					
						
							|  |  |  |     username: string; | 
					
						
							|  |  |  |     [key: string]: any; | 
					
						
							|  |  |  |   }; | 
					
						
							|  |  |  |    | 
					
						
							|  |  |  |   /** | 
					
						
							|  |  |  |    * Matched route for this session | 
					
						
							|  |  |  |    */ | 
					
						
							|  |  |  |   matchedRoute?: IEmailRoute; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /** | 
					
						
							|  |  |  |  * Authentication data for SMTP | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | import type { ISmtpAuth } from '../delivery/interfaces.ts'; | 
					
						
							|  |  |  | export type IAuthData = ISmtpAuth; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /** | 
					
						
							|  |  |  |  * Server statistics | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | export interface IServerStats { | 
					
						
							|  |  |  |   startTime: Date; | 
					
						
							|  |  |  |   connections: { | 
					
						
							|  |  |  |     current: number; | 
					
						
							|  |  |  |     total: number; | 
					
						
							|  |  |  |   }; | 
					
						
							|  |  |  |   messages: { | 
					
						
							|  |  |  |     processed: number; | 
					
						
							|  |  |  |     delivered: number; | 
					
						
							|  |  |  |     failed: number; | 
					
						
							|  |  |  |   }; | 
					
						
							|  |  |  |   processingTime: { | 
					
						
							|  |  |  |     avg: number; | 
					
						
							|  |  |  |     max: number; | 
					
						
							|  |  |  |     min: number; | 
					
						
							|  |  |  |   }; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /** | 
					
						
							|  |  |  |  * Unified email server that handles all email traffic with pattern-based routing | 
					
						
							|  |  |  |  */ | 
					
						
							| 
									
										
										
										
											2025-10-24 10:00:25 +00:00
										 |  |  | export class UnifiedEmailServer extends plugins.EventEmitter { | 
					
						
							| 
									
										
										
										
											2025-10-24 08:09:29 +00:00
										 |  |  |   private dcRouter: DcRouter; | 
					
						
							|  |  |  |   private options: IUnifiedEmailServerOptions; | 
					
						
							|  |  |  |   private emailRouter: EmailRouter; | 
					
						
							|  |  |  |   public domainRegistry: DomainRegistry; | 
					
						
							|  |  |  |   private servers: any[] = []; | 
					
						
							|  |  |  |   private stats: IServerStats; | 
					
						
							|  |  |  |    | 
					
						
							|  |  |  |   // Add components needed for sending and securing emails
 | 
					
						
							|  |  |  |   public dkimCreator: DKIMCreator; | 
					
						
							|  |  |  |   private ipReputationChecker: IPReputationChecker; // TODO: Implement IP reputation checks in processEmailByMode
 | 
					
						
							|  |  |  |   private bounceManager: BounceManager; | 
					
						
							|  |  |  |   private ipWarmupManager: IPWarmupManager; | 
					
						
							|  |  |  |   private senderReputationMonitor: SenderReputationMonitor; | 
					
						
							|  |  |  |   public deliveryQueue: UnifiedDeliveryQueue; | 
					
						
							|  |  |  |   public deliverySystem: MultiModeDeliverySystem; | 
					
						
							|  |  |  |   private rateLimiter: UnifiedRateLimiter; // TODO: Implement rate limiting in SMTP server handlers
 | 
					
						
							|  |  |  |   private dkimKeys: Map<string, string> = new Map(); // domain -> private key
 | 
					
						
							|  |  |  |   private smtpClients: Map<string, SmtpClient> = new Map(); // host:port -> client
 | 
					
						
							|  |  |  |    | 
					
						
							|  |  |  |   constructor(dcRouter: DcRouter, options: IUnifiedEmailServerOptions) { | 
					
						
							|  |  |  |     super(); | 
					
						
							|  |  |  |     this.dcRouter = dcRouter; | 
					
						
							|  |  |  |      | 
					
						
							|  |  |  |     // Set default options
 | 
					
						
							|  |  |  |     this.options = { | 
					
						
							|  |  |  |       ...options, | 
					
						
							|  |  |  |       banner: options.banner || `${options.hostname} ESMTP UnifiedEmailServer`, | 
					
						
							|  |  |  |       maxMessageSize: options.maxMessageSize || 10 * 1024 * 1024, // 10MB
 | 
					
						
							|  |  |  |       maxClients: options.maxClients || 100, | 
					
						
							|  |  |  |       maxConnections: options.maxConnections || 1000, | 
					
						
							|  |  |  |       connectionTimeout: options.connectionTimeout || 60000, // 1 minute
 | 
					
						
							|  |  |  |       socketTimeout: options.socketTimeout || 60000 // 1 minute
 | 
					
						
							|  |  |  |     }; | 
					
						
							|  |  |  |      | 
					
						
							|  |  |  |     // Initialize DKIM creator with storage manager
 | 
					
						
							|  |  |  |     this.dkimCreator = new DKIMCreator(paths.keysDir, dcRouter.storageManager); | 
					
						
							|  |  |  |      | 
					
						
							|  |  |  |     // Initialize IP reputation checker with storage manager
 | 
					
						
							|  |  |  |     this.ipReputationChecker = IPReputationChecker.getInstance({ | 
					
						
							|  |  |  |       enableLocalCache: true, | 
					
						
							|  |  |  |       enableDNSBL: true, | 
					
						
							|  |  |  |       enableIPInfo: true | 
					
						
							|  |  |  |     }, dcRouter.storageManager); | 
					
						
							|  |  |  |      | 
					
						
							|  |  |  |     // Initialize bounce manager with storage manager
 | 
					
						
							|  |  |  |     this.bounceManager = new BounceManager({ | 
					
						
							|  |  |  |       maxCacheSize: 10000, | 
					
						
							|  |  |  |       cacheTTL: 30 * 24 * 60 * 60 * 1000, // 30 days
 | 
					
						
							|  |  |  |       storageManager: dcRouter.storageManager | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  |      | 
					
						
							|  |  |  |     // Initialize IP warmup manager
 | 
					
						
							|  |  |  |     this.ipWarmupManager = IPWarmupManager.getInstance(options.ipWarmupConfig || { | 
					
						
							|  |  |  |       enabled: true, | 
					
						
							|  |  |  |       ipAddresses: [], | 
					
						
							|  |  |  |       targetDomains: [] | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  |      | 
					
						
							|  |  |  |     // Initialize sender reputation monitor with storage manager
 | 
					
						
							|  |  |  |     this.senderReputationMonitor = SenderReputationMonitor.getInstance( | 
					
						
							|  |  |  |       options.reputationMonitorConfig || { | 
					
						
							|  |  |  |         enabled: true, | 
					
						
							|  |  |  |         domains: [] | 
					
						
							|  |  |  |       }, | 
					
						
							|  |  |  |       dcRouter.storageManager | 
					
						
							|  |  |  |     ); | 
					
						
							|  |  |  |      | 
					
						
							|  |  |  |     // Initialize domain registry
 | 
					
						
							|  |  |  |     this.domainRegistry = new DomainRegistry(options.domains, options.defaults); | 
					
						
							|  |  |  |      | 
					
						
							|  |  |  |     // Initialize email router with routes and storage manager
 | 
					
						
							|  |  |  |     this.emailRouter = new EmailRouter(options.routes || [], { | 
					
						
							|  |  |  |       storageManager: dcRouter.storageManager, | 
					
						
							|  |  |  |       persistChanges: true | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  |      | 
					
						
							|  |  |  |     // Initialize rate limiter
 | 
					
						
							|  |  |  |     this.rateLimiter = new UnifiedRateLimiter(options.rateLimits || { | 
					
						
							|  |  |  |       global: { | 
					
						
							|  |  |  |         maxConnectionsPerIP: 10, | 
					
						
							|  |  |  |         maxMessagesPerMinute: 100, | 
					
						
							|  |  |  |         maxRecipientsPerMessage: 50, | 
					
						
							|  |  |  |         maxErrorsPerIP: 10, | 
					
						
							|  |  |  |         maxAuthFailuresPerIP: 5, | 
					
						
							|  |  |  |         blockDuration: 300000 // 5 minutes
 | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  |      | 
					
						
							|  |  |  |     // Initialize delivery components
 | 
					
						
							|  |  |  |     const queueOptions: IQueueOptions = { | 
					
						
							|  |  |  |       storageType: 'memory', // Default to memory storage
 | 
					
						
							|  |  |  |       maxRetries: 3, | 
					
						
							|  |  |  |       baseRetryDelay: 300000, // 5 minutes
 | 
					
						
							|  |  |  |       maxRetryDelay: 3600000 // 1 hour
 | 
					
						
							|  |  |  |     }; | 
					
						
							|  |  |  |      | 
					
						
							|  |  |  |     this.deliveryQueue = new UnifiedDeliveryQueue(queueOptions); | 
					
						
							|  |  |  |      | 
					
						
							|  |  |  |     const deliveryOptions: IMultiModeDeliveryOptions = { | 
					
						
							|  |  |  |       globalRateLimit: 100, // Default to 100 emails per minute
 | 
					
						
							|  |  |  |       concurrentDeliveries: 10, | 
					
						
							|  |  |  |       processBounces: true, | 
					
						
							|  |  |  |       bounceHandler: { | 
					
						
							|  |  |  |         processSmtpFailure: this.processSmtpFailure.bind(this) | 
					
						
							|  |  |  |       }, | 
					
						
							|  |  |  |       onDeliverySuccess: async (item, _result) => { | 
					
						
							|  |  |  |         // Record delivery success event for reputation monitoring
 | 
					
						
							|  |  |  |         const email = item.processingResult as Email; | 
					
						
							|  |  |  |         const senderDomain = email.from.split('@')[1]; | 
					
						
							|  |  |  |          | 
					
						
							|  |  |  |         if (senderDomain) { | 
					
						
							|  |  |  |           this.recordReputationEvent(senderDomain, { | 
					
						
							|  |  |  |             type: 'delivered', | 
					
						
							|  |  |  |             count: email.to.length | 
					
						
							|  |  |  |           }); | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |     }; | 
					
						
							|  |  |  |      | 
					
						
							|  |  |  |     this.deliverySystem = new MultiModeDeliverySystem(this.deliveryQueue, deliveryOptions, this); | 
					
						
							|  |  |  |      | 
					
						
							|  |  |  |     // Initialize statistics
 | 
					
						
							|  |  |  |     this.stats = { | 
					
						
							|  |  |  |       startTime: new Date(), | 
					
						
							|  |  |  |       connections: { | 
					
						
							|  |  |  |         current: 0, | 
					
						
							|  |  |  |         total: 0 | 
					
						
							|  |  |  |       }, | 
					
						
							|  |  |  |       messages: { | 
					
						
							|  |  |  |         processed: 0, | 
					
						
							|  |  |  |         delivered: 0, | 
					
						
							|  |  |  |         failed: 0 | 
					
						
							|  |  |  |       }, | 
					
						
							|  |  |  |       processingTime: { | 
					
						
							|  |  |  |         avg: 0, | 
					
						
							|  |  |  |         max: 0, | 
					
						
							|  |  |  |         min: 0 | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |     }; | 
					
						
							|  |  |  |      | 
					
						
							|  |  |  |     // We'll create the SMTP servers during the start() method
 | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  |    | 
					
						
							|  |  |  |   /** | 
					
						
							|  |  |  |    * Get or create an SMTP client for the given host and port | 
					
						
							|  |  |  |    * Uses connection pooling for efficiency | 
					
						
							|  |  |  |    */ | 
					
						
							|  |  |  |   public getSmtpClient(host: string, port: number = 25): SmtpClient { | 
					
						
							|  |  |  |     const clientKey = `${host}:${port}`; | 
					
						
							|  |  |  |      | 
					
						
							|  |  |  |     // Check if we already have a client for this destination
 | 
					
						
							|  |  |  |     let client = this.smtpClients.get(clientKey); | 
					
						
							|  |  |  |      | 
					
						
							|  |  |  |     if (!client) { | 
					
						
							|  |  |  |       // Create a new pooled SMTP client
 | 
					
						
							|  |  |  |       client = createPooledSmtpClient({ | 
					
						
							|  |  |  |         host, | 
					
						
							|  |  |  |         port, | 
					
						
							|  |  |  |         secure: port === 465, | 
					
						
							|  |  |  |         connectionTimeout: this.options.outbound?.connectionTimeout || 30000, | 
					
						
							|  |  |  |         socketTimeout: this.options.outbound?.socketTimeout || 120000, | 
					
						
							|  |  |  |         maxConnections: this.options.outbound?.maxConnections || 10, | 
					
						
							|  |  |  |         maxMessages: 1000, // Messages per connection before reconnect
 | 
					
						
							|  |  |  |         pool: true, | 
					
						
							|  |  |  |         debug: false | 
					
						
							|  |  |  |       }); | 
					
						
							|  |  |  |        | 
					
						
							|  |  |  |       this.smtpClients.set(clientKey, client); | 
					
						
							|  |  |  |       logger.log('info', `Created new SMTP client pool for ${clientKey}`); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |      | 
					
						
							|  |  |  |     return client; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  |    | 
					
						
							|  |  |  |   /** | 
					
						
							|  |  |  |    * Start the unified email server | 
					
						
							|  |  |  |    */ | 
					
						
							|  |  |  |   public async start(): Promise<void> { | 
					
						
							|  |  |  |     logger.log('info', `Starting UnifiedEmailServer on ports: ${(this.options.ports as number[]).join(', ')}`); | 
					
						
							|  |  |  |      | 
					
						
							|  |  |  |     try { | 
					
						
							|  |  |  |       // Initialize the delivery queue
 | 
					
						
							|  |  |  |       await this.deliveryQueue.initialize(); | 
					
						
							|  |  |  |       logger.log('info', 'Email delivery queue initialized'); | 
					
						
							|  |  |  |        | 
					
						
							|  |  |  |       // Start the delivery system
 | 
					
						
							|  |  |  |       await this.deliverySystem.start(); | 
					
						
							|  |  |  |       logger.log('info', 'Email delivery system started'); | 
					
						
							|  |  |  |        | 
					
						
							|  |  |  |       // Set up DKIM for all domains
 | 
					
						
							|  |  |  |       await this.setupDkimForDomains(); | 
					
						
							|  |  |  |       logger.log('info', 'DKIM configuration completed for all domains'); | 
					
						
							|  |  |  |        | 
					
						
							|  |  |  |       // Create DNS manager and ensure all DNS records are created
 | 
					
						
							|  |  |  |       const dnsManager = new DnsManager(this.dcRouter); | 
					
						
							|  |  |  |       await dnsManager.ensureDnsRecords(this.domainRegistry.getAllConfigs(), this.dkimCreator); | 
					
						
							|  |  |  |       logger.log('info', 'DNS records ensured for all configured domains'); | 
					
						
							|  |  |  |        | 
					
						
							|  |  |  |       // Apply per-domain rate limits
 | 
					
						
							|  |  |  |       this.applyDomainRateLimits(); | 
					
						
							|  |  |  |       logger.log('info', 'Per-domain rate limits configured'); | 
					
						
							|  |  |  |        | 
					
						
							|  |  |  |       // Check and rotate DKIM keys if needed
 | 
					
						
							|  |  |  |       await this.checkAndRotateDkimKeys(); | 
					
						
							|  |  |  |       logger.log('info', 'DKIM key rotation check completed'); | 
					
						
							|  |  |  |        | 
					
						
							|  |  |  |       // Skip server creation in socket-handler mode
 | 
					
						
							|  |  |  |       if (this.options.useSocketHandler) { | 
					
						
							|  |  |  |         logger.log('info', 'UnifiedEmailServer started in socket-handler mode (no port listening)'); | 
					
						
							|  |  |  |         this.emit('started'); | 
					
						
							|  |  |  |         return; | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |        | 
					
						
							|  |  |  |       // Ensure we have the necessary TLS options
 | 
					
						
							|  |  |  |       const hasTlsConfig = this.options.tls?.keyPath && this.options.tls?.certPath; | 
					
						
							|  |  |  |        | 
					
						
							|  |  |  |       // Prepare the certificate and key if available
 | 
					
						
							|  |  |  |       let key: string | undefined; | 
					
						
							|  |  |  |       let cert: string | undefined; | 
					
						
							|  |  |  |        | 
					
						
							|  |  |  |       if (hasTlsConfig) { | 
					
						
							|  |  |  |         try { | 
					
						
							|  |  |  |           key = plugins.fs.readFileSync(this.options.tls.keyPath!, 'utf8'); | 
					
						
							|  |  |  |           cert = plugins.fs.readFileSync(this.options.tls.certPath!, 'utf8'); | 
					
						
							|  |  |  |           logger.log('info', 'TLS certificates loaded successfully'); | 
					
						
							|  |  |  |         } catch (error) { | 
					
						
							|  |  |  |           logger.log('warn', `Failed to load TLS certificates: ${error.message}`); | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |        | 
					
						
							|  |  |  |       // Create a SMTP server for each port
 | 
					
						
							|  |  |  |       for (const port of this.options.ports as number[]) { | 
					
						
							|  |  |  |         // Create a reference object to hold the MTA service during setup
 | 
					
						
							|  |  |  |         const mtaRef = { | 
					
						
							|  |  |  |           config: { | 
					
						
							|  |  |  |             smtp: { | 
					
						
							|  |  |  |               hostname: this.options.hostname | 
					
						
							|  |  |  |             }, | 
					
						
							|  |  |  |             security: { | 
					
						
							|  |  |  |               checkIPReputation: false, | 
					
						
							|  |  |  |               verifyDkim: true, | 
					
						
							|  |  |  |               verifySpf: true, | 
					
						
							|  |  |  |               verifyDmarc: true | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |           }, | 
					
						
							|  |  |  |           // These will be implemented in the real integration:
 | 
					
						
							|  |  |  |           dkimVerifier: { | 
					
						
							|  |  |  |             verify: async () => ({ isValid: true, domain: '' }) | 
					
						
							|  |  |  |           }, | 
					
						
							|  |  |  |           spfVerifier: { | 
					
						
							|  |  |  |             verifyAndApply: async () => true | 
					
						
							|  |  |  |           }, | 
					
						
							|  |  |  |           dmarcVerifier: { | 
					
						
							|  |  |  |             verify: async () => ({}), | 
					
						
							|  |  |  |             applyPolicy: () => true | 
					
						
							|  |  |  |           }, | 
					
						
							|  |  |  |           processIncomingEmail: async (email: Email) => { | 
					
						
							|  |  |  |             // Process email using the new route-based system
 | 
					
						
							|  |  |  |             await this.processEmailByMode(email, {  | 
					
						
							|  |  |  |               id: 'session-' + Math.random().toString(36).substring(2), | 
					
						
							|  |  |  |               state: SmtpState.FINISHED, | 
					
						
							|  |  |  |               mailFrom: email.from, | 
					
						
							|  |  |  |               rcptTo: email.to, | 
					
						
							|  |  |  |               emailData: email.toRFC822String(), // Use the proper method to get the full email content
 | 
					
						
							|  |  |  |               useTLS: false, | 
					
						
							|  |  |  |               connectionEnded: true, | 
					
						
							|  |  |  |               remoteAddress: '127.0.0.1', | 
					
						
							|  |  |  |               clientHostname: '', | 
					
						
							|  |  |  |               secure: false, | 
					
						
							|  |  |  |               authenticated: false, | 
					
						
							|  |  |  |               envelope: { | 
					
						
							|  |  |  |                 mailFrom: { address: email.from, args: {} }, | 
					
						
							|  |  |  |                 rcptTo: email.to.map(recipient => ({ address: recipient, args: {} })) | 
					
						
							|  |  |  |               } | 
					
						
							|  |  |  |             }); | 
					
						
							|  |  |  |              | 
					
						
							|  |  |  |             return true; | 
					
						
							|  |  |  |           } | 
					
						
							|  |  |  |         }; | 
					
						
							|  |  |  |          | 
					
						
							|  |  |  |         // Create server options
 | 
					
						
							|  |  |  |         const serverOptions = { | 
					
						
							|  |  |  |           port, | 
					
						
							|  |  |  |           hostname: this.options.hostname, | 
					
						
							|  |  |  |           key, | 
					
						
							|  |  |  |           cert | 
					
						
							|  |  |  |         }; | 
					
						
							|  |  |  |          | 
					
						
							|  |  |  |         // Create and start the SMTP server
 | 
					
						
							|  |  |  |         const smtpServer = createSmtpServer(mtaRef as any, serverOptions); | 
					
						
							|  |  |  |         this.servers.push(smtpServer); | 
					
						
							|  |  |  |          | 
					
						
							|  |  |  |         // Start the server
 | 
					
						
							|  |  |  |         await new Promise<void>((resolve, reject) => { | 
					
						
							|  |  |  |           try { | 
					
						
							|  |  |  |             // Leave this empty for now, smtpServer.start() is handled by the SMTPServer class internally
 | 
					
						
							|  |  |  |             // The server is started when it's created
 | 
					
						
							|  |  |  |             logger.log('info', `UnifiedEmailServer listening on port ${port}`); | 
					
						
							|  |  |  |              | 
					
						
							|  |  |  |             // Event handlers are managed internally by the SmtpServer class
 | 
					
						
							|  |  |  |             // No need to access the private server property
 | 
					
						
							|  |  |  |              | 
					
						
							|  |  |  |             resolve(); | 
					
						
							|  |  |  |           } catch (err) { | 
					
						
							|  |  |  |             if ((err as any).code === 'EADDRINUSE') { | 
					
						
							|  |  |  |               logger.log('error', `Port ${port} is already in use`); | 
					
						
							|  |  |  |               reject(new Error(`Port ${port} is already in use`)); | 
					
						
							|  |  |  |             } else { | 
					
						
							|  |  |  |               logger.log('error', `Error starting server on port ${port}: ${err.message}`); | 
					
						
							|  |  |  |               reject(err); | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |           } | 
					
						
							|  |  |  |         }); | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |        | 
					
						
							|  |  |  |       logger.log('info', 'UnifiedEmailServer started successfully'); | 
					
						
							|  |  |  |       this.emit('started'); | 
					
						
							|  |  |  |     } catch (error) { | 
					
						
							|  |  |  |       logger.log('error', `Failed to start UnifiedEmailServer: ${error.message}`); | 
					
						
							|  |  |  |       throw error; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  |    | 
					
						
							|  |  |  |   /** | 
					
						
							|  |  |  |    * Handle a socket from smartproxy in socket-handler mode | 
					
						
							|  |  |  |    * @param socket The socket to handle | 
					
						
							|  |  |  |    * @param port The port this connection is for (25, 587, 465) | 
					
						
							|  |  |  |    */ | 
					
						
							|  |  |  |   public async handleSocket(socket: plugins.net.Socket | plugins.tls.TLSSocket, port: number): Promise<void> { | 
					
						
							|  |  |  |     if (!this.options.useSocketHandler) { | 
					
						
							|  |  |  |       logger.log('error', 'handleSocket called but useSocketHandler is not enabled'); | 
					
						
							|  |  |  |       socket.destroy(); | 
					
						
							|  |  |  |       return; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |      | 
					
						
							|  |  |  |     logger.log('info', `Handling socket for port ${port}`); | 
					
						
							|  |  |  |      | 
					
						
							|  |  |  |     // Create a temporary SMTP server instance for this connection
 | 
					
						
							|  |  |  |     // We need a full server instance because the SMTP protocol handler needs all components
 | 
					
						
							|  |  |  |     const smtpServerOptions = { | 
					
						
							|  |  |  |       port, | 
					
						
							|  |  |  |       hostname: this.options.hostname, | 
					
						
							|  |  |  |       key: this.options.tls?.keyPath ? plugins.fs.readFileSync(this.options.tls.keyPath, 'utf8') : undefined, | 
					
						
							|  |  |  |       cert: this.options.tls?.certPath ? plugins.fs.readFileSync(this.options.tls.certPath, 'utf8') : undefined | 
					
						
							|  |  |  |     }; | 
					
						
							|  |  |  |      | 
					
						
							|  |  |  |     // Create the SMTP server instance
 | 
					
						
							|  |  |  |     const smtpServer = createSmtpServer(this, smtpServerOptions); | 
					
						
							|  |  |  |      | 
					
						
							|  |  |  |     // Get the connection manager from the server
 | 
					
						
							|  |  |  |     const connectionManager = (smtpServer as any).connectionManager; | 
					
						
							|  |  |  |      | 
					
						
							|  |  |  |     if (!connectionManager) { | 
					
						
							|  |  |  |       logger.log('error', 'Could not get connection manager from SMTP server'); | 
					
						
							|  |  |  |       socket.destroy(); | 
					
						
							|  |  |  |       return; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |      | 
					
						
							|  |  |  |     // Determine if this is a secure connection
 | 
					
						
							|  |  |  |     // Port 465 uses implicit TLS, so the socket is already secure
 | 
					
						
							|  |  |  |     const isSecure = port === 465 || socket instanceof plugins.tls.TLSSocket; | 
					
						
							|  |  |  |      | 
					
						
							|  |  |  |     // Pass the socket to the connection manager
 | 
					
						
							|  |  |  |     try { | 
					
						
							|  |  |  |       await connectionManager.handleConnection(socket, isSecure); | 
					
						
							|  |  |  |     } catch (error) { | 
					
						
							|  |  |  |       logger.log('error', `Error handling socket connection: ${error.message}`); | 
					
						
							|  |  |  |       socket.destroy(); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  |    | 
					
						
							|  |  |  |   /** | 
					
						
							|  |  |  |    * Stop the unified email server | 
					
						
							|  |  |  |    */ | 
					
						
							|  |  |  |   public async stop(): Promise<void> { | 
					
						
							|  |  |  |     logger.log('info', 'Stopping UnifiedEmailServer'); | 
					
						
							|  |  |  |      | 
					
						
							|  |  |  |     try { | 
					
						
							|  |  |  |       // Clear the servers array - servers will be garbage collected
 | 
					
						
							|  |  |  |       this.servers = []; | 
					
						
							|  |  |  |        | 
					
						
							|  |  |  |       // Stop the delivery system
 | 
					
						
							|  |  |  |       if (this.deliverySystem) { | 
					
						
							|  |  |  |         await this.deliverySystem.stop(); | 
					
						
							|  |  |  |         logger.log('info', 'Email delivery system stopped'); | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |        | 
					
						
							|  |  |  |       // Shut down the delivery queue
 | 
					
						
							|  |  |  |       if (this.deliveryQueue) { | 
					
						
							|  |  |  |         await this.deliveryQueue.shutdown(); | 
					
						
							|  |  |  |         logger.log('info', 'Email delivery queue shut down'); | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |        | 
					
						
							|  |  |  |       // Close all SMTP client connections
 | 
					
						
							|  |  |  |       for (const [clientKey, client] of this.smtpClients) { | 
					
						
							|  |  |  |         try { | 
					
						
							|  |  |  |           await client.close(); | 
					
						
							|  |  |  |           logger.log('info', `Closed SMTP client pool for ${clientKey}`); | 
					
						
							|  |  |  |         } catch (error) { | 
					
						
							|  |  |  |           logger.log('warn', `Error closing SMTP client for ${clientKey}: ${error.message}`); | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |       this.smtpClients.clear(); | 
					
						
							|  |  |  |        | 
					
						
							|  |  |  |       logger.log('info', 'UnifiedEmailServer stopped successfully'); | 
					
						
							|  |  |  |       this.emit('stopped'); | 
					
						
							|  |  |  |     } catch (error) { | 
					
						
							|  |  |  |       logger.log('error', `Error stopping UnifiedEmailServer: ${error.message}`); | 
					
						
							|  |  |  |       throw error; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  |    | 
					
						
							|  |  |  |    | 
					
						
							|  |  |  |    | 
					
						
							|  |  |  |    | 
					
						
							|  |  |  |    | 
					
						
							|  |  |  |   /** | 
					
						
							|  |  |  |    * Process email based on routing rules | 
					
						
							|  |  |  |    */ | 
					
						
							|  |  |  |   public async processEmailByMode(emailData: Email | Buffer, session: IExtendedSmtpSession): Promise<Email> { | 
					
						
							|  |  |  |     // Convert Buffer to Email if needed
 | 
					
						
							|  |  |  |     let email: Email; | 
					
						
							|  |  |  |     if (Buffer.isBuffer(emailData)) { | 
					
						
							|  |  |  |       // Parse the email data buffer into an Email object
 | 
					
						
							|  |  |  |       try { | 
					
						
							|  |  |  |         const parsed = await plugins.mailparser.simpleParser(emailData); | 
					
						
							|  |  |  |         email = new Email({ | 
					
						
							|  |  |  |           from: parsed.from?.value[0]?.address || session.envelope.mailFrom.address, | 
					
						
							|  |  |  |           to: session.envelope.rcptTo[0]?.address || '', | 
					
						
							|  |  |  |           subject: parsed.subject || '', | 
					
						
							|  |  |  |           text: parsed.text || '', | 
					
						
							|  |  |  |           html: parsed.html || undefined, | 
					
						
							|  |  |  |           attachments: parsed.attachments?.map(att => ({ | 
					
						
							|  |  |  |             filename: att.filename || '', | 
					
						
							|  |  |  |             content: att.content, | 
					
						
							|  |  |  |             contentType: att.contentType | 
					
						
							|  |  |  |           })) || [] | 
					
						
							|  |  |  |         }); | 
					
						
							|  |  |  |       } catch (error) { | 
					
						
							|  |  |  |         logger.log('error', `Error parsing email data: ${error.message}`); | 
					
						
							|  |  |  |         throw new Error(`Error parsing email data: ${error.message}`); | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |     } else { | 
					
						
							|  |  |  |       email = emailData; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |      | 
					
						
							|  |  |  |     // First check if this is a bounce notification email
 | 
					
						
							|  |  |  |     // Look for common bounce notification subject patterns
 | 
					
						
							|  |  |  |     const subject = email.subject || ''; | 
					
						
							|  |  |  |     const isBounceLike = /mail delivery|delivery (failed|status|notification)|failure notice|returned mail|undeliverable|delivery problem/i.test(subject); | 
					
						
							|  |  |  |      | 
					
						
							|  |  |  |     if (isBounceLike) { | 
					
						
							|  |  |  |       logger.log('info', `Email subject matches bounce notification pattern: "${subject}"`); | 
					
						
							|  |  |  |        | 
					
						
							|  |  |  |       // Try to process as a bounce
 | 
					
						
							|  |  |  |       const isBounce = await this.processBounceNotification(email); | 
					
						
							|  |  |  |        | 
					
						
							|  |  |  |       if (isBounce) { | 
					
						
							|  |  |  |         logger.log('info', 'Successfully processed as bounce notification, skipping regular processing'); | 
					
						
							|  |  |  |         return email; | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |        | 
					
						
							|  |  |  |       logger.log('info', 'Not a valid bounce notification, continuing with regular processing'); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     // Find matching route
 | 
					
						
							|  |  |  |     const context: IEmailContext = { email, session }; | 
					
						
							|  |  |  |     const route = await this.emailRouter.evaluateRoutes(context); | 
					
						
							|  |  |  |      | 
					
						
							|  |  |  |     if (!route) { | 
					
						
							|  |  |  |       // No matching route - reject
 | 
					
						
							|  |  |  |       throw new Error('No matching route for email'); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |      | 
					
						
							|  |  |  |     // Store matched route in session
 | 
					
						
							|  |  |  |     session.matchedRoute = route; | 
					
						
							|  |  |  |      | 
					
						
							|  |  |  |     // Execute action based on route
 | 
					
						
							|  |  |  |     await this.executeAction(route.action, email, context); | 
					
						
							|  |  |  |      | 
					
						
							|  |  |  |     // Return the processed email
 | 
					
						
							|  |  |  |     return email; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  |    | 
					
						
							|  |  |  |   /** | 
					
						
							|  |  |  |    * Execute action based on route configuration | 
					
						
							|  |  |  |    */ | 
					
						
							|  |  |  |   private async executeAction(action: IEmailAction, email: Email, context: IEmailContext): Promise<void> { | 
					
						
							|  |  |  |     switch (action.type) { | 
					
						
							|  |  |  |       case 'forward': | 
					
						
							|  |  |  |         await this.handleForwardAction(action, email, context); | 
					
						
							|  |  |  |         break; | 
					
						
							|  |  |  |          | 
					
						
							|  |  |  |       case 'process': | 
					
						
							|  |  |  |         await this.handleProcessAction(action, email, context); | 
					
						
							|  |  |  |         break; | 
					
						
							|  |  |  |          | 
					
						
							|  |  |  |       case 'deliver': | 
					
						
							|  |  |  |         await this.handleDeliverAction(action, email, context); | 
					
						
							|  |  |  |         break; | 
					
						
							|  |  |  |          | 
					
						
							|  |  |  |       case 'reject': | 
					
						
							|  |  |  |         await this.handleRejectAction(action, email, context); | 
					
						
							|  |  |  |         break; | 
					
						
							|  |  |  |          | 
					
						
							|  |  |  |       default: | 
					
						
							|  |  |  |         throw new Error(`Unknown action type: ${(action as any).type}`); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  |    | 
					
						
							|  |  |  |   /** | 
					
						
							|  |  |  |    * Handle forward action | 
					
						
							|  |  |  |    */ | 
					
						
							|  |  |  |   private async handleForwardAction(_action: IEmailAction, email: Email, context: IEmailContext): Promise<void> { | 
					
						
							|  |  |  |     if (!_action.forward) { | 
					
						
							|  |  |  |       throw new Error('Forward action requires forward configuration'); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |      | 
					
						
							|  |  |  |     const { host, port = 25, auth, addHeaders } = _action.forward; | 
					
						
							|  |  |  |      | 
					
						
							|  |  |  |     logger.log('info', `Forwarding email to ${host}:${port}`); | 
					
						
							|  |  |  |      | 
					
						
							|  |  |  |     // Add forwarding headers
 | 
					
						
							|  |  |  |     if (addHeaders) { | 
					
						
							|  |  |  |       for (const [key, value] of Object.entries(addHeaders)) { | 
					
						
							|  |  |  |         email.headers[key] = value; | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |      | 
					
						
							|  |  |  |     // Add standard forwarding headers
 | 
					
						
							|  |  |  |     email.headers['X-Forwarded-For'] = context.session.remoteAddress || 'unknown'; | 
					
						
							|  |  |  |     email.headers['X-Forwarded-To'] = email.to.join(', '); | 
					
						
							|  |  |  |     email.headers['X-Forwarded-Date'] = new Date().toISOString(); | 
					
						
							|  |  |  |      | 
					
						
							|  |  |  |     // Get SMTP client
 | 
					
						
							|  |  |  |     const client = this.getSmtpClient(host, port); | 
					
						
							|  |  |  |      | 
					
						
							|  |  |  |     try { | 
					
						
							|  |  |  |       // Send email
 | 
					
						
							|  |  |  |       await client.sendMail(email); | 
					
						
							|  |  |  |        | 
					
						
							|  |  |  |       logger.log('info', `Successfully forwarded email to ${host}:${port}`); | 
					
						
							|  |  |  |        | 
					
						
							|  |  |  |       SecurityLogger.getInstance().logEvent({ | 
					
						
							|  |  |  |         level: SecurityLogLevel.INFO, | 
					
						
							|  |  |  |         type: SecurityEventType.EMAIL_FORWARDING, | 
					
						
							|  |  |  |         message: 'Email forwarded successfully', | 
					
						
							|  |  |  |         ipAddress: context.session.remoteAddress, | 
					
						
							|  |  |  |         details: { | 
					
						
							|  |  |  |           sessionId: context.session.id, | 
					
						
							|  |  |  |           routeName: context.session.matchedRoute?.name, | 
					
						
							|  |  |  |           targetHost: host, | 
					
						
							|  |  |  |           targetPort: port, | 
					
						
							|  |  |  |           recipients: email.to | 
					
						
							|  |  |  |         }, | 
					
						
							|  |  |  |         success: true | 
					
						
							|  |  |  |       }); | 
					
						
							|  |  |  |     } catch (error) { | 
					
						
							|  |  |  |       logger.log('error', `Failed to forward email: ${error.message}`); | 
					
						
							|  |  |  |        | 
					
						
							|  |  |  |       SecurityLogger.getInstance().logEvent({ | 
					
						
							|  |  |  |         level: SecurityLogLevel.ERROR, | 
					
						
							|  |  |  |         type: SecurityEventType.EMAIL_FORWARDING, | 
					
						
							|  |  |  |         message: 'Email forwarding failed', | 
					
						
							|  |  |  |         ipAddress: context.session.remoteAddress, | 
					
						
							|  |  |  |         details: { | 
					
						
							|  |  |  |           sessionId: context.session.id, | 
					
						
							|  |  |  |           routeName: context.session.matchedRoute?.name, | 
					
						
							|  |  |  |           targetHost: host, | 
					
						
							|  |  |  |           targetPort: port, | 
					
						
							|  |  |  |           error: error.message | 
					
						
							|  |  |  |         }, | 
					
						
							|  |  |  |         success: false | 
					
						
							|  |  |  |       }); | 
					
						
							|  |  |  |        | 
					
						
							|  |  |  |       // Handle as bounce
 | 
					
						
							|  |  |  |       for (const recipient of email.getAllRecipients()) { | 
					
						
							|  |  |  |         await this.bounceManager.processSmtpFailure(recipient, error.message, { | 
					
						
							|  |  |  |           sender: email.from, | 
					
						
							|  |  |  |           originalEmailId: email.headers['Message-ID'] as string | 
					
						
							|  |  |  |         }); | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |       throw error; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  |    | 
					
						
							|  |  |  |   /** | 
					
						
							|  |  |  |    * Handle process action | 
					
						
							|  |  |  |    */ | 
					
						
							|  |  |  |   private async handleProcessAction(action: IEmailAction, email: Email, context: IEmailContext): Promise<void> { | 
					
						
							|  |  |  |     logger.log('info', `Processing email with action options`); | 
					
						
							|  |  |  |      | 
					
						
							|  |  |  |     // Apply scanning if requested
 | 
					
						
							|  |  |  |     if (action.process?.scan) { | 
					
						
							|  |  |  |       // Use existing content scanner
 | 
					
						
							|  |  |  |       // Note: ContentScanner integration would go here
 | 
					
						
							|  |  |  |       logger.log('info', 'Content scanning requested'); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |      | 
					
						
							|  |  |  |     // Note: DKIM signing will be applied at delivery time to ensure signature validity
 | 
					
						
							|  |  |  |      | 
					
						
							|  |  |  |     // Queue for delivery
 | 
					
						
							|  |  |  |     const queue = action.process?.queue || 'normal'; | 
					
						
							|  |  |  |     await this.deliveryQueue.enqueue(email, 'process', context.session.matchedRoute!); | 
					
						
							|  |  |  |      | 
					
						
							|  |  |  |     logger.log('info', `Email queued for delivery in ${queue} queue`); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  |    | 
					
						
							|  |  |  |   /** | 
					
						
							|  |  |  |    * Handle deliver action | 
					
						
							|  |  |  |    */ | 
					
						
							|  |  |  |   private async handleDeliverAction(_action: IEmailAction, email: Email, context: IEmailContext): Promise<void> { | 
					
						
							|  |  |  |     logger.log('info', `Delivering email locally`); | 
					
						
							|  |  |  |      | 
					
						
							|  |  |  |     // Queue for local delivery
 | 
					
						
							|  |  |  |     await this.deliveryQueue.enqueue(email, 'mta', context.session.matchedRoute!); | 
					
						
							|  |  |  |      | 
					
						
							|  |  |  |     logger.log('info', 'Email queued for local delivery'); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  |    | 
					
						
							|  |  |  |   /** | 
					
						
							|  |  |  |    * Handle reject action | 
					
						
							|  |  |  |    */ | 
					
						
							|  |  |  |   private async handleRejectAction(action: IEmailAction, email: Email, context: IEmailContext): Promise<void> { | 
					
						
							|  |  |  |     const code = action.reject?.code || 550; | 
					
						
							|  |  |  |     const message = action.reject?.message || 'Message rejected'; | 
					
						
							|  |  |  |      | 
					
						
							|  |  |  |     logger.log('info', `Rejecting email with code ${code}: ${message}`); | 
					
						
							|  |  |  |      | 
					
						
							|  |  |  |     SecurityLogger.getInstance().logEvent({ | 
					
						
							|  |  |  |       level: SecurityLogLevel.WARN, | 
					
						
							|  |  |  |       type: SecurityEventType.EMAIL_PROCESSING, | 
					
						
							|  |  |  |       message: 'Email rejected by routing rule', | 
					
						
							|  |  |  |       ipAddress: context.session.remoteAddress, | 
					
						
							|  |  |  |       details: { | 
					
						
							|  |  |  |         sessionId: context.session.id, | 
					
						
							|  |  |  |         routeName: context.session.matchedRoute?.name, | 
					
						
							|  |  |  |         rejectCode: code, | 
					
						
							|  |  |  |         rejectMessage: message, | 
					
						
							|  |  |  |         from: email.from, | 
					
						
							|  |  |  |         to: email.to | 
					
						
							|  |  |  |       }, | 
					
						
							|  |  |  |       success: false | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  |      | 
					
						
							|  |  |  |     // Throw error with SMTP code and message
 | 
					
						
							|  |  |  |     const error = new Error(message); | 
					
						
							|  |  |  |     (error as any).responseCode = code; | 
					
						
							|  |  |  |     throw error; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  |    | 
					
						
							|  |  |  |   /** | 
					
						
							|  |  |  |    * Handle email in MTA mode (programmatic processing) | 
					
						
							|  |  |  |    */ | 
					
						
							|  |  |  |   private async _handleMtaMode(email: Email, session: IExtendedSmtpSession): Promise<void> { | 
					
						
							|  |  |  |     logger.log('info', `Handling email in MTA mode for session ${session.id}`); | 
					
						
							|  |  |  |      | 
					
						
							|  |  |  |     try { | 
					
						
							|  |  |  |       // Apply MTA rule options if provided
 | 
					
						
							|  |  |  |       if (session.matchedRoute?.action.options?.mtaOptions) { | 
					
						
							|  |  |  |         const options = session.matchedRoute.action.options.mtaOptions; | 
					
						
							|  |  |  |          | 
					
						
							|  |  |  |         // Apply DKIM signing if enabled
 | 
					
						
							|  |  |  |         if (options.dkimSign && options.dkimOptions) { | 
					
						
							|  |  |  |           // Sign the email with DKIM
 | 
					
						
							|  |  |  |           logger.log('info', `Signing email with DKIM for domain ${options.dkimOptions.domainName}`); | 
					
						
							|  |  |  |            | 
					
						
							|  |  |  |           try { | 
					
						
							|  |  |  |             // Ensure DKIM keys exist for the domain
 | 
					
						
							|  |  |  |             await this.dkimCreator.handleDKIMKeysForDomain(options.dkimOptions.domainName); | 
					
						
							|  |  |  |              | 
					
						
							|  |  |  |             // Convert Email to raw format for signing
 | 
					
						
							|  |  |  |             const rawEmail = email.toRFC822String(); | 
					
						
							|  |  |  |              | 
					
						
							|  |  |  |             // Create headers object
 | 
					
						
							|  |  |  |             const headers = {}; | 
					
						
							|  |  |  |             for (const [key, value] of Object.entries(email.headers)) { | 
					
						
							|  |  |  |               headers[key] = value; | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |              | 
					
						
							|  |  |  |             // Sign the email
 | 
					
						
							|  |  |  |             const signResult = await plugins.dkimSign(rawEmail, { | 
					
						
							|  |  |  |               canonicalization: 'relaxed/relaxed', | 
					
						
							|  |  |  |               algorithm: 'rsa-sha256', | 
					
						
							|  |  |  |               signTime: new Date(), | 
					
						
							|  |  |  |               signatureData: [ | 
					
						
							|  |  |  |                 { | 
					
						
							|  |  |  |                   signingDomain: options.dkimOptions.domainName, | 
					
						
							|  |  |  |                   selector: options.dkimOptions.keySelector || 'mta', | 
					
						
							|  |  |  |                   privateKey: (await this.dkimCreator.readDKIMKeys(options.dkimOptions.domainName)).privateKey, | 
					
						
							|  |  |  |                   algorithm: 'rsa-sha256', | 
					
						
							|  |  |  |                   canonicalization: 'relaxed/relaxed' | 
					
						
							|  |  |  |                 } | 
					
						
							|  |  |  |               ] | 
					
						
							|  |  |  |             }); | 
					
						
							|  |  |  |              | 
					
						
							|  |  |  |             // Add the DKIM-Signature header to the email
 | 
					
						
							|  |  |  |             if (signResult.signatures) { | 
					
						
							|  |  |  |               email.addHeader('DKIM-Signature', signResult.signatures); | 
					
						
							|  |  |  |               logger.log('info', `Successfully added DKIM signature for ${options.dkimOptions.domainName}`); | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |           } catch (error) { | 
					
						
							|  |  |  |             logger.log('error', `Failed to sign email with DKIM: ${error.message}`); | 
					
						
							|  |  |  |           } | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |        | 
					
						
							|  |  |  |       // Get email content for logging/processing
 | 
					
						
							|  |  |  |       const subject = email.subject; | 
					
						
							|  |  |  |       const recipients = email.getAllRecipients().join(', '); | 
					
						
							|  |  |  |        | 
					
						
							|  |  |  |       logger.log('info', `Email processed by MTA: ${subject} to ${recipients}`); | 
					
						
							|  |  |  |        | 
					
						
							|  |  |  |       SecurityLogger.getInstance().logEvent({ | 
					
						
							|  |  |  |         level: SecurityLogLevel.INFO, | 
					
						
							|  |  |  |         type: SecurityEventType.EMAIL_PROCESSING, | 
					
						
							|  |  |  |         message: 'Email processed by MTA', | 
					
						
							|  |  |  |         ipAddress: session.remoteAddress, | 
					
						
							|  |  |  |         details: { | 
					
						
							|  |  |  |           sessionId: session.id, | 
					
						
							|  |  |  |           ruleName: session.matchedRoute?.name || 'default', | 
					
						
							|  |  |  |           subject, | 
					
						
							|  |  |  |           recipients | 
					
						
							|  |  |  |         }, | 
					
						
							|  |  |  |         success: true | 
					
						
							|  |  |  |       }); | 
					
						
							|  |  |  |     } catch (error) { | 
					
						
							|  |  |  |       logger.log('error', `Failed to process email in MTA mode: ${error.message}`); | 
					
						
							|  |  |  |        | 
					
						
							|  |  |  |       SecurityLogger.getInstance().logEvent({ | 
					
						
							|  |  |  |         level: SecurityLogLevel.ERROR, | 
					
						
							|  |  |  |         type: SecurityEventType.EMAIL_PROCESSING, | 
					
						
							|  |  |  |         message: 'MTA processing failed', | 
					
						
							|  |  |  |         ipAddress: session.remoteAddress, | 
					
						
							|  |  |  |         details: { | 
					
						
							|  |  |  |           sessionId: session.id, | 
					
						
							|  |  |  |           ruleName: session.matchedRoute?.name || 'default', | 
					
						
							|  |  |  |           error: error.message | 
					
						
							|  |  |  |         }, | 
					
						
							|  |  |  |         success: false | 
					
						
							|  |  |  |       }); | 
					
						
							|  |  |  |        | 
					
						
							|  |  |  |       throw error; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  |    | 
					
						
							|  |  |  |   /** | 
					
						
							|  |  |  |    * Handle email in process mode (store-and-forward with scanning) | 
					
						
							|  |  |  |    */ | 
					
						
							|  |  |  |   private async _handleProcessMode(email: Email, session: IExtendedSmtpSession): Promise<void> { | 
					
						
							|  |  |  |     logger.log('info', `Handling email in process mode for session ${session.id}`); | 
					
						
							|  |  |  |      | 
					
						
							|  |  |  |     try { | 
					
						
							|  |  |  |       const route = session.matchedRoute; | 
					
						
							|  |  |  |        | 
					
						
							|  |  |  |       // Apply content scanning if enabled
 | 
					
						
							|  |  |  |       if (route?.action.options?.contentScanning && route.action.options.scanners && route.action.options.scanners.length > 0) { | 
					
						
							|  |  |  |         logger.log('info', 'Performing content scanning'); | 
					
						
							|  |  |  |          | 
					
						
							|  |  |  |         // Apply each scanner
 | 
					
						
							|  |  |  |         for (const scanner of route.action.options.scanners) { | 
					
						
							|  |  |  |           switch (scanner.type) { | 
					
						
							|  |  |  |             case 'spam': | 
					
						
							|  |  |  |               logger.log('info', 'Scanning for spam content'); | 
					
						
							|  |  |  |               // Implement spam scanning
 | 
					
						
							|  |  |  |               break; | 
					
						
							|  |  |  |                | 
					
						
							|  |  |  |             case 'virus': | 
					
						
							|  |  |  |               logger.log('info', 'Scanning for virus content'); | 
					
						
							|  |  |  |               // Implement virus scanning
 | 
					
						
							|  |  |  |               break; | 
					
						
							|  |  |  |                | 
					
						
							|  |  |  |             case 'attachment': | 
					
						
							|  |  |  |               logger.log('info', 'Scanning attachments'); | 
					
						
							|  |  |  |                | 
					
						
							|  |  |  |               // Check for blocked extensions
 | 
					
						
							|  |  |  |               if (scanner.blockedExtensions && scanner.blockedExtensions.length > 0) { | 
					
						
							|  |  |  |                 for (const attachment of email.attachments) { | 
					
						
							|  |  |  |                   const ext = this.getFileExtension(attachment.filename); | 
					
						
							|  |  |  |                   if (scanner.blockedExtensions.includes(ext)) { | 
					
						
							|  |  |  |                     if (scanner.action === 'reject') { | 
					
						
							|  |  |  |                       throw new Error(`Blocked attachment type: ${ext}`); | 
					
						
							|  |  |  |                     } else { // tag
 | 
					
						
							|  |  |  |                       email.addHeader('X-Attachment-Warning', `Potentially unsafe attachment: ${attachment.filename}`); | 
					
						
							|  |  |  |                     } | 
					
						
							|  |  |  |                   } | 
					
						
							|  |  |  |                 } | 
					
						
							|  |  |  |               } | 
					
						
							|  |  |  |               break; | 
					
						
							|  |  |  |           } | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |        | 
					
						
							|  |  |  |       // Apply transformations if defined
 | 
					
						
							|  |  |  |       if (route?.action.options?.transformations && route.action.options.transformations.length > 0) { | 
					
						
							|  |  |  |         logger.log('info', 'Applying email transformations'); | 
					
						
							|  |  |  |          | 
					
						
							|  |  |  |         for (const transform of route.action.options.transformations) { | 
					
						
							|  |  |  |           switch (transform.type) { | 
					
						
							|  |  |  |             case 'addHeader': | 
					
						
							|  |  |  |               if (transform.header && transform.value) { | 
					
						
							|  |  |  |                 email.addHeader(transform.header, transform.value); | 
					
						
							|  |  |  |               } | 
					
						
							|  |  |  |               break; | 
					
						
							|  |  |  |           } | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |        | 
					
						
							|  |  |  |       logger.log('info', `Email successfully processed in store-and-forward mode`); | 
					
						
							|  |  |  |        | 
					
						
							|  |  |  |       SecurityLogger.getInstance().logEvent({ | 
					
						
							|  |  |  |         level: SecurityLogLevel.INFO, | 
					
						
							|  |  |  |         type: SecurityEventType.EMAIL_PROCESSING, | 
					
						
							|  |  |  |         message: 'Email processed and queued', | 
					
						
							|  |  |  |         ipAddress: session.remoteAddress, | 
					
						
							|  |  |  |         details: { | 
					
						
							|  |  |  |           sessionId: session.id, | 
					
						
							|  |  |  |           ruleName: route?.name || 'default', | 
					
						
							|  |  |  |           contentScanning: route?.action.options?.contentScanning || false, | 
					
						
							|  |  |  |           subject: email.subject | 
					
						
							|  |  |  |         }, | 
					
						
							|  |  |  |         success: true | 
					
						
							|  |  |  |       }); | 
					
						
							|  |  |  |     } catch (error) { | 
					
						
							|  |  |  |       logger.log('error', `Failed to process email: ${error.message}`); | 
					
						
							|  |  |  |        | 
					
						
							|  |  |  |       SecurityLogger.getInstance().logEvent({ | 
					
						
							|  |  |  |         level: SecurityLogLevel.ERROR, | 
					
						
							|  |  |  |         type: SecurityEventType.EMAIL_PROCESSING, | 
					
						
							|  |  |  |         message: 'Email processing failed', | 
					
						
							|  |  |  |         ipAddress: session.remoteAddress, | 
					
						
							|  |  |  |         details: { | 
					
						
							|  |  |  |           sessionId: session.id, | 
					
						
							|  |  |  |           ruleName: session.matchedRoute?.name || 'default', | 
					
						
							|  |  |  |           error: error.message | 
					
						
							|  |  |  |         }, | 
					
						
							|  |  |  |         success: false | 
					
						
							|  |  |  |       }); | 
					
						
							|  |  |  |        | 
					
						
							|  |  |  |       throw error; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  |    | 
					
						
							|  |  |  |   /** | 
					
						
							|  |  |  |    * Get file extension from filename | 
					
						
							|  |  |  |    */ | 
					
						
							|  |  |  |   private getFileExtension(filename: string): string { | 
					
						
							|  |  |  |     return filename.substring(filename.lastIndexOf('.')).toLowerCase(); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  |    | 
					
						
							|  |  |  |    | 
					
						
							|  |  |  |    | 
					
						
							|  |  |  |   /** | 
					
						
							|  |  |  |    * Set up DKIM configuration for all domains | 
					
						
							|  |  |  |    */ | 
					
						
							|  |  |  |   private async setupDkimForDomains(): Promise<void> { | 
					
						
							|  |  |  |     const domainConfigs = this.domainRegistry.getAllConfigs(); | 
					
						
							|  |  |  |      | 
					
						
							|  |  |  |     if (domainConfigs.length === 0) { | 
					
						
							|  |  |  |       logger.log('warn', 'No domains configured for DKIM'); | 
					
						
							|  |  |  |       return; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |      | 
					
						
							|  |  |  |     for (const domainConfig of domainConfigs) { | 
					
						
							|  |  |  |       const domain = domainConfig.domain; | 
					
						
							|  |  |  |       const selector = domainConfig.dkim?.selector || 'default'; | 
					
						
							|  |  |  |        | 
					
						
							|  |  |  |       try { | 
					
						
							|  |  |  |         // Check if DKIM keys already exist for this domain
 | 
					
						
							|  |  |  |         let keyPair: { privateKey: string; publicKey: string }; | 
					
						
							|  |  |  |          | 
					
						
							|  |  |  |         try { | 
					
						
							|  |  |  |           // Try to read existing keys
 | 
					
						
							|  |  |  |           keyPair = await this.dkimCreator.readDKIMKeys(domain); | 
					
						
							|  |  |  |           logger.log('info', `Using existing DKIM keys for domain: ${domain}`); | 
					
						
							|  |  |  |         } catch (error) { | 
					
						
							|  |  |  |           // Generate new keys if they don't exist
 | 
					
						
							|  |  |  |           keyPair = await this.dkimCreator.createDKIMKeys(); | 
					
						
							|  |  |  |           // Store them for future use
 | 
					
						
							|  |  |  |           await this.dkimCreator.createAndStoreDKIMKeys(domain); | 
					
						
							|  |  |  |           logger.log('info', `Generated new DKIM keys for domain: ${domain}`); | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |          | 
					
						
							|  |  |  |         // Store the private key for signing
 | 
					
						
							|  |  |  |         this.dkimKeys.set(domain, keyPair.privateKey); | 
					
						
							|  |  |  |          | 
					
						
							|  |  |  |         // DNS record creation is now handled by DnsManager
 | 
					
						
							|  |  |  |         logger.log('info', `DKIM keys loaded for domain: ${domain} with selector: ${selector}`); | 
					
						
							|  |  |  |       } catch (error) { | 
					
						
							|  |  |  |         logger.log('error', `Failed to set up DKIM for domain ${domain}: ${error.message}`); | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  |    | 
					
						
							|  |  |  |    | 
					
						
							|  |  |  |   /** | 
					
						
							|  |  |  |    * Apply per-domain rate limits from domain configurations | 
					
						
							|  |  |  |    */ | 
					
						
							|  |  |  |   private applyDomainRateLimits(): void { | 
					
						
							|  |  |  |     const domainConfigs = this.domainRegistry.getAllConfigs(); | 
					
						
							|  |  |  |      | 
					
						
							|  |  |  |     for (const domainConfig of domainConfigs) { | 
					
						
							|  |  |  |       if (domainConfig.rateLimits) { | 
					
						
							|  |  |  |         const domain = domainConfig.domain; | 
					
						
							|  |  |  |         const rateLimitConfig: any = {}; | 
					
						
							|  |  |  |          | 
					
						
							|  |  |  |         // Convert domain-specific rate limits to the format expected by UnifiedRateLimiter
 | 
					
						
							|  |  |  |         if (domainConfig.rateLimits.outbound) { | 
					
						
							|  |  |  |           if (domainConfig.rateLimits.outbound.messagesPerMinute) { | 
					
						
							|  |  |  |             rateLimitConfig.maxMessagesPerMinute = domainConfig.rateLimits.outbound.messagesPerMinute; | 
					
						
							|  |  |  |           } | 
					
						
							|  |  |  |           // Note: messagesPerHour and messagesPerDay would need additional implementation in rate limiter
 | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |          | 
					
						
							|  |  |  |         if (domainConfig.rateLimits.inbound) { | 
					
						
							|  |  |  |           if (domainConfig.rateLimits.inbound.messagesPerMinute) { | 
					
						
							|  |  |  |             rateLimitConfig.maxMessagesPerMinute = domainConfig.rateLimits.inbound.messagesPerMinute; | 
					
						
							|  |  |  |           } | 
					
						
							|  |  |  |           if (domainConfig.rateLimits.inbound.connectionsPerIp) { | 
					
						
							|  |  |  |             rateLimitConfig.maxConnectionsPerIP = domainConfig.rateLimits.inbound.connectionsPerIp; | 
					
						
							|  |  |  |           } | 
					
						
							|  |  |  |           if (domainConfig.rateLimits.inbound.recipientsPerMessage) { | 
					
						
							|  |  |  |             rateLimitConfig.maxRecipientsPerMessage = domainConfig.rateLimits.inbound.recipientsPerMessage; | 
					
						
							|  |  |  |           } | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |          | 
					
						
							|  |  |  |         // Apply the rate limits if we have any
 | 
					
						
							|  |  |  |         if (Object.keys(rateLimitConfig).length > 0) { | 
					
						
							|  |  |  |           this.rateLimiter.applyDomainLimits(domain, rateLimitConfig); | 
					
						
							|  |  |  |           logger.log('info', `Applied rate limits for domain ${domain}:`, rateLimitConfig); | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  |    | 
					
						
							|  |  |  |   /** | 
					
						
							|  |  |  |    * Check and rotate DKIM keys if needed | 
					
						
							|  |  |  |    */ | 
					
						
							|  |  |  |   private async checkAndRotateDkimKeys(): Promise<void> { | 
					
						
							|  |  |  |     const domainConfigs = this.domainRegistry.getAllConfigs(); | 
					
						
							|  |  |  |      | 
					
						
							|  |  |  |     for (const domainConfig of domainConfigs) { | 
					
						
							|  |  |  |       const domain = domainConfig.domain; | 
					
						
							|  |  |  |       const selector = domainConfig.dkim?.selector || 'default'; | 
					
						
							|  |  |  |       const rotateKeys = domainConfig.dkim?.rotateKeys || false; | 
					
						
							|  |  |  |       const rotationInterval = domainConfig.dkim?.rotationInterval || 90; | 
					
						
							|  |  |  |       const keySize = domainConfig.dkim?.keySize || 2048; | 
					
						
							|  |  |  |        | 
					
						
							|  |  |  |       if (!rotateKeys) { | 
					
						
							|  |  |  |         logger.log('debug', `DKIM key rotation disabled for ${domain}`); | 
					
						
							|  |  |  |         continue; | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |        | 
					
						
							|  |  |  |       try { | 
					
						
							|  |  |  |         // Check if keys need rotation
 | 
					
						
							|  |  |  |         const needsRotation = await this.dkimCreator.needsRotation(domain, selector, rotationInterval); | 
					
						
							|  |  |  |          | 
					
						
							|  |  |  |         if (needsRotation) { | 
					
						
							|  |  |  |           logger.log('info', `DKIM keys need rotation for ${domain} (selector: ${selector})`); | 
					
						
							|  |  |  |            | 
					
						
							|  |  |  |           // Rotate the keys
 | 
					
						
							|  |  |  |           const newSelector = await this.dkimCreator.rotateDkimKeys(domain, selector, keySize); | 
					
						
							|  |  |  |            | 
					
						
							|  |  |  |           // Update the domain config with new selector
 | 
					
						
							|  |  |  |           domainConfig.dkim = { | 
					
						
							|  |  |  |             ...domainConfig.dkim, | 
					
						
							|  |  |  |             selector: newSelector | 
					
						
							|  |  |  |           }; | 
					
						
							|  |  |  |            | 
					
						
							|  |  |  |           // Re-register DNS handler for new selector if internal-dns mode
 | 
					
						
							|  |  |  |           if (domainConfig.dnsMode === 'internal-dns' && this.dcRouter.dnsServer) { | 
					
						
							|  |  |  |             // Get new public key
 | 
					
						
							|  |  |  |             const keyPair = await this.dkimCreator.readDKIMKeysForSelector(domain, newSelector); | 
					
						
							|  |  |  |             const publicKeyBase64 = keyPair.publicKey | 
					
						
							|  |  |  |               .replace(/-----BEGIN PUBLIC KEY-----/g, '') | 
					
						
							|  |  |  |               .replace(/-----END PUBLIC KEY-----/g, '') | 
					
						
							|  |  |  |               .replace(/\s/g, ''); | 
					
						
							|  |  |  |              | 
					
						
							|  |  |  |             const ttl = domainConfig.dns?.internal?.ttl || 3600; | 
					
						
							|  |  |  |              | 
					
						
							|  |  |  |             // Register new selector
 | 
					
						
							|  |  |  |             this.dcRouter.dnsServer.registerHandler( | 
					
						
							|  |  |  |               `${newSelector}._domainkey.${domain}`, | 
					
						
							|  |  |  |               ['TXT'], | 
					
						
							|  |  |  |               () => ({ | 
					
						
							|  |  |  |                 name: `${newSelector}._domainkey.${domain}`, | 
					
						
							|  |  |  |                 type: 'TXT', | 
					
						
							|  |  |  |                 class: 'IN', | 
					
						
							|  |  |  |                 ttl: ttl, | 
					
						
							|  |  |  |                 data: `v=DKIM1; k=rsa; p=${publicKeyBase64}` | 
					
						
							|  |  |  |               }) | 
					
						
							|  |  |  |             ); | 
					
						
							|  |  |  |              | 
					
						
							|  |  |  |             logger.log('info', `DKIM DNS handler registered for new selector: ${newSelector}._domainkey.${domain}`); | 
					
						
							|  |  |  |              | 
					
						
							|  |  |  |             // Store the updated public key in storage
 | 
					
						
							|  |  |  |             await this.dcRouter.storageManager.set( | 
					
						
							|  |  |  |               `/email/dkim/${domain}/public.key`, | 
					
						
							|  |  |  |               keyPair.publicKey | 
					
						
							|  |  |  |             ); | 
					
						
							|  |  |  |           } | 
					
						
							|  |  |  |            | 
					
						
							|  |  |  |           // Clean up old keys after grace period (async, don't wait)
 | 
					
						
							|  |  |  |           this.dkimCreator.cleanupOldKeys(domain, 30).catch(error => { | 
					
						
							|  |  |  |             logger.log('warn', `Failed to cleanup old DKIM keys for ${domain}: ${error.message}`); | 
					
						
							|  |  |  |           }); | 
					
						
							|  |  |  |            | 
					
						
							|  |  |  |         } else { | 
					
						
							|  |  |  |           logger.log('debug', `DKIM keys for ${domain} are up to date`); | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |       } catch (error) { | 
					
						
							|  |  |  |         logger.log('error', `Failed to check/rotate DKIM keys for ${domain}: ${error.message}`); | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  |    | 
					
						
							|  |  |  |    | 
					
						
							|  |  |  |   /** | 
					
						
							|  |  |  |    * Generate SmartProxy routes for email ports | 
					
						
							|  |  |  |    */ | 
					
						
							|  |  |  |   public generateProxyRoutes(portMapping?: Record<number, number>): any[] { | 
					
						
							|  |  |  |     const routes: any[] = []; | 
					
						
							|  |  |  |     const defaultPortMapping = { | 
					
						
							|  |  |  |       25: 10025, | 
					
						
							|  |  |  |       587: 10587, | 
					
						
							|  |  |  |       465: 10465 | 
					
						
							|  |  |  |     }; | 
					
						
							|  |  |  |      | 
					
						
							|  |  |  |     const actualPortMapping = portMapping || defaultPortMapping; | 
					
						
							|  |  |  |      | 
					
						
							|  |  |  |     // Generate routes for each configured port
 | 
					
						
							|  |  |  |     for (const externalPort of this.options.ports) { | 
					
						
							|  |  |  |       const internalPort = actualPortMapping[externalPort] || externalPort + 10000; | 
					
						
							|  |  |  |        | 
					
						
							|  |  |  |       let routeName = 'email-route'; | 
					
						
							|  |  |  |       let tlsMode = 'passthrough'; | 
					
						
							|  |  |  |        | 
					
						
							|  |  |  |       // Configure based on port
 | 
					
						
							|  |  |  |       switch (externalPort) { | 
					
						
							|  |  |  |         case 25: | 
					
						
							|  |  |  |           routeName = 'smtp-route'; | 
					
						
							|  |  |  |           tlsMode = 'passthrough'; // STARTTLS
 | 
					
						
							|  |  |  |           break; | 
					
						
							|  |  |  |         case 587: | 
					
						
							|  |  |  |           routeName = 'submission-route'; | 
					
						
							|  |  |  |           tlsMode = 'passthrough'; // STARTTLS
 | 
					
						
							|  |  |  |           break; | 
					
						
							|  |  |  |         case 465: | 
					
						
							|  |  |  |           routeName = 'smtps-route'; | 
					
						
							|  |  |  |           tlsMode = 'terminate'; // Implicit TLS
 | 
					
						
							|  |  |  |           break; | 
					
						
							|  |  |  |         default: | 
					
						
							|  |  |  |           routeName = `email-port-${externalPort}-route`; | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |        | 
					
						
							|  |  |  |       routes.push({ | 
					
						
							|  |  |  |         name: routeName, | 
					
						
							|  |  |  |         match: { | 
					
						
							|  |  |  |           ports: [externalPort] | 
					
						
							|  |  |  |         }, | 
					
						
							|  |  |  |         action: { | 
					
						
							|  |  |  |           type: 'forward', | 
					
						
							|  |  |  |           target: { | 
					
						
							|  |  |  |             host: 'localhost', | 
					
						
							|  |  |  |             port: internalPort | 
					
						
							|  |  |  |           }, | 
					
						
							|  |  |  |           tls: { | 
					
						
							|  |  |  |             mode: tlsMode | 
					
						
							|  |  |  |           } | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |       }); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |      | 
					
						
							|  |  |  |     return routes; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  |    | 
					
						
							|  |  |  |   /** | 
					
						
							|  |  |  |    * Update server configuration | 
					
						
							|  |  |  |    */ | 
					
						
							|  |  |  |   public updateOptions(options: Partial<IUnifiedEmailServerOptions>): void { | 
					
						
							|  |  |  |     // Stop the server if changing ports
 | 
					
						
							|  |  |  |     const portsChanged = options.ports &&  | 
					
						
							|  |  |  |       (!this.options.ports ||  | 
					
						
							|  |  |  |        JSON.stringify(options.ports) !== JSON.stringify(this.options.ports)); | 
					
						
							|  |  |  |      | 
					
						
							|  |  |  |     if (portsChanged) { | 
					
						
							|  |  |  |       this.stop().then(() => { | 
					
						
							|  |  |  |         this.options = { ...this.options, ...options }; | 
					
						
							|  |  |  |         this.start(); | 
					
						
							|  |  |  |       }); | 
					
						
							|  |  |  |     } else { | 
					
						
							|  |  |  |       // Update options without restart
 | 
					
						
							|  |  |  |       this.options = { ...this.options, ...options }; | 
					
						
							|  |  |  |        | 
					
						
							|  |  |  |       // Update domain registry if domains changed
 | 
					
						
							|  |  |  |       if (options.domains) { | 
					
						
							|  |  |  |         this.domainRegistry = new DomainRegistry(options.domains, options.defaults || this.options.defaults); | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |        | 
					
						
							|  |  |  |       // Update email router if routes changed
 | 
					
						
							|  |  |  |       if (options.routes) { | 
					
						
							|  |  |  |         this.emailRouter.updateRoutes(options.routes); | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  |    | 
					
						
							|  |  |  |   /** | 
					
						
							|  |  |  |    * Update email routes | 
					
						
							|  |  |  |    */ | 
					
						
							|  |  |  |   public updateEmailRoutes(routes: IEmailRoute[]): void { | 
					
						
							|  |  |  |     this.options.routes = routes; | 
					
						
							|  |  |  |     this.emailRouter.updateRoutes(routes); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  |    | 
					
						
							|  |  |  |   /** | 
					
						
							|  |  |  |    * Get server statistics | 
					
						
							|  |  |  |    */ | 
					
						
							|  |  |  |   public getStats(): IServerStats { | 
					
						
							|  |  |  |     return { ...this.stats }; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  |    | 
					
						
							|  |  |  |   /** | 
					
						
							|  |  |  |    * Get domain registry | 
					
						
							|  |  |  |    */ | 
					
						
							|  |  |  |   public getDomainRegistry(): DomainRegistry { | 
					
						
							|  |  |  |     return this.domainRegistry; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  |    | 
					
						
							|  |  |  |   /** | 
					
						
							|  |  |  |    * Update email routes dynamically | 
					
						
							|  |  |  |    */ | 
					
						
							|  |  |  |   public updateRoutes(routes: IEmailRoute[]): void { | 
					
						
							|  |  |  |     this.emailRouter.setRoutes(routes); | 
					
						
							|  |  |  |     logger.log('info', `Updated email routes with ${routes.length} routes`); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  |    | 
					
						
							|  |  |  |   /** | 
					
						
							|  |  |  |    * Send an email through the delivery system | 
					
						
							|  |  |  |    * @param email The email to send | 
					
						
							|  |  |  |    * @param mode The processing mode to use | 
					
						
							|  |  |  |    * @param rule Optional rule to apply | 
					
						
							|  |  |  |    * @param options Optional sending options | 
					
						
							|  |  |  |    * @returns The ID of the queued email | 
					
						
							|  |  |  |    */ | 
					
						
							|  |  |  |   public async sendEmail( | 
					
						
							|  |  |  |     email: Email,  | 
					
						
							|  |  |  |     mode: EmailProcessingMode = 'mta',  | 
					
						
							|  |  |  |     route?: IEmailRoute,  | 
					
						
							|  |  |  |     options?: { | 
					
						
							|  |  |  |       skipSuppressionCheck?: boolean; | 
					
						
							|  |  |  |       ipAddress?: string; | 
					
						
							|  |  |  |       isTransactional?: boolean; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |   ): Promise<string> { | 
					
						
							|  |  |  |     logger.log('info', `Sending email: ${email.subject} to ${email.to.join(', ')}`); | 
					
						
							|  |  |  |      | 
					
						
							|  |  |  |     try { | 
					
						
							|  |  |  |       // Validate the email
 | 
					
						
							|  |  |  |       if (!email.from) { | 
					
						
							|  |  |  |         throw new Error('Email must have a sender address'); | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |        | 
					
						
							|  |  |  |       if (!email.to || email.to.length === 0) { | 
					
						
							|  |  |  |         throw new Error('Email must have at least one recipient'); | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |        | 
					
						
							|  |  |  |       // Check if any recipients are on the suppression list (unless explicitly skipped)
 | 
					
						
							|  |  |  |       if (!options?.skipSuppressionCheck) { | 
					
						
							|  |  |  |         const suppressedRecipients = email.to.filter(recipient => this.isEmailSuppressed(recipient)); | 
					
						
							|  |  |  |          | 
					
						
							|  |  |  |         if (suppressedRecipients.length > 0) { | 
					
						
							|  |  |  |           // Filter out suppressed recipients
 | 
					
						
							|  |  |  |           const originalCount = email.to.length; | 
					
						
							|  |  |  |           const suppressed = suppressedRecipients.map(recipient => { | 
					
						
							|  |  |  |             const info = this.getSuppressionInfo(recipient); | 
					
						
							|  |  |  |             return { | 
					
						
							|  |  |  |               email: recipient, | 
					
						
							|  |  |  |               reason: info?.reason || 'Unknown', | 
					
						
							|  |  |  |               until: info?.expiresAt ? new Date(info.expiresAt).toISOString() : 'permanent' | 
					
						
							|  |  |  |             }; | 
					
						
							|  |  |  |           }); | 
					
						
							|  |  |  |            | 
					
						
							|  |  |  |           logger.log('warn', `Filtering out ${suppressedRecipients.length} suppressed recipient(s)`, { suppressed }); | 
					
						
							|  |  |  |            | 
					
						
							|  |  |  |           // If all recipients are suppressed, throw an error
 | 
					
						
							|  |  |  |           if (suppressedRecipients.length === originalCount) { | 
					
						
							|  |  |  |             throw new Error('All recipients are on the suppression list'); | 
					
						
							|  |  |  |           } | 
					
						
							|  |  |  |            | 
					
						
							|  |  |  |           // Filter the recipients list to only include non-suppressed addresses
 | 
					
						
							|  |  |  |           email.to = email.to.filter(recipient => !this.isEmailSuppressed(recipient)); | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |        | 
					
						
							|  |  |  |       // IP warmup handling
 | 
					
						
							|  |  |  |       let ipAddress = options?.ipAddress; | 
					
						
							|  |  |  |        | 
					
						
							|  |  |  |       // If no specific IP was provided, use IP warmup manager to find the best IP
 | 
					
						
							|  |  |  |       if (!ipAddress) { | 
					
						
							|  |  |  |         const domain = email.from.split('@')[1]; | 
					
						
							|  |  |  |          | 
					
						
							|  |  |  |         ipAddress = this.getBestIPForSending({ | 
					
						
							|  |  |  |           from: email.from, | 
					
						
							|  |  |  |           to: email.to, | 
					
						
							|  |  |  |           domain, | 
					
						
							|  |  |  |           isTransactional: options?.isTransactional | 
					
						
							|  |  |  |         }); | 
					
						
							|  |  |  |          | 
					
						
							|  |  |  |         if (ipAddress) { | 
					
						
							|  |  |  |           logger.log('info', `Selected IP ${ipAddress} for sending based on warmup status`); | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |        | 
					
						
							|  |  |  |       // If an IP is provided or selected by warmup manager, check its capacity
 | 
					
						
							|  |  |  |       if (ipAddress) { | 
					
						
							|  |  |  |         // Check if the IP can send more today
 | 
					
						
							|  |  |  |         if (!this.canIPSendMoreToday(ipAddress)) { | 
					
						
							|  |  |  |           logger.log('warn', `IP ${ipAddress} has reached its daily sending limit, email will be queued for later delivery`); | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |          | 
					
						
							|  |  |  |         // Check if the IP can send more this hour
 | 
					
						
							|  |  |  |         if (!this.canIPSendMoreThisHour(ipAddress)) { | 
					
						
							|  |  |  |           logger.log('warn', `IP ${ipAddress} has reached its hourly sending limit, email will be queued for later delivery`); | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |          | 
					
						
							|  |  |  |         // Record the send for IP warmup tracking
 | 
					
						
							|  |  |  |         this.recordIPSend(ipAddress); | 
					
						
							|  |  |  |          | 
					
						
							|  |  |  |         // Add IP header to the email
 | 
					
						
							|  |  |  |         email.addHeader('X-Sending-IP', ipAddress); | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |        | 
					
						
							|  |  |  |       // Check if the sender domain has DKIM keys and sign the email if needed
 | 
					
						
							|  |  |  |       if (mode === 'mta' && route?.action.options?.mtaOptions?.dkimSign) { | 
					
						
							|  |  |  |         const domain = email.from.split('@')[1]; | 
					
						
							|  |  |  |         await this.handleDkimSigning(email, domain, route.action.options.mtaOptions.dkimOptions?.keySelector || 'mta'); | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |        | 
					
						
							|  |  |  |       // Generate a unique ID for this email
 | 
					
						
							|  |  |  |       const id = plugins.uuid.v4(); | 
					
						
							|  |  |  |        | 
					
						
							|  |  |  |       // Queue the email for delivery
 | 
					
						
							|  |  |  |       await this.deliveryQueue.enqueue(email, mode, route); | 
					
						
							|  |  |  |        | 
					
						
							|  |  |  |       // Record 'sent' event for domain reputation monitoring
 | 
					
						
							|  |  |  |       const senderDomain = email.from.split('@')[1]; | 
					
						
							|  |  |  |       if (senderDomain) { | 
					
						
							|  |  |  |         this.recordReputationEvent(senderDomain, { | 
					
						
							|  |  |  |           type: 'sent', | 
					
						
							|  |  |  |           count: email.to.length | 
					
						
							|  |  |  |         }); | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |        | 
					
						
							|  |  |  |       logger.log('info', `Email queued with ID: ${id}`); | 
					
						
							|  |  |  |       return id; | 
					
						
							|  |  |  |     } catch (error) { | 
					
						
							|  |  |  |       logger.log('error', `Failed to send email: ${error.message}`); | 
					
						
							|  |  |  |       throw error; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  |    | 
					
						
							|  |  |  |   /** | 
					
						
							|  |  |  |    * Handle DKIM signing for an email | 
					
						
							|  |  |  |    * @param email The email to sign | 
					
						
							|  |  |  |    * @param domain The domain to sign with | 
					
						
							|  |  |  |    * @param selector The DKIM selector | 
					
						
							|  |  |  |    */ | 
					
						
							|  |  |  |   private async handleDkimSigning(email: Email, domain: string, selector: string): Promise<void> { | 
					
						
							|  |  |  |     try { | 
					
						
							|  |  |  |       // Ensure we have DKIM keys for this domain
 | 
					
						
							|  |  |  |       await this.dkimCreator.handleDKIMKeysForDomain(domain); | 
					
						
							|  |  |  |        | 
					
						
							|  |  |  |       // Get the private key
 | 
					
						
							|  |  |  |       const { privateKey } = await this.dkimCreator.readDKIMKeys(domain); | 
					
						
							|  |  |  |        | 
					
						
							|  |  |  |       // Convert Email to raw format for signing
 | 
					
						
							|  |  |  |       const rawEmail = email.toRFC822String(); | 
					
						
							|  |  |  |        | 
					
						
							|  |  |  |       // Sign the email
 | 
					
						
							|  |  |  |       const signResult = await plugins.dkimSign(rawEmail, { | 
					
						
							|  |  |  |         canonicalization: 'relaxed/relaxed', | 
					
						
							|  |  |  |         algorithm: 'rsa-sha256', | 
					
						
							|  |  |  |         signTime: new Date(), | 
					
						
							|  |  |  |         signatureData: [ | 
					
						
							|  |  |  |           { | 
					
						
							|  |  |  |             signingDomain: domain, | 
					
						
							|  |  |  |             selector: selector, | 
					
						
							|  |  |  |             privateKey: privateKey, | 
					
						
							|  |  |  |             algorithm: 'rsa-sha256', | 
					
						
							|  |  |  |             canonicalization: 'relaxed/relaxed' | 
					
						
							|  |  |  |           } | 
					
						
							|  |  |  |         ] | 
					
						
							|  |  |  |       }); | 
					
						
							|  |  |  |        | 
					
						
							|  |  |  |       // Add the DKIM-Signature header to the email
 | 
					
						
							|  |  |  |       if (signResult.signatures) { | 
					
						
							|  |  |  |         email.addHeader('DKIM-Signature', signResult.signatures); | 
					
						
							|  |  |  |         logger.log('info', `Successfully added DKIM signature for ${domain}`); | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |     } catch (error) { | 
					
						
							|  |  |  |       logger.log('error', `Failed to sign email with DKIM: ${error.message}`); | 
					
						
							|  |  |  |       // Continue without DKIM rather than failing the send
 | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  |    | 
					
						
							|  |  |  |   /** | 
					
						
							|  |  |  |    * Process a bounce notification email | 
					
						
							|  |  |  |    * @param bounceEmail The email containing bounce notification information | 
					
						
							|  |  |  |    * @returns Processed bounce record or null if not a bounce | 
					
						
							|  |  |  |    */ | 
					
						
							|  |  |  |   public async processBounceNotification(bounceEmail: Email): Promise<boolean> { | 
					
						
							|  |  |  |     logger.log('info', 'Processing potential bounce notification email'); | 
					
						
							|  |  |  |      | 
					
						
							|  |  |  |     try { | 
					
						
							|  |  |  |       // Process as a bounce notification (no conversion needed anymore)
 | 
					
						
							|  |  |  |       const bounceRecord = await this.bounceManager.processBounceEmail(bounceEmail); | 
					
						
							|  |  |  |        | 
					
						
							|  |  |  |       if (bounceRecord) { | 
					
						
							|  |  |  |         logger.log('info', `Successfully processed bounce notification for ${bounceRecord.recipient}`, { | 
					
						
							|  |  |  |           bounceType: bounceRecord.bounceType, | 
					
						
							|  |  |  |           bounceCategory: bounceRecord.bounceCategory | 
					
						
							|  |  |  |         }); | 
					
						
							|  |  |  |          | 
					
						
							|  |  |  |         // Notify any registered listeners about the bounce
 | 
					
						
							|  |  |  |         this.emit('bounceProcessed', bounceRecord); | 
					
						
							|  |  |  |          | 
					
						
							|  |  |  |         // Record bounce event for domain reputation tracking
 | 
					
						
							|  |  |  |         if (bounceRecord.domain) { | 
					
						
							|  |  |  |           this.recordReputationEvent(bounceRecord.domain, { | 
					
						
							|  |  |  |             type: 'bounce', | 
					
						
							|  |  |  |             hardBounce: bounceRecord.bounceCategory === BounceCategory.HARD, | 
					
						
							|  |  |  |             receivingDomain: bounceRecord.recipient.split('@')[1] | 
					
						
							|  |  |  |           }); | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |          | 
					
						
							|  |  |  |         // Log security event
 | 
					
						
							|  |  |  |         SecurityLogger.getInstance().logEvent({ | 
					
						
							|  |  |  |           level: SecurityLogLevel.INFO, | 
					
						
							|  |  |  |           type: SecurityEventType.EMAIL_VALIDATION, | 
					
						
							|  |  |  |           message: `Bounce notification processed for recipient`, | 
					
						
							|  |  |  |           domain: bounceRecord.domain, | 
					
						
							|  |  |  |           details: { | 
					
						
							|  |  |  |             recipient: bounceRecord.recipient, | 
					
						
							|  |  |  |             bounceType: bounceRecord.bounceType, | 
					
						
							|  |  |  |             bounceCategory: bounceRecord.bounceCategory | 
					
						
							|  |  |  |           }, | 
					
						
							|  |  |  |           success: true | 
					
						
							|  |  |  |         }); | 
					
						
							|  |  |  |          | 
					
						
							|  |  |  |         return true; | 
					
						
							|  |  |  |       } else { | 
					
						
							|  |  |  |         logger.log('info', 'Email not recognized as a bounce notification'); | 
					
						
							|  |  |  |         return false; | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |     } catch (error) { | 
					
						
							|  |  |  |       logger.log('error', `Error processing bounce notification: ${error.message}`); | 
					
						
							|  |  |  |        | 
					
						
							|  |  |  |       SecurityLogger.getInstance().logEvent({ | 
					
						
							|  |  |  |         level: SecurityLogLevel.ERROR, | 
					
						
							|  |  |  |         type: SecurityEventType.EMAIL_VALIDATION, | 
					
						
							|  |  |  |         message: 'Failed to process bounce notification', | 
					
						
							|  |  |  |         details: { | 
					
						
							|  |  |  |           error: error.message, | 
					
						
							|  |  |  |           subject: bounceEmail.subject | 
					
						
							|  |  |  |         }, | 
					
						
							|  |  |  |         success: false | 
					
						
							|  |  |  |       }); | 
					
						
							|  |  |  |        | 
					
						
							|  |  |  |       return false; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  |    | 
					
						
							|  |  |  |   /** | 
					
						
							|  |  |  |    * Process an SMTP failure as a bounce | 
					
						
							|  |  |  |    * @param recipient Recipient email that failed | 
					
						
							|  |  |  |    * @param smtpResponse SMTP error response | 
					
						
							|  |  |  |    * @param options Additional options for bounce processing | 
					
						
							|  |  |  |    * @returns Processed bounce record | 
					
						
							|  |  |  |    */ | 
					
						
							|  |  |  |   public async processSmtpFailure( | 
					
						
							|  |  |  |     recipient: string, | 
					
						
							|  |  |  |     smtpResponse: string, | 
					
						
							|  |  |  |     options: { | 
					
						
							|  |  |  |       sender?: string; | 
					
						
							|  |  |  |       originalEmailId?: string; | 
					
						
							|  |  |  |       statusCode?: string; | 
					
						
							|  |  |  |       headers?: Record<string, string>; | 
					
						
							|  |  |  |     } = {} | 
					
						
							|  |  |  |   ): Promise<boolean> { | 
					
						
							|  |  |  |     logger.log('info', `Processing SMTP failure for ${recipient}: ${smtpResponse}`); | 
					
						
							|  |  |  |      | 
					
						
							|  |  |  |     try { | 
					
						
							|  |  |  |       // Process the SMTP failure through the bounce manager
 | 
					
						
							|  |  |  |       const bounceRecord = await this.bounceManager.processSmtpFailure( | 
					
						
							|  |  |  |         recipient, | 
					
						
							|  |  |  |         smtpResponse, | 
					
						
							|  |  |  |         options | 
					
						
							|  |  |  |       ); | 
					
						
							|  |  |  |        | 
					
						
							|  |  |  |       logger.log('info', `Successfully processed SMTP failure for ${recipient} as ${bounceRecord.bounceCategory} bounce`, { | 
					
						
							|  |  |  |         bounceType: bounceRecord.bounceType | 
					
						
							|  |  |  |       }); | 
					
						
							|  |  |  |        | 
					
						
							|  |  |  |       // Notify any registered listeners about the bounce
 | 
					
						
							|  |  |  |       this.emit('bounceProcessed', bounceRecord); | 
					
						
							|  |  |  |        | 
					
						
							|  |  |  |       // Record bounce event for domain reputation tracking
 | 
					
						
							|  |  |  |       if (bounceRecord.domain) { | 
					
						
							|  |  |  |         this.recordReputationEvent(bounceRecord.domain, { | 
					
						
							|  |  |  |           type: 'bounce', | 
					
						
							|  |  |  |           hardBounce: bounceRecord.bounceCategory === BounceCategory.HARD, | 
					
						
							|  |  |  |           receivingDomain: bounceRecord.recipient.split('@')[1] | 
					
						
							|  |  |  |         }); | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |        | 
					
						
							|  |  |  |       // Log security event
 | 
					
						
							|  |  |  |       SecurityLogger.getInstance().logEvent({ | 
					
						
							|  |  |  |         level: SecurityLogLevel.INFO, | 
					
						
							|  |  |  |         type: SecurityEventType.EMAIL_VALIDATION, | 
					
						
							|  |  |  |         message: `SMTP failure processed for recipient`, | 
					
						
							|  |  |  |         domain: bounceRecord.domain, | 
					
						
							|  |  |  |         details: { | 
					
						
							|  |  |  |           recipient: bounceRecord.recipient, | 
					
						
							|  |  |  |           bounceType: bounceRecord.bounceType, | 
					
						
							|  |  |  |           bounceCategory: bounceRecord.bounceCategory, | 
					
						
							|  |  |  |           smtpResponse | 
					
						
							|  |  |  |         }, | 
					
						
							|  |  |  |         success: true | 
					
						
							|  |  |  |       }); | 
					
						
							|  |  |  |        | 
					
						
							|  |  |  |       return true; | 
					
						
							|  |  |  |     } catch (error) { | 
					
						
							|  |  |  |       logger.log('error', `Error processing SMTP failure: ${error.message}`); | 
					
						
							|  |  |  |        | 
					
						
							|  |  |  |       SecurityLogger.getInstance().logEvent({ | 
					
						
							|  |  |  |         level: SecurityLogLevel.ERROR,  | 
					
						
							|  |  |  |         type: SecurityEventType.EMAIL_VALIDATION, | 
					
						
							|  |  |  |         message: 'Failed to process SMTP failure', | 
					
						
							|  |  |  |         details: { | 
					
						
							|  |  |  |           recipient, | 
					
						
							|  |  |  |           smtpResponse, | 
					
						
							|  |  |  |           error: error.message | 
					
						
							|  |  |  |         }, | 
					
						
							|  |  |  |         success: false | 
					
						
							|  |  |  |       }); | 
					
						
							|  |  |  |        | 
					
						
							|  |  |  |       return false; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  |    | 
					
						
							|  |  |  |   /** | 
					
						
							|  |  |  |    * Check if an email address is suppressed (has bounced previously) | 
					
						
							|  |  |  |    * @param email Email address to check | 
					
						
							|  |  |  |    * @returns Whether the email is suppressed | 
					
						
							|  |  |  |    */ | 
					
						
							|  |  |  |   public isEmailSuppressed(email: string): boolean { | 
					
						
							|  |  |  |     return this.bounceManager.isEmailSuppressed(email); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  |    | 
					
						
							|  |  |  |   /** | 
					
						
							|  |  |  |    * Get suppression information for an email | 
					
						
							|  |  |  |    * @param email Email address to check | 
					
						
							|  |  |  |    * @returns Suppression information or null if not suppressed | 
					
						
							|  |  |  |    */ | 
					
						
							|  |  |  |   public getSuppressionInfo(email: string): { | 
					
						
							|  |  |  |     reason: string; | 
					
						
							|  |  |  |     timestamp: number; | 
					
						
							|  |  |  |     expiresAt?: number; | 
					
						
							|  |  |  |   } | null { | 
					
						
							|  |  |  |     return this.bounceManager.getSuppressionInfo(email); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  |    | 
					
						
							|  |  |  |   /** | 
					
						
							|  |  |  |    * Get bounce history information for an email | 
					
						
							|  |  |  |    * @param email Email address to check | 
					
						
							|  |  |  |    * @returns Bounce history or null if no bounces | 
					
						
							|  |  |  |    */ | 
					
						
							|  |  |  |   public getBounceHistory(email: string): { | 
					
						
							|  |  |  |     lastBounce: number; | 
					
						
							|  |  |  |     count: number; | 
					
						
							|  |  |  |     type: BounceType; | 
					
						
							|  |  |  |     category: BounceCategory; | 
					
						
							|  |  |  |   } | null { | 
					
						
							|  |  |  |     return this.bounceManager.getBounceInfo(email); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  |    | 
					
						
							|  |  |  |   /** | 
					
						
							|  |  |  |    * Get all suppressed email addresses | 
					
						
							|  |  |  |    * @returns Array of suppressed email addresses | 
					
						
							|  |  |  |    */ | 
					
						
							|  |  |  |   public getSuppressionList(): string[] { | 
					
						
							|  |  |  |     return this.bounceManager.getSuppressionList(); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  |    | 
					
						
							|  |  |  |   /** | 
					
						
							|  |  |  |    * Get all hard bounced email addresses | 
					
						
							|  |  |  |    * @returns Array of hard bounced email addresses | 
					
						
							|  |  |  |    */ | 
					
						
							|  |  |  |   public getHardBouncedAddresses(): string[] { | 
					
						
							|  |  |  |     return this.bounceManager.getHardBouncedAddresses(); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  |    | 
					
						
							|  |  |  |   /** | 
					
						
							|  |  |  |    * Add an email to the suppression list | 
					
						
							|  |  |  |    * @param email Email address to suppress | 
					
						
							|  |  |  |    * @param reason Reason for suppression | 
					
						
							|  |  |  |    * @param expiresAt Optional expiration time (undefined for permanent) | 
					
						
							|  |  |  |    */ | 
					
						
							|  |  |  |   public addToSuppressionList(email: string, reason: string, expiresAt?: number): void { | 
					
						
							|  |  |  |     this.bounceManager.addToSuppressionList(email, reason, expiresAt); | 
					
						
							|  |  |  |     logger.log('info', `Added ${email} to suppression list: ${reason}`); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  |    | 
					
						
							|  |  |  |   /** | 
					
						
							|  |  |  |    * Remove an email from the suppression list | 
					
						
							|  |  |  |    * @param email Email address to remove from suppression | 
					
						
							|  |  |  |    */ | 
					
						
							|  |  |  |   public removeFromSuppressionList(email: string): void { | 
					
						
							|  |  |  |     this.bounceManager.removeFromSuppressionList(email); | 
					
						
							|  |  |  |     logger.log('info', `Removed ${email} from suppression list`); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  |    | 
					
						
							|  |  |  |   /** | 
					
						
							|  |  |  |    * Get the status of IP warmup process | 
					
						
							|  |  |  |    * @param ipAddress Optional specific IP to check | 
					
						
							|  |  |  |    * @returns Status of IP warmup | 
					
						
							|  |  |  |    */ | 
					
						
							|  |  |  |   public getIPWarmupStatus(ipAddress?: string): any { | 
					
						
							|  |  |  |     return this.ipWarmupManager.getWarmupStatus(ipAddress); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  |    | 
					
						
							|  |  |  |   /** | 
					
						
							|  |  |  |    * Add a new IP address to the warmup process | 
					
						
							|  |  |  |    * @param ipAddress IP address to add | 
					
						
							|  |  |  |    */ | 
					
						
							|  |  |  |   public addIPToWarmup(ipAddress: string): void { | 
					
						
							|  |  |  |     this.ipWarmupManager.addIPToWarmup(ipAddress); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  |    | 
					
						
							|  |  |  |   /** | 
					
						
							|  |  |  |    * Remove an IP address from the warmup process | 
					
						
							|  |  |  |    * @param ipAddress IP address to remove | 
					
						
							|  |  |  |    */ | 
					
						
							|  |  |  |   public removeIPFromWarmup(ipAddress: string): void { | 
					
						
							|  |  |  |     this.ipWarmupManager.removeIPFromWarmup(ipAddress); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  |    | 
					
						
							|  |  |  |   /** | 
					
						
							|  |  |  |    * Update metrics for an IP in the warmup process | 
					
						
							|  |  |  |    * @param ipAddress IP address | 
					
						
							|  |  |  |    * @param metrics Metrics to update | 
					
						
							|  |  |  |    */ | 
					
						
							|  |  |  |   public updateIPWarmupMetrics( | 
					
						
							|  |  |  |     ipAddress: string, | 
					
						
							|  |  |  |     metrics: { openRate?: number; bounceRate?: number; complaintRate?: number } | 
					
						
							|  |  |  |   ): void { | 
					
						
							|  |  |  |     this.ipWarmupManager.updateMetrics(ipAddress, metrics); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  |    | 
					
						
							|  |  |  |   /** | 
					
						
							|  |  |  |    * Check if an IP can send more emails today | 
					
						
							|  |  |  |    * @param ipAddress IP address to check | 
					
						
							|  |  |  |    * @returns Whether the IP can send more today | 
					
						
							|  |  |  |    */ | 
					
						
							|  |  |  |   public canIPSendMoreToday(ipAddress: string): boolean { | 
					
						
							|  |  |  |     return this.ipWarmupManager.canSendMoreToday(ipAddress); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  |    | 
					
						
							|  |  |  |   /** | 
					
						
							|  |  |  |    * Check if an IP can send more emails in the current hour | 
					
						
							|  |  |  |    * @param ipAddress IP address to check | 
					
						
							|  |  |  |    * @returns Whether the IP can send more this hour | 
					
						
							|  |  |  |    */ | 
					
						
							|  |  |  |   public canIPSendMoreThisHour(ipAddress: string): boolean { | 
					
						
							|  |  |  |     return this.ipWarmupManager.canSendMoreThisHour(ipAddress); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  |    | 
					
						
							|  |  |  |   /** | 
					
						
							|  |  |  |    * Get the best IP to use for sending an email based on warmup status | 
					
						
							|  |  |  |    * @param emailInfo Information about the email being sent | 
					
						
							|  |  |  |    * @returns Best IP to use or null | 
					
						
							|  |  |  |    */ | 
					
						
							|  |  |  |   public getBestIPForSending(emailInfo: { | 
					
						
							|  |  |  |     from: string; | 
					
						
							|  |  |  |     to: string[]; | 
					
						
							|  |  |  |     domain: string; | 
					
						
							|  |  |  |     isTransactional?: boolean; | 
					
						
							|  |  |  |   }): string | null { | 
					
						
							|  |  |  |     return this.ipWarmupManager.getBestIPForSending(emailInfo); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  |    | 
					
						
							|  |  |  |   /** | 
					
						
							|  |  |  |    * Set the active IP allocation policy for warmup | 
					
						
							|  |  |  |    * @param policyName Name of the policy to set | 
					
						
							|  |  |  |    */ | 
					
						
							|  |  |  |   public setIPAllocationPolicy(policyName: string): void { | 
					
						
							|  |  |  |     this.ipWarmupManager.setActiveAllocationPolicy(policyName); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  |    | 
					
						
							|  |  |  |   /** | 
					
						
							|  |  |  |    * Record that an email was sent using a specific IP | 
					
						
							|  |  |  |    * @param ipAddress IP address used for sending | 
					
						
							|  |  |  |    */ | 
					
						
							|  |  |  |   public recordIPSend(ipAddress: string): void { | 
					
						
							|  |  |  |     this.ipWarmupManager.recordSend(ipAddress); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  |    | 
					
						
							|  |  |  |   /** | 
					
						
							|  |  |  |    * Get reputation data for a domain | 
					
						
							|  |  |  |    * @param domain Domain to get reputation for | 
					
						
							|  |  |  |    * @returns Domain reputation metrics | 
					
						
							|  |  |  |    */ | 
					
						
							|  |  |  |   public getDomainReputationData(domain: string): any { | 
					
						
							|  |  |  |     return this.senderReputationMonitor.getReputationData(domain); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  |    | 
					
						
							|  |  |  |   /** | 
					
						
							|  |  |  |    * Get summary reputation data for all monitored domains | 
					
						
							|  |  |  |    * @returns Summary data for all domains | 
					
						
							|  |  |  |    */ | 
					
						
							|  |  |  |   public getReputationSummary(): any { | 
					
						
							|  |  |  |     return this.senderReputationMonitor.getReputationSummary(); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  |    | 
					
						
							|  |  |  |   /** | 
					
						
							|  |  |  |    * Add a domain to the reputation monitoring system | 
					
						
							|  |  |  |    * @param domain Domain to add | 
					
						
							|  |  |  |    */ | 
					
						
							|  |  |  |   public addDomainToMonitoring(domain: string): void { | 
					
						
							|  |  |  |     this.senderReputationMonitor.addDomain(domain); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  |    | 
					
						
							|  |  |  |   /** | 
					
						
							|  |  |  |    * Remove a domain from the reputation monitoring system | 
					
						
							|  |  |  |    * @param domain Domain to remove | 
					
						
							|  |  |  |    */ | 
					
						
							|  |  |  |   public removeDomainFromMonitoring(domain: string): void { | 
					
						
							|  |  |  |     this.senderReputationMonitor.removeDomain(domain); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  |    | 
					
						
							|  |  |  |   /** | 
					
						
							|  |  |  |    * Record an email event for domain reputation tracking | 
					
						
							|  |  |  |    * @param domain Domain sending the email | 
					
						
							|  |  |  |    * @param event Event details | 
					
						
							|  |  |  |    */ | 
					
						
							|  |  |  |   public recordReputationEvent(domain: string, event: { | 
					
						
							|  |  |  |     type: 'sent' | 'delivered' | 'bounce' | 'complaint' | 'open' | 'click'; | 
					
						
							|  |  |  |     count?: number; | 
					
						
							|  |  |  |     hardBounce?: boolean; | 
					
						
							|  |  |  |     receivingDomain?: string; | 
					
						
							|  |  |  |   }): void { | 
					
						
							|  |  |  |     this.senderReputationMonitor.recordSendEvent(domain, event); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  |    | 
					
						
							|  |  |  |   /** | 
					
						
							|  |  |  |    * Check if DKIM key exists for a domain | 
					
						
							|  |  |  |    * @param domain Domain to check | 
					
						
							|  |  |  |    */ | 
					
						
							|  |  |  |   public hasDkimKey(domain: string): boolean { | 
					
						
							|  |  |  |     return this.dkimKeys.has(domain); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  |    | 
					
						
							|  |  |  |   /** | 
					
						
							|  |  |  |    * Record successful email delivery | 
					
						
							|  |  |  |    * @param domain Sending domain | 
					
						
							|  |  |  |    */ | 
					
						
							|  |  |  |   public recordDelivery(domain: string): void { | 
					
						
							|  |  |  |     this.recordReputationEvent(domain, { | 
					
						
							|  |  |  |       type: 'delivered', | 
					
						
							|  |  |  |       count: 1 | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  |    | 
					
						
							|  |  |  |   /** | 
					
						
							|  |  |  |    * Record email bounce | 
					
						
							|  |  |  |    * @param domain Sending domain | 
					
						
							|  |  |  |    * @param receivingDomain Receiving domain that bounced | 
					
						
							|  |  |  |    * @param bounceType Type of bounce (hard/soft) | 
					
						
							|  |  |  |    * @param reason Bounce reason | 
					
						
							|  |  |  |    */ | 
					
						
							|  |  |  |   public recordBounce(domain: string, receivingDomain: string, bounceType: 'hard' | 'soft', reason: string): void { | 
					
						
							|  |  |  |     // Record bounce in bounce manager
 | 
					
						
							|  |  |  |     const bounceRecord = { | 
					
						
							|  |  |  |       id: `bounce_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`, | 
					
						
							|  |  |  |       recipient: `user@${receivingDomain}`, | 
					
						
							|  |  |  |       sender: `user@${domain}`, | 
					
						
							|  |  |  |       domain: domain, | 
					
						
							|  |  |  |       bounceType: bounceType === 'hard' ? BounceType.INVALID_RECIPIENT : BounceType.TEMPORARY_FAILURE, | 
					
						
							|  |  |  |       bounceCategory: bounceType === 'hard' ? BounceCategory.HARD : BounceCategory.SOFT, | 
					
						
							|  |  |  |       timestamp: Date.now(), | 
					
						
							|  |  |  |       smtpResponse: reason, | 
					
						
							|  |  |  |       diagnosticCode: reason, | 
					
						
							|  |  |  |       statusCode: bounceType === 'hard' ? '550' : '450', | 
					
						
							|  |  |  |       processed: false | 
					
						
							|  |  |  |     }; | 
					
						
							|  |  |  |      | 
					
						
							|  |  |  |     // Process the bounce
 | 
					
						
							|  |  |  |     this.bounceManager.processBounce(bounceRecord); | 
					
						
							|  |  |  |      | 
					
						
							|  |  |  |     // Record reputation event
 | 
					
						
							|  |  |  |     this.recordReputationEvent(domain, { | 
					
						
							|  |  |  |       type: 'bounce', | 
					
						
							|  |  |  |       count: 1, | 
					
						
							|  |  |  |       hardBounce: bounceType === 'hard', | 
					
						
							|  |  |  |       receivingDomain | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  |    | 
					
						
							|  |  |  |   /** | 
					
						
							|  |  |  |    * Get the rate limiter instance | 
					
						
							|  |  |  |    * @returns The unified rate limiter | 
					
						
							|  |  |  |    */ | 
					
						
							|  |  |  |   public getRateLimiter(): UnifiedRateLimiter { | 
					
						
							|  |  |  |     return this.rateLimiter; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | } |