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 { createSmtpServer } from '../delivery/smtpserver/index.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' ) ;
// 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 ;
let cert ;
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 ) {
// 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
}
} ,
2026-02-10 20:30:43 +00:00
// Security verification delegated to the Rust bridge
2026-02-10 15:54:09 +00:00
dkimVerifier : {
2026-02-10 16:38:31 +00:00
verify : async ( rawMessage ) => {
2026-02-10 20:30:43 +00:00
try {
const results = await this . rustBridge . verifyDkim ( rawMessage ) ;
const first = results [ 0 ] ;
return { isValid : first ? . is _valid ? ? false , domain : first ? . domain ? ? '' } ;
}
catch ( err ) {
logger . log ( 'warn' , ` Rust DKIM verification failed: ${ err . message } ` ) ;
return { isValid : false , domain : '' } ;
2026-02-10 16:38:31 +00:00
}
}
2026-02-10 15:54:09 +00:00
} ,
spfVerifier : {
2026-02-10 16:38:31 +00:00
verifyAndApply : async ( session ) => {
2026-02-10 20:30:43 +00:00
if ( ! session ? . remoteAddress || session . remoteAddress === '127.0.0.1' ) {
return true ; // localhost — skip SPF
}
try {
const result = await this . rustBridge . checkSpf ( {
ip : session . remoteAddress ,
heloDomain : session . clientHostname || '' ,
hostname : this . options . hostname ,
mailFrom : session . envelope ? . mailFrom ? . address || session . mailFrom || '' ,
} ) ;
return result . result === 'pass' || result . result === 'none' || result . result === 'neutral' ;
}
catch ( err ) {
logger . log ( 'warn' , ` Rust SPF check failed: ${ err . message } ` ) ;
return true ; // Accept on error to avoid blocking mail
2026-02-10 16:38:31 +00:00
}
}
2026-02-10 15:54:09 +00:00
} ,
dmarcVerifier : {
verify : async ( ) => ( { } ) ,
applyPolicy : ( ) => true
} ,
processIncomingEmail : async ( 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 , serverOptions ) ;
this . servers . push ( smtpServer ) ;
// Start the server
await new Promise ( ( 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 . 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 )
* /
async handleSocket ( socket , port ) {
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 . 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
* /
async stop ( ) {
logger . log ( 'info' , 'Stopping UnifiedEmailServer' ) ;
try {
// 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 16:38:31 +00:00
/ * *
* Verify inbound email security ( DKIM / SPF / DMARC ) using the Rust bridge .
* Falls back gracefully if the bridge is not running .
* /
async verifyInboundSecurity ( email , session ) {
try {
const rawMessage = session . emailData || email . toRFC822String ( ) ;
const result = await this . rustBridge . verifyEmail ( {
rawMessage ,
ip : session . remoteAddress ,
heloDomain : session . clientHostname || '' ,
hostname : this . options . hostname ,
mailFrom : session . envelope ? . mailFrom ? . address || session . mailFrom || '' ,
} ) ;
// Apply DKIM result headers
if ( result . dkim && result . dkim . length > 0 ) {
const dkimSummary = result . dkim
. map ( d => ` ${ d . status } ${ d . domain ? ` ( ${ d . domain } ) ` : '' } ` )
. 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 ` ) ;
}
}
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 20:30:43 +00:00
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY2xhc3Nlcy51bmlmaWVkLmVtYWlsLnNlcnZlci5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uL3RzL21haWwvcm91dGluZy9jbGFzc2VzLnVuaWZpZWQuZW1haWwuc2VydmVyLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLE9BQU8sS0FBSyxPQUFPLE1BQU0sa0JBQWtCLENBQUM7QUFDNUMsT0FBTyxLQUFLLEtBQUssTUFBTSxnQkFBZ0IsQ0FBQztBQUN4QyxPQUFPLEVBQUUsWUFBWSxFQUFFLE1BQU0sUUFBUSxDQUFDO0FBQ3RDLE9BQU8sRUFBRSxNQUFNLEVBQUUsTUFBTSxpQkFBaUIsQ0FBQztBQUN6QyxPQUFPLEVBQ0wsY0FBYyxFQUNkLGdCQUFnQixFQUNoQixpQkFBaUIsRUFDbEIsTUFBTSx5QkFBeUIsQ0FBQztBQUNqQyxPQUFPLEVBQUUsV0FBVyxFQUFFLE1BQU0sb0NBQW9DLENBQUM7QUFDakUsT0FBTyxFQUFFLG1CQUFtQixFQUFFLE1BQU0sK0NBQStDLENBQUM7QUFDcEYsT0FBTyxFQUFFLGtCQUFrQixFQUFFLE1BQU0sOENBQThDLENBQUM7QUE4QmxGLE9BQU8sRUFBRSxXQUFXLEVBQUUsTUFBTSwyQkFBMkIsQ0FBQztBQUV4RCxPQUFPLEVBQUUsS0FBSyxFQUFFLE1BQU0sMEJBQTBCLENBQUM7QUFDakQsT0FBTyxFQUFFLGNBQWMsRUFBRSxNQUFNLDhCQUE4QixDQUFDO0FBQzlELE9BQU8sRUFBRSxVQUFVLEVBQUUsTUFBTSwwQkFBMEIsQ0FBQztBQUN0RCxPQUFPLEVBQUUsYUFBYSxFQUFFLFVBQVUsRUFBRSxjQUFjLEVBQUUsTUFBTSxrQ0FBa0MsQ0FBQztBQUM3RixPQUFPLEVBQUUsZ0JBQWdCLEVBQUUsTUFBTSxpQ0FBaUMsQ0FBQztBQUNuRSxPQUFPLEVBQUUsc0JBQXNCLEVBQUUsTUFBTSx5Q0FBeUMsQ0FBQztBQUVqRixPQUFPLEVBQUUsdUJBQXVCLEVBQWtDLE1BQU0sd0NBQXdDLENBQUM7QUFD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