2026-02-10 15:54:09 +00:00
|
|
|
import * as plugins from '../../plugins.js';
|
|
|
|
|
import { EventEmitter } from 'events';
|
|
|
|
|
import { DKIMCreator } from '../security/classes.dkimcreator.js';
|
|
|
|
|
interface IIPWarmupConfig {
|
|
|
|
|
enabled?: boolean;
|
|
|
|
|
ips?: string[];
|
|
|
|
|
[key: string]: any;
|
|
|
|
|
}
|
|
|
|
|
interface IReputationMonitorConfig {
|
|
|
|
|
enabled?: boolean;
|
|
|
|
|
domains?: string[];
|
|
|
|
|
[key: string]: any;
|
|
|
|
|
}
|
|
|
|
|
import type { IEmailRoute, IEmailDomainConfig } from './interfaces.js';
|
|
|
|
|
import { Email } from '../core/classes.email.js';
|
|
|
|
|
import { DomainRegistry } from './classes.domain.registry.js';
|
|
|
|
|
import { BounceType, BounceCategory } from '../core/classes.bouncemanager.js';
|
|
|
|
|
import type { SmtpClient } from '../delivery/smtpclient/smtp-client.js';
|
|
|
|
|
import { MultiModeDeliverySystem } from '../delivery/classes.delivery.system.js';
|
|
|
|
|
import { UnifiedDeliveryQueue } from '../delivery/classes.delivery.queue.js';
|
|
|
|
|
import { UnifiedRateLimiter, type IHierarchicalRateLimits } from '../delivery/classes.unified.rate.limiter.js';
|
|
|
|
|
import type { EmailProcessingMode, ISmtpSession as IBaseSmtpSession } from '../delivery/interfaces.js';
|
|
|
|
|
/** External DcRouter interface shape used by UnifiedEmailServer */
|
|
|
|
|
interface DcRouter {
|
|
|
|
|
storageManager: any;
|
|
|
|
|
dnsServer?: any;
|
|
|
|
|
options?: any;
|
|
|
|
|
}
|
|
|
|
|
/**
|
|
|
|
|
* Extended SMTP session interface with route information
|
|
|
|
|
*/
|
|
|
|
|
export interface IExtendedSmtpSession extends ISmtpSession {
|
|
|
|
|
/**
|
|
|
|
|
* Matched route for this session
|
|
|
|
|
*/
|
|
|
|
|
matchedRoute?: IEmailRoute;
|
|
|
|
|
}
|
|
|
|
|
/**
|
|
|
|
|
* Options for the unified email server
|
|
|
|
|
*/
|
|
|
|
|
export interface IUnifiedEmailServerOptions {
|
|
|
|
|
ports: number[];
|
|
|
|
|
hostname: string;
|
|
|
|
|
domains: IEmailDomainConfig[];
|
|
|
|
|
banner?: string;
|
|
|
|
|
debug?: boolean;
|
|
|
|
|
useSocketHandler?: boolean;
|
|
|
|
|
auth?: {
|
|
|
|
|
required?: boolean;
|
|
|
|
|
methods?: ('PLAIN' | 'LOGIN' | 'OAUTH2')[];
|
|
|
|
|
users?: Array<{
|
|
|
|
|
username: string;
|
|
|
|
|
password: string;
|
|
|
|
|
}>;
|
|
|
|
|
};
|
|
|
|
|
tls?: {
|
|
|
|
|
certPath?: string;
|
|
|
|
|
keyPath?: string;
|
|
|
|
|
caPath?: string;
|
|
|
|
|
minVersion?: string;
|
|
|
|
|
ciphers?: string;
|
|
|
|
|
};
|
|
|
|
|
maxMessageSize?: number;
|
|
|
|
|
maxClients?: number;
|
|
|
|
|
maxConnections?: number;
|
|
|
|
|
connectionTimeout?: number;
|
|
|
|
|
socketTimeout?: number;
|
|
|
|
|
routes: IEmailRoute[];
|
|
|
|
|
defaults?: {
|
|
|
|
|
dnsMode?: 'forward' | 'internal-dns' | 'external-dns';
|
|
|
|
|
dkim?: IEmailDomainConfig['dkim'];
|
|
|
|
|
rateLimits?: IEmailDomainConfig['rateLimits'];
|
|
|
|
|
};
|
|
|
|
|
outbound?: {
|
|
|
|
|
maxConnections?: number;
|
|
|
|
|
connectionTimeout?: number;
|
|
|
|
|
socketTimeout?: number;
|
|
|
|
|
retryAttempts?: number;
|
|
|
|
|
defaultFrom?: string;
|
|
|
|
|
};
|
|
|
|
|
rateLimits?: IHierarchicalRateLimits;
|
|
|
|
|
ipWarmupConfig?: IIPWarmupConfig;
|
|
|
|
|
reputationMonitorConfig?: IReputationMonitorConfig;
|
|
|
|
|
}
|
|
|
|
|
/**
|
|
|
|
|
* Extended SMTP session interface for UnifiedEmailServer
|
|
|
|
|
*/
|
|
|
|
|
export interface ISmtpSession extends IBaseSmtpSession {
|
|
|
|
|
/**
|
|
|
|
|
* User information if authenticated
|
|
|
|
|
*/
|
|
|
|
|
user?: {
|
|
|
|
|
username: string;
|
|
|
|
|
[key: string]: any;
|
|
|
|
|
};
|
|
|
|
|
/**
|
|
|
|
|
* Matched route for this session
|
|
|
|
|
*/
|
|
|
|
|
matchedRoute?: IEmailRoute;
|
|
|
|
|
}
|
|
|
|
|
/**
|
|
|
|
|
* Authentication data for SMTP
|
|
|
|
|
*/
|
|
|
|
|
import type { ISmtpAuth } from '../delivery/interfaces.js';
|
|
|
|
|
export type IAuthData = ISmtpAuth;
|
|
|
|
|
/**
|
|
|
|
|
* Server statistics
|
|
|
|
|
*/
|
|
|
|
|
export interface IServerStats {
|
|
|
|
|
startTime: Date;
|
|
|
|
|
connections: {
|
|
|
|
|
current: number;
|
|
|
|
|
total: number;
|
|
|
|
|
};
|
|
|
|
|
messages: {
|
|
|
|
|
processed: number;
|
|
|
|
|
delivered: number;
|
|
|
|
|
failed: number;
|
|
|
|
|
};
|
|
|
|
|
processingTime: {
|
|
|
|
|
avg: number;
|
|
|
|
|
max: number;
|
|
|
|
|
min: number;
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
/**
|
|
|
|
|
* Unified email server that handles all email traffic with pattern-based routing
|
|
|
|
|
*/
|
|
|
|
|
export declare class UnifiedEmailServer extends EventEmitter {
|
|
|
|
|
private dcRouter;
|
|
|
|
|
private options;
|
|
|
|
|
private emailRouter;
|
|
|
|
|
domainRegistry: DomainRegistry;
|
|
|
|
|
private servers;
|
|
|
|
|
private stats;
|
|
|
|
|
dkimCreator: DKIMCreator;
|
2026-02-10 16:38:31 +00:00
|
|
|
private rustBridge;
|
2026-02-10 15:54:09 +00:00
|
|
|
private ipReputationChecker;
|
|
|
|
|
private bounceManager;
|
|
|
|
|
private ipWarmupManager;
|
|
|
|
|
private senderReputationMonitor;
|
|
|
|
|
deliveryQueue: UnifiedDeliveryQueue;
|
|
|
|
|
deliverySystem: MultiModeDeliverySystem;
|
|
|
|
|
private rateLimiter;
|
|
|
|
|
private dkimKeys;
|
|
|
|
|
private smtpClients;
|
|
|
|
|
constructor(dcRouter: DcRouter, options: IUnifiedEmailServerOptions);
|
|
|
|
|
/**
|
|
|
|
|
* Get or create an SMTP client for the given host and port
|
|
|
|
|
* Uses connection pooling for efficiency
|
|
|
|
|
*/
|
|
|
|
|
getSmtpClient(host: string, port?: number): SmtpClient;
|
|
|
|
|
/**
|
|
|
|
|
* Start the unified email server
|
|
|
|
|
*/
|
|
|
|
|
start(): Promise<void>;
|
|
|
|
|
/**
|
|
|
|
|
* 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)
|
|
|
|
|
*/
|
|
|
|
|
handleSocket(socket: plugins.net.Socket | plugins.tls.TLSSocket, port: number): Promise<void>;
|
|
|
|
|
/**
|
|
|
|
|
* Stop the unified email server
|
|
|
|
|
*/
|
|
|
|
|
stop(): Promise<void>;
|
2026-02-10 22:00:44 +00:00
|
|
|
/**
|
|
|
|
|
* 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.
|
|
|
|
|
*/
|
|
|
|
|
private handleRustEmailReceived;
|
|
|
|
|
/**
|
|
|
|
|
* Handle an authRequest event from the Rust SMTP server.
|
|
|
|
|
* Validates credentials and sends back the result.
|
|
|
|
|
*/
|
|
|
|
|
private handleRustAuthRequest;
|
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.
|
|
|
|
|
*/
|
|
|
|
|
private verifyInboundSecurity;
|
2026-02-10 15:54:09 +00:00
|
|
|
/**
|
|
|
|
|
* Process email based on routing rules
|
|
|
|
|
*/
|
|
|
|
|
processEmailByMode(emailData: Email | Buffer, session: IExtendedSmtpSession): Promise<Email>;
|
|
|
|
|
/**
|
|
|
|
|
* Execute action based on route configuration
|
|
|
|
|
*/
|
|
|
|
|
private executeAction;
|
|
|
|
|
/**
|
|
|
|
|
* Handle forward action
|
|
|
|
|
*/
|
|
|
|
|
private handleForwardAction;
|
|
|
|
|
/**
|
|
|
|
|
* Handle process action
|
|
|
|
|
*/
|
|
|
|
|
private handleProcessAction;
|
|
|
|
|
/**
|
|
|
|
|
* Handle deliver action
|
|
|
|
|
*/
|
|
|
|
|
private handleDeliverAction;
|
|
|
|
|
/**
|
|
|
|
|
* Handle reject action
|
|
|
|
|
*/
|
|
|
|
|
private handleRejectAction;
|
|
|
|
|
/**
|
|
|
|
|
* Handle email in MTA mode (programmatic processing)
|
|
|
|
|
*/
|
|
|
|
|
private _handleMtaMode;
|
|
|
|
|
/**
|
|
|
|
|
* Handle email in process mode (store-and-forward with scanning)
|
|
|
|
|
*/
|
|
|
|
|
private _handleProcessMode;
|
|
|
|
|
/**
|
|
|
|
|
* Get file extension from filename
|
|
|
|
|
*/
|
|
|
|
|
private getFileExtension;
|
|
|
|
|
/**
|
|
|
|
|
* Set up DKIM configuration for all domains
|
|
|
|
|
*/
|
|
|
|
|
private setupDkimForDomains;
|
|
|
|
|
/**
|
|
|
|
|
* Apply per-domain rate limits from domain configurations
|
|
|
|
|
*/
|
|
|
|
|
private applyDomainRateLimits;
|
|
|
|
|
/**
|
|
|
|
|
* Check and rotate DKIM keys if needed
|
|
|
|
|
*/
|
|
|
|
|
private checkAndRotateDkimKeys;
|
|
|
|
|
/**
|
|
|
|
|
* Generate SmartProxy routes for email ports
|
|
|
|
|
*/
|
|
|
|
|
generateProxyRoutes(portMapping?: Record<number, number>): any[];
|
|
|
|
|
/**
|
|
|
|
|
* Update server configuration
|
|
|
|
|
*/
|
|
|
|
|
updateOptions(options: Partial<IUnifiedEmailServerOptions>): void;
|
|
|
|
|
/**
|
|
|
|
|
* Update email routes
|
|
|
|
|
*/
|
|
|
|
|
updateEmailRoutes(routes: IEmailRoute[]): void;
|
|
|
|
|
/**
|
|
|
|
|
* Get server statistics
|
|
|
|
|
*/
|
|
|
|
|
getStats(): IServerStats;
|
|
|
|
|
/**
|
|
|
|
|
* Get domain registry
|
|
|
|
|
*/
|
|
|
|
|
getDomainRegistry(): DomainRegistry;
|
|
|
|
|
/**
|
|
|
|
|
* Update email routes dynamically
|
|
|
|
|
*/
|
|
|
|
|
updateRoutes(routes: IEmailRoute[]): void;
|
|
|
|
|
/**
|
|
|
|
|
* 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
|
|
|
|
|
*/
|
|
|
|
|
sendEmail(email: Email, mode?: EmailProcessingMode, route?: IEmailRoute, options?: {
|
|
|
|
|
skipSuppressionCheck?: boolean;
|
|
|
|
|
ipAddress?: string;
|
|
|
|
|
isTransactional?: boolean;
|
|
|
|
|
}): Promise<string>;
|
|
|
|
|
/**
|
|
|
|
|
* Handle DKIM signing for an email
|
|
|
|
|
* @param email The email to sign
|
|
|
|
|
* @param domain The domain to sign with
|
|
|
|
|
* @param selector The DKIM selector
|
|
|
|
|
*/
|
|
|
|
|
private handleDkimSigning;
|
|
|
|
|
/**
|
|
|
|
|
* Process a bounce notification email
|
|
|
|
|
* @param bounceEmail The email containing bounce notification information
|
|
|
|
|
* @returns Processed bounce record or null if not a bounce
|
|
|
|
|
*/
|
|
|
|
|
processBounceNotification(bounceEmail: Email): Promise<boolean>;
|
|
|
|
|
/**
|
|
|
|
|
* 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
|
|
|
|
|
*/
|
|
|
|
|
processSmtpFailure(recipient: string, smtpResponse: string, options?: {
|
|
|
|
|
sender?: string;
|
|
|
|
|
originalEmailId?: string;
|
|
|
|
|
statusCode?: string;
|
|
|
|
|
headers?: Record<string, string>;
|
|
|
|
|
}): Promise<boolean>;
|
|
|
|
|
/**
|
|
|
|
|
* Check if an email address is suppressed (has bounced previously)
|
|
|
|
|
* @param email Email address to check
|
|
|
|
|
* @returns Whether the email is suppressed
|
|
|
|
|
*/
|
|
|
|
|
isEmailSuppressed(email: string): boolean;
|
|
|
|
|
/**
|
|
|
|
|
* Get suppression information for an email
|
|
|
|
|
* @param email Email address to check
|
|
|
|
|
* @returns Suppression information or null if not suppressed
|
|
|
|
|
*/
|
|
|
|
|
getSuppressionInfo(email: string): {
|
|
|
|
|
reason: string;
|
|
|
|
|
timestamp: number;
|
|
|
|
|
expiresAt?: number;
|
|
|
|
|
} | null;
|
|
|
|
|
/**
|
|
|
|
|
* Get bounce history information for an email
|
|
|
|
|
* @param email Email address to check
|
|
|
|
|
* @returns Bounce history or null if no bounces
|
|
|
|
|
*/
|
|
|
|
|
getBounceHistory(email: string): {
|
|
|
|
|
lastBounce: number;
|
|
|
|
|
count: number;
|
|
|
|
|
type: BounceType;
|
|
|
|
|
category: BounceCategory;
|
|
|
|
|
} | null;
|
|
|
|
|
/**
|
|
|
|
|
* Get all suppressed email addresses
|
|
|
|
|
* @returns Array of suppressed email addresses
|
|
|
|
|
*/
|
|
|
|
|
getSuppressionList(): string[];
|
|
|
|
|
/**
|
|
|
|
|
* Get all hard bounced email addresses
|
|
|
|
|
* @returns Array of hard bounced email addresses
|
|
|
|
|
*/
|
|
|
|
|
getHardBouncedAddresses(): string[];
|
|
|
|
|
/**
|
|
|
|
|
* 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: string, reason: string, expiresAt?: number): void;
|
|
|
|
|
/**
|
|
|
|
|
* Remove an email from the suppression list
|
|
|
|
|
* @param email Email address to remove from suppression
|
|
|
|
|
*/
|
|
|
|
|
removeFromSuppressionList(email: string): void;
|
|
|
|
|
/**
|
|
|
|
|
* Get the status of IP warmup process
|
|
|
|
|
* @param ipAddress Optional specific IP to check
|
|
|
|
|
* @returns Status of IP warmup
|
|
|
|
|
*/
|
|
|
|
|
getIPWarmupStatus(ipAddress?: string): any;
|
|
|
|
|
/**
|
|
|
|
|
* Add a new IP address to the warmup process
|
|
|
|
|
* @param ipAddress IP address to add
|
|
|
|
|
*/
|
|
|
|
|
addIPToWarmup(ipAddress: string): void;
|
|
|
|
|
/**
|
|
|
|
|
* Remove an IP address from the warmup process
|
|
|
|
|
* @param ipAddress IP address to remove
|
|
|
|
|
*/
|
|
|
|
|
removeIPFromWarmup(ipAddress: string): void;
|
|
|
|
|
/**
|
|
|
|
|
* Update metrics for an IP in the warmup process
|
|
|
|
|
* @param ipAddress IP address
|
|
|
|
|
* @param metrics Metrics to update
|
|
|
|
|
*/
|
|
|
|
|
updateIPWarmupMetrics(ipAddress: string, metrics: {
|
|
|
|
|
openRate?: number;
|
|
|
|
|
bounceRate?: number;
|
|
|
|
|
complaintRate?: number;
|
|
|
|
|
}): void;
|
|
|
|
|
/**
|
|
|
|
|
* 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: string): boolean;
|
|
|
|
|
/**
|
|
|
|
|
* 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: string): boolean;
|
|
|
|
|
/**
|
|
|
|
|
* 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: {
|
|
|
|
|
from: string;
|
|
|
|
|
to: string[];
|
|
|
|
|
domain: string;
|
|
|
|
|
isTransactional?: boolean;
|
|
|
|
|
}): string | null;
|
|
|
|
|
/**
|
|
|
|
|
* Set the active IP allocation policy for warmup
|
|
|
|
|
* @param policyName Name of the policy to set
|
|
|
|
|
*/
|
|
|
|
|
setIPAllocationPolicy(policyName: string): void;
|
|
|
|
|
/**
|
|
|
|
|
* Record that an email was sent using a specific IP
|
|
|
|
|
* @param ipAddress IP address used for sending
|
|
|
|
|
*/
|
|
|
|
|
recordIPSend(ipAddress: string): void;
|
|
|
|
|
/**
|
|
|
|
|
* Get reputation data for a domain
|
|
|
|
|
* @param domain Domain to get reputation for
|
|
|
|
|
* @returns Domain reputation metrics
|
|
|
|
|
*/
|
|
|
|
|
getDomainReputationData(domain: string): any;
|
|
|
|
|
/**
|
|
|
|
|
* Get summary reputation data for all monitored domains
|
|
|
|
|
* @returns Summary data for all domains
|
|
|
|
|
*/
|
|
|
|
|
getReputationSummary(): any;
|
|
|
|
|
/**
|
|
|
|
|
* Add a domain to the reputation monitoring system
|
|
|
|
|
* @param domain Domain to add
|
|
|
|
|
*/
|
|
|
|
|
addDomainToMonitoring(domain: string): void;
|
|
|
|
|
/**
|
|
|
|
|
* Remove a domain from the reputation monitoring system
|
|
|
|
|
* @param domain Domain to remove
|
|
|
|
|
*/
|
|
|
|
|
removeDomainFromMonitoring(domain: string): void;
|
|
|
|
|
/**
|
|
|
|
|
* Record an email event for domain reputation tracking
|
|
|
|
|
* @param domain Domain sending the email
|
|
|
|
|
* @param event Event details
|
|
|
|
|
*/
|
|
|
|
|
recordReputationEvent(domain: string, event: {
|
|
|
|
|
type: 'sent' | 'delivered' | 'bounce' | 'complaint' | 'open' | 'click';
|
|
|
|
|
count?: number;
|
|
|
|
|
hardBounce?: boolean;
|
|
|
|
|
receivingDomain?: string;
|
|
|
|
|
}): void;
|
|
|
|
|
/**
|
|
|
|
|
* Check if DKIM key exists for a domain
|
|
|
|
|
* @param domain Domain to check
|
|
|
|
|
*/
|
|
|
|
|
hasDkimKey(domain: string): boolean;
|
|
|
|
|
/**
|
|
|
|
|
* Record successful email delivery
|
|
|
|
|
* @param domain Sending domain
|
|
|
|
|
*/
|
|
|
|
|
recordDelivery(domain: string): void;
|
|
|
|
|
/**
|
|
|
|
|
* 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: string, receivingDomain: string, bounceType: 'hard' | 'soft', reason: string): void;
|
|
|
|
|
/**
|
|
|
|
|
* Get the rate limiter instance
|
|
|
|
|
* @returns The unified rate limiter
|
|
|
|
|
*/
|
|
|
|
|
getRateLimiter(): UnifiedRateLimiter;
|
|
|
|
|
}
|
|
|
|
|
export {};
|