2026-02-10 15:54:09 +00:00
import * as plugins from '../../plugins.js' ;
import * as paths from '../../paths.js' ;
import { EventEmitter } from 'events' ;
import { logger } from '../../logger.js' ;
import { SecurityLogger , SecurityLogLevel , SecurityEventType } from '../../security/index.js' ;
import { DKIMCreator } from '../security/classes.dkimcreator.js' ;
import { IPReputationChecker } from '../../security/classes.ipreputationchecker.js' ;
2026-02-10 16:38:31 +00:00
import { RustSecurityBridge } from '../../security/classes.rustsecuritybridge.js' ;
2026-02-10 15:54:09 +00:00
import { EmailRouter } from './classes.email.router.js' ;
import { Email } from '../core/classes.email.js' ;
import { DomainRegistry } from './classes.domain.registry.js' ;
import { DnsManager } from './classes.dns.manager.js' ;
import { BounceManager , BounceType , BounceCategory } from '../core/classes.bouncemanager.js' ;
import { createPooledSmtpClient } from '../delivery/smtpclient/create-client.js' ;
import { MultiModeDeliverySystem } from '../delivery/classes.delivery.system.js' ;
import { UnifiedDeliveryQueue } from '../delivery/classes.delivery.queue.js' ;
import { UnifiedRateLimiter } from '../delivery/classes.unified.rate.limiter.js' ;
import { SmtpState } from '../delivery/interfaces.js' ;
/ * *
* Unified email server that handles all email traffic with pattern - based routing
* /
export class UnifiedEmailServer extends EventEmitter {
dcRouter ;
options ;
emailRouter ;
domainRegistry ;
servers = [ ] ;
stats ;
// Add components needed for sending and securing emails
dkimCreator ;
2026-02-10 16:38:31 +00:00
rustBridge ;
ipReputationChecker ;
2026-02-10 15:54:09 +00:00
bounceManager ;
ipWarmupManager ;
senderReputationMonitor ;
deliveryQueue ;
deliverySystem ;
rateLimiter ; // TODO: Implement rate limiting in SMTP server handlers
dkimKeys = new Map ( ) ; // domain -> private key
smtpClients = new Map ( ) ; // host:port -> client
constructor ( dcRouter , options ) {
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
} ;
2026-02-10 16:38:31 +00:00
// Initialize Rust security bridge (singleton)
this . rustBridge = RustSecurityBridge . getInstance ( ) ;
2026-02-10 15:54:09 +00:00
// 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
} ) ;
// IP warmup manager and sender reputation monitor are optional
// They will be initialized when the deliverability module is available
this . ipWarmupManager = null ;
this . senderReputationMonitor = null ;
// 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 = {
storageType : 'memory' , // Default to memory storage
maxRetries : 3 ,
baseRetryDelay : 300000 , // 5 minutes
maxRetryDelay : 3600000 // 1 hour
} ;
this . deliveryQueue = new UnifiedDeliveryQueue ( queueOptions ) ;
const deliveryOptions = {
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 ;
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
* /
getSmtpClient ( host , port = 25 ) {
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
* /
async start ( ) {
logger . log ( 'info' , ` Starting UnifiedEmailServer on ports: ${ this . options . ports . 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' ) ;
2026-02-10 20:30:43 +00:00
// Start Rust security bridge — required for all security operations
2026-02-10 16:38:31 +00:00
const bridgeOk = await this . rustBridge . start ( ) ;
2026-02-10 20:30:43 +00:00
if ( ! bridgeOk ) {
throw new Error ( 'Rust security bridge failed to start. The mailer-bin binary is required. Run "pnpm build" to compile it.' ) ;
2026-02-10 16:38:31 +00:00
}
2026-02-10 20:30:43 +00:00
logger . log ( 'info' , 'Rust security bridge started — Rust is the primary security backend' ) ;
2026-02-10 15:54:09 +00:00
// 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' ) ;
// Ensure we have the necessary TLS options
const hasTlsConfig = this . options . tls ? . keyPath && this . options . tls ? . certPath ;
// Prepare the certificate and key if available
2026-02-10 22:00:44 +00:00
let tlsCertPem ;
let tlsKeyPem ;
2026-02-10 15:54:09 +00:00
if ( hasTlsConfig ) {
try {
2026-02-10 22:00:44 +00:00
tlsKeyPem = plugins . fs . readFileSync ( this . options . tls . keyPath , 'utf8' ) ;
tlsCertPem = plugins . fs . readFileSync ( this . options . tls . certPath , 'utf8' ) ;
2026-02-10 15:54:09 +00:00
logger . log ( 'info' , 'TLS certificates loaded successfully' ) ;
}
catch ( error ) {
logger . log ( 'warn' , ` Failed to load TLS certificates: ${ error . message } ` ) ;
}
}
2026-02-10 22:00:44 +00:00
// --- Start Rust SMTP server ---
// Register event handlers for email reception and auth
this . rustBridge . onEmailReceived ( async ( data ) => {
try {
await this . handleRustEmailReceived ( data ) ;
}
catch ( err ) {
logger . log ( 'error' , ` Error handling email from Rust SMTP: ${ err . message } ` ) ;
// Send rejection back to Rust
await this . rustBridge . sendEmailProcessingResult ( {
correlationId : data . correlationId ,
accepted : false ,
smtpCode : 451 ,
smtpMessage : 'Internal processing error' ,
} ) ;
}
} ) ;
this . rustBridge . onAuthRequest ( async ( data ) => {
try {
await this . handleRustAuthRequest ( data ) ;
}
catch ( err ) {
logger . log ( 'error' , ` Error handling auth from Rust SMTP: ${ err . message } ` ) ;
await this . rustBridge . sendAuthResult ( {
correlationId : data . correlationId ,
success : false ,
message : 'Internal auth error' ,
} ) ;
}
} ) ;
// Determine which ports need STARTTLS and which need implicit TLS
const smtpPorts = this . options . ports . filter ( p => p !== 465 ) ;
const securePort = this . options . ports . find ( p => p === 465 ) ;
const started = await this . rustBridge . startSmtpServer ( {
hostname : this . options . hostname ,
ports : smtpPorts ,
securePort : securePort ,
tlsCertPem ,
tlsKeyPem ,
maxMessageSize : this . options . maxMessageSize || 10 * 1024 * 1024 ,
maxConnections : this . options . maxConnections || this . options . maxClients || 100 ,
maxRecipients : 100 ,
connectionTimeoutSecs : this . options . connectionTimeout ? Math . floor ( this . options . connectionTimeout / 1000 ) : 30 ,
dataTimeoutSecs : 60 ,
authEnabled : ! ! this . options . auth ? . required || ! ! ( this . options . auth ? . users ? . length ) ,
maxAuthFailures : 3 ,
socketTimeoutSecs : this . options . socketTimeout ? Math . floor ( this . options . socketTimeout / 1000 ) : 300 ,
processingTimeoutSecs : 30 ,
rateLimits : this . options . rateLimits ? {
maxConnectionsPerIp : this . options . rateLimits . global ? . maxConnectionsPerIP || 50 ,
maxMessagesPerSender : this . options . rateLimits . global ? . maxMessagesPerMinute || 100 ,
maxAuthFailuresPerIp : this . options . rateLimits . global ? . maxAuthFailuresPerIP || 5 ,
windowSecs : 60 ,
} : undefined ,
} ) ;
if ( ! started ) {
throw new Error ( 'Failed to start Rust SMTP server' ) ;
2026-02-10 15:54:09 +00:00
}
2026-02-10 22:00:44 +00:00
logger . log ( 'info' , ` Rust SMTP server listening on ports: ${ smtpPorts . join ( ', ' ) } ${ securePort ? ` + ${ securePort } (TLS) ` : '' } ` ) ;
2026-02-10 15:54:09 +00:00
logger . log ( 'info' , 'UnifiedEmailServer started successfully' ) ;
this . emit ( 'started' ) ;
}
catch ( error ) {
logger . log ( 'error' , ` Failed to start UnifiedEmailServer: ${ error . message } ` ) ;
throw error ;
}
}
/ * *
* Stop the unified email server
* /
async stop ( ) {
logger . log ( 'info' , 'Stopping UnifiedEmailServer' ) ;
try {
2026-02-10 22:00:44 +00:00
// Stop the Rust SMTP server first
try {
await this . rustBridge . stopSmtpServer ( ) ;
logger . log ( 'info' , 'Rust SMTP server stopped' ) ;
}
catch ( err ) {
logger . log ( 'warn' , ` Error stopping Rust SMTP server: ${ err . message } ` ) ;
}
2026-02-10 15:54:09 +00:00
// Clear the servers array - servers will be garbage collected
this . servers = [ ] ;
2026-02-10 16:38:31 +00:00
// Stop Rust security bridge
await this . rustBridge . stop ( ) ;
2026-02-10 15:54:09 +00:00
// 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 ;
}
}
2026-02-10 22:00:44 +00:00
// -----------------------------------------------------------------------
// Rust SMTP server event handlers
// -----------------------------------------------------------------------
/ * *
* Handle an emailReceived event from the Rust SMTP server .
* Decodes the email data , processes it through the routing system ,
* and sends back the result via the correlation - ID callback .
* /
async handleRustEmailReceived ( data ) {
const { correlationId , mailFrom , rcptTo , remoteAddr , clientHostname , secure , authenticatedUser } = data ;
logger . log ( 'info' , ` Rust SMTP received email from= ${ mailFrom } to= ${ rcptTo . join ( ',' ) } remote= ${ remoteAddr } ` ) ;
try {
// Decode the email data
let rawMessageBuffer ;
if ( data . data . type === 'inline' && data . data . base64 ) {
rawMessageBuffer = Buffer . from ( data . data . base64 , 'base64' ) ;
}
else if ( data . data . type === 'file' && data . data . path ) {
rawMessageBuffer = plugins . fs . readFileSync ( data . data . path ) ;
// Clean up temp file
try {
plugins . fs . unlinkSync ( data . data . path ) ;
}
catch {
// Ignore cleanup errors
}
}
else {
throw new Error ( 'Invalid email data transport' ) ;
}
// Build a session-like object for processEmailByMode
const session = {
id : data . sessionId || 'rust-' + Math . random ( ) . toString ( 36 ) . substring ( 2 ) ,
state : SmtpState . FINISHED ,
mailFrom : mailFrom ,
rcptTo : rcptTo ,
emailData : rawMessageBuffer . toString ( 'utf8' ) ,
useTLS : secure ,
connectionEnded : false ,
remoteAddress : remoteAddr ,
clientHostname : clientHostname || '' ,
secure : secure ,
authenticated : ! ! authenticatedUser ,
envelope : {
mailFrom : { address : mailFrom , args : { } } ,
rcptTo : rcptTo . map ( addr => ( { address : addr , args : { } } ) ) ,
} ,
} ;
if ( authenticatedUser ) {
session . user = { username : authenticatedUser } ;
}
2026-02-10 22:26:20 +00:00
// Attach pre-computed security results from Rust in-process pipeline
if ( data . securityResults ) {
session . _precomputedSecurityResults = data . securityResults ;
}
2026-02-10 22:00:44 +00:00
// Process the email through the routing system
await this . processEmailByMode ( rawMessageBuffer , session ) ;
// Send acceptance back to Rust
await this . rustBridge . sendEmailProcessingResult ( {
correlationId ,
accepted : true ,
smtpCode : 250 ,
smtpMessage : '2.0.0 Message accepted for delivery' ,
} ) ;
}
catch ( err ) {
logger . log ( 'error' , ` Failed to process email from Rust SMTP: ${ err . message } ` ) ;
await this . rustBridge . sendEmailProcessingResult ( {
correlationId ,
accepted : false ,
smtpCode : 550 ,
smtpMessage : ` 5.0.0 Processing failed: ${ err . message } ` ,
} ) ;
}
}
/ * *
* Handle an authRequest event from the Rust SMTP server .
* Validates credentials and sends back the result .
* /
async handleRustAuthRequest ( data ) {
const { correlationId , username , password , remoteAddr } = data ;
logger . log ( 'info' , ` Rust SMTP auth request for user= ${ username } from= ${ remoteAddr } ` ) ;
// Check against configured users
const users = this . options . auth ? . users || [ ] ;
const matched = users . find ( u => u . username === username && u . password === password ) ;
if ( matched ) {
await this . rustBridge . sendAuthResult ( {
correlationId ,
success : true ,
} ) ;
}
else {
logger . log ( 'warn' , ` Auth failed for user= ${ username } from= ${ remoteAddr } ` ) ;
await this . rustBridge . sendAuthResult ( {
correlationId ,
success : false ,
message : 'Invalid credentials' ,
} ) ;
}
}
2026-02-10 16:38:31 +00:00
/ * *
2026-02-10 22:26:20 +00:00
* Verify inbound email security ( DKIM / SPF / DMARC ) using pre - computed Rust results
* or falling back to IPC call if no pre - computed results are available .
2026-02-10 16:38:31 +00:00
* /
async verifyInboundSecurity ( email , session ) {
try {
2026-02-10 22:26:20 +00:00
// Check for pre-computed results from Rust in-process security pipeline
const precomputed = session . _precomputedSecurityResults ;
let result ;
if ( precomputed ) {
logger . log ( 'info' , 'Using pre-computed security results from Rust in-process pipeline' ) ;
result = precomputed ;
}
else {
// Fallback: IPC round-trip to Rust (for backward compat / handleSocket mode)
const rawMessage = session . emailData || email . toRFC822String ( ) ;
result = await this . rustBridge . verifyEmail ( {
rawMessage ,
ip : session . remoteAddress ,
heloDomain : session . clientHostname || '' ,
hostname : this . options . hostname ,
mailFrom : session . envelope ? . mailFrom ? . address || session . mailFrom || '' ,
} ) ;
}
2026-02-10 16:38:31 +00:00
// Apply DKIM result headers
if ( result . dkim && result . dkim . length > 0 ) {
const dkimSummary = result . dkim
2026-02-10 22:26:20 +00:00
. map ( ( d ) => ` ${ d . status } ${ d . domain ? ` ( ${ d . domain } ) ` : '' } ` )
2026-02-10 16:38:31 +00:00
. join ( ', ' ) ;
email . addHeader ( 'X-DKIM-Result' , dkimSummary ) ;
}
// Apply SPF result header
if ( result . spf ) {
email . addHeader ( 'Received-SPF' , ` ${ result . spf . result } (domain: ${ result . spf . domain } , ip: ${ result . spf . ip } ) ` ) ;
// Mark as spam on SPF hard fail
if ( result . spf . result === 'fail' ) {
email . mightBeSpam = true ;
logger . log ( 'warn' , ` SPF fail for ${ session . remoteAddress } — marking as potential spam ` ) ;
}
}
// Apply DMARC result header and policy
if ( result . dmarc ) {
email . addHeader ( 'X-DMARC-Result' , ` ${ result . dmarc . action } (policy= ${ result . dmarc . policy } , dkim= ${ result . dmarc . dkim _result } , spf= ${ result . dmarc . spf _result } ) ` ) ;
if ( result . dmarc . action === 'reject' ) {
email . mightBeSpam = true ;
logger . log ( 'warn' , ` DMARC reject for domain ${ result . dmarc . domain } — marking as spam ` ) ;
}
else if ( result . dmarc . action === 'quarantine' ) {
email . mightBeSpam = true ;
logger . log ( 'info' , ` DMARC quarantine for domain ${ result . dmarc . domain } — marking as potential spam ` ) ;
}
}
2026-02-10 22:26:20 +00:00
// Apply content scan results (from pre-computed pipeline)
if ( result . contentScan ) {
const scan = result . contentScan ;
if ( scan . threatScore > 0 ) {
email . addHeader ( 'X-Spam-Score' , String ( scan . threatScore ) ) ;
if ( scan . threatType ) {
email . addHeader ( 'X-Spam-Type' , scan . threatType ) ;
}
if ( scan . threatScore >= 50 ) {
email . mightBeSpam = true ;
logger . log ( 'warn' , ` Content scan threat score ${ scan . threatScore } ( ${ scan . threatType } ) — marking as potential spam ` ) ;
}
}
}
// Apply IP reputation results (from pre-computed pipeline)
if ( result . ipReputation ) {
const rep = result . ipReputation ;
email . addHeader ( 'X-IP-Reputation-Score' , String ( rep . score ) ) ;
if ( rep . is _spam ) {
email . mightBeSpam = true ;
logger . log ( 'warn' , ` IP ${ rep . ip } flagged by reputation check (score= ${ rep . score } ) — marking as potential spam ` ) ;
}
}
2026-02-10 16:38:31 +00:00
logger . log ( 'info' , ` Inbound security verified for email from ${ session . remoteAddress } : DKIM= ${ result . dkim ? . [ 0 ] ? . status ? ? 'none' } , SPF= ${ result . spf ? . result ? ? 'none' } , DMARC= ${ result . dmarc ? . action ? ? 'none' } ` ) ;
}
catch ( err ) {
logger . log ( 'warn' , ` Inbound security verification failed: ${ err . message } — accepting email ` ) ;
}
}
2026-02-10 15:54:09 +00:00
/ * *
* Process email based on routing rules
* /
async processEmailByMode ( emailData , session ) {
// Convert Buffer to Email if needed
let 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 ;
}
2026-02-10 16:38:31 +00:00
// Run inbound security verification (DKIM/SPF/DMARC) via Rust bridge
if ( session . remoteAddress && session . remoteAddress !== '127.0.0.1' ) {
await this . verifyInboundSecurity ( email , session ) ;
}
2026-02-10 15:54:09 +00:00
// 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 = { 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
* /
async executeAction ( action , email , context ) {
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 . type } ` ) ;
}
}
/ * *
* Handle forward action
* /
async handleForwardAction ( _action , email , context ) {
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' ]
} ) ;
}
throw error ;
}
}
/ * *
* Handle process action
* /
async handleProcessAction ( action , email , context ) {
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
* /
async handleDeliverAction ( _action , email , context ) {
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
* /
async handleRejectAction ( action , email , context ) {
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 . responseCode = code ;
throw error ;
}
/ * *
* Handle email in MTA mode ( programmatic processing )
* /
async _handleMtaMode ( email , session ) {
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 ) {
2026-02-10 20:30:43 +00:00
const dkimDomain = options . dkimOptions . domainName ;
const dkimSelector = options . dkimOptions . keySelector || 'mta' ;
logger . log ( 'info' , ` Signing email with DKIM for domain ${ dkimDomain } ` ) ;
await this . handleDkimSigning ( email , dkimDomain , dkimSelector ) ;
2026-02-10 15:54:09 +00:00
}
}
// 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 )
* /
async _handleProcessMode ( email , session ) {
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
* /
getFileExtension ( filename ) {
return filename . substring ( filename . lastIndexOf ( '.' ) ) . toLowerCase ( ) ;
}
/ * *
* Set up DKIM configuration for all domains
* /
async setupDkimForDomains ( ) {
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 ;
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
* /
applyDomainRateLimits ( ) {
const domainConfigs = this . domainRegistry . getAllConfigs ( ) ;
for ( const domainConfig of domainConfigs ) {
if ( domainConfig . rateLimits ) {
const domain = domainConfig . domain ;
const rateLimitConfig = { } ;
// 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
* /
async checkAndRotateDkimKeys ( ) {
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
* /
generateProxyRoutes ( portMapping ) {
const routes = [ ] ;
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
* /
updateOptions ( options ) {
// 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
* /
updateEmailRoutes ( routes ) {
this . options . routes = routes ;
this . emailRouter . updateRoutes ( routes ) ;
}
/ * *
* Get server statistics
* /
getStats ( ) {
return { ... this . stats } ;
}
/ * *
* Get domain registry
* /
getDomainRegistry ( ) {
return this . domainRegistry ;
}
/ * *
* Update email routes dynamically
* /
updateRoutes ( routes ) {
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
* /
async sendEmail ( email , mode = 'mta' , route , options ) {
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
* /
async handleDkimSigning ( email , domain , selector ) {
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 ( ) ;
2026-02-10 20:30:43 +00:00
// Sign the email via Rust bridge
const signResult = await this . rustBridge . signDkim ( {
rawMessage : rawEmail ,
domain ,
selector ,
privateKey ,
2026-02-10 15:54:09 +00:00
} ) ;
2026-02-10 20:30:43 +00:00
if ( signResult . header ) {
email . addHeader ( 'DKIM-Signature' , signResult . header ) ;
2026-02-10 15:54:09 +00:00
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
* /
async processBounceNotification ( bounceEmail ) {
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
* /
async processSmtpFailure ( recipient , smtpResponse , options = { } ) {
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
* /
isEmailSuppressed ( email ) {
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
* /
getSuppressionInfo ( email ) {
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
* /
getBounceHistory ( email ) {
return this . bounceManager . getBounceInfo ( email ) ;
}
/ * *
* Get all suppressed email addresses
* @ returns Array of suppressed email addresses
* /
getSuppressionList ( ) {
return this . bounceManager . getSuppressionList ( ) ;
}
/ * *
* Get all hard bounced email addresses
* @ returns Array of hard bounced email addresses
* /
getHardBouncedAddresses ( ) {
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 )
* /
addToSuppressionList ( email , reason , expiresAt ) {
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
* /
removeFromSuppressionList ( email ) {
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
* /
getIPWarmupStatus ( ipAddress ) {
return this . ipWarmupManager . getWarmupStatus ( ipAddress ) ;
}
/ * *
* Add a new IP address to the warmup process
* @ param ipAddress IP address to add
* /
addIPToWarmup ( ipAddress ) {
this . ipWarmupManager . addIPToWarmup ( ipAddress ) ;
}
/ * *
* Remove an IP address from the warmup process
* @ param ipAddress IP address to remove
* /
removeIPFromWarmup ( ipAddress ) {
this . ipWarmupManager . removeIPFromWarmup ( ipAddress ) ;
}
/ * *
* Update metrics for an IP in the warmup process
* @ param ipAddress IP address
* @ param metrics Metrics to update
* /
updateIPWarmupMetrics ( ipAddress , metrics ) {
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
* /
canIPSendMoreToday ( ipAddress ) {
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
* /
canIPSendMoreThisHour ( ipAddress ) {
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
* /
getBestIPForSending ( emailInfo ) {
return this . ipWarmupManager . getBestIPForSending ( emailInfo ) ;
}
/ * *
* Set the active IP allocation policy for warmup
* @ param policyName Name of the policy to set
* /
setIPAllocationPolicy ( policyName ) {
this . ipWarmupManager . setActiveAllocationPolicy ( policyName ) ;
}
/ * *
* Record that an email was sent using a specific IP
* @ param ipAddress IP address used for sending
* /
recordIPSend ( ipAddress ) {
this . ipWarmupManager . recordSend ( ipAddress ) ;
}
/ * *
* Get reputation data for a domain
* @ param domain Domain to get reputation for
* @ returns Domain reputation metrics
* /
getDomainReputationData ( domain ) {
return this . senderReputationMonitor . getReputationData ( domain ) ;
}
/ * *
* Get summary reputation data for all monitored domains
* @ returns Summary data for all domains
* /
getReputationSummary ( ) {
return this . senderReputationMonitor . getReputationSummary ( ) ;
}
/ * *
* Add a domain to the reputation monitoring system
* @ param domain Domain to add
* /
addDomainToMonitoring ( domain ) {
this . senderReputationMonitor . addDomain ( domain ) ;
}
/ * *
* Remove a domain from the reputation monitoring system
* @ param domain Domain to remove
* /
removeDomainFromMonitoring ( domain ) {
this . senderReputationMonitor . removeDomain ( domain ) ;
}
/ * *
* Record an email event for domain reputation tracking
* @ param domain Domain sending the email
* @ param event Event details
* /
recordReputationEvent ( domain , event ) {
this . senderReputationMonitor . recordSendEvent ( domain , event ) ;
}
/ * *
* Check if DKIM key exists for a domain
* @ param domain Domain to check
* /
hasDkimKey ( domain ) {
return this . dkimKeys . has ( domain ) ;
}
/ * *
* Record successful email delivery
* @ param domain Sending domain
* /
recordDelivery ( domain ) {
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
* /
recordBounce ( domain , receivingDomain , bounceType , reason ) {
// 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
* /
getRateLimiter ( ) {
return this . rateLimiter ;
}
}
2026-02-10 22:26:20 +00:00
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY2xhc3Nlcy51bmlmaWVkLmVtYWlsLnNlcnZlci5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uL3RzL21haWwvcm91dGluZy9jbGFzc2VzLnVuaWZpZWQuZW1haWwuc2VydmVyLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLE9BQU8sS0FBSyxPQUFPLE1BQU0sa0JBQWtCLENBQUM7QUFDNUMsT0FBTyxLQUFLLEtBQUssTUFBTSxnQkFBZ0IsQ0FBQztBQUN4QyxPQUFPLEVBQUUsWUFBWSxFQUFFLE1BQU0sUUFBUSxDQUFDO0FBQ3RDLE9BQU8sRUFBRSxNQUFNLEVBQUUsTUFBTSxpQkFBaUIsQ0FBQztBQUN6QyxPQUFPLEVBQ0wsY0FBYyxFQUNkLGdCQUFnQixFQUNoQixpQkFBaUIsRUFDbEIsTUFBTSx5QkFBeUIsQ0FBQztBQUNqQyxPQUFPLEVBQUUsV0FBVyxFQUFFLE1BQU0sb0NBQW9DLENBQUM7QUFDakUsT0FBTyxFQUFFLG1CQUFtQixFQUFFLE1BQU0sK0NBQStDLENBQUM7QUFDcEYsT0FBTyxFQUFFLGtCQUFrQixFQUFFLE1BQU0sOENBQThDLENBQUM7QUErQmxGLE9BQU8sRUFBRSxXQUFXLEVBQUUsTUFBTSwyQkFBMkIsQ0FBQztBQUV4RCxPQUFPLEVBQUUsS0FBSyxFQUFFLE1BQU0sMEJBQTBCLENBQUM7QUFDakQsT0FBTyxFQUFFLGNBQWMsRUFBRSxNQUFNLDhCQUE4QixDQUFDO0FBQzlELE9BQU8sRUFBRSxVQUFVLEVBQUUsTUFBTSwwQkFBMEIsQ0FBQztBQUN0RCxPQUFPLEVBQUUsYUFBYSxFQUFFLFVBQVUsRUFBRSxjQUFjLEVBQUUsTUFBTSxrQ0FBa0MsQ0FBQztBQUM3RixPQUFPLEVBQUUsc0JBQXNCLEVBQUUsTUFBTSx5Q0FBeUMsQ0FBQztBQUVqRixPQUFPLEVBQUUsdUJBQXVCLEVBQWtDLE1BQU0sd0NBQXdDLENBQUM7QUFDakgsT0FBTyxFQUFFLG9CQUFvQixFQUFzQixNQUFNLHVDQUF1QyxDQUFDO0FB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