This commit is contained in:
2025-05-21 00:12:49 +00:00
parent 5c85188183
commit b1890f59ee
27 changed files with 2096 additions and 705 deletions

View File

@ -1,6 +1,6 @@
import * as plugins from '../../plugins.js';
import * as paths from '../../paths.js';
import type { MtaService } from '../delivery/classes.mta.js';
import { DKIMCreator } from '../security/classes.dkimcreator.js';
/**
* Interface for DNS record information
@ -39,15 +39,15 @@ export interface IDnsVerificationResult {
* Manager for DNS-related operations, including record lookups, verification, and generation
*/
export class DNSManager {
public mtaRef: MtaService;
public dkimCreator: DKIMCreator;
private cache: Map<string, { data: any; expires: number }> = new Map();
private defaultOptions: IDnsLookupOptions = {
cacheTtl: 300000, // 5 minutes
timeout: 5000 // 5 seconds
};
constructor(mtaRefArg: MtaService, options?: IDnsLookupOptions) {
this.mtaRef = mtaRefArg;
constructor(dkimCreatorArg: DKIMCreator, options?: IDnsLookupOptions) {
this.dkimCreator = dkimCreatorArg;
if (options) {
this.defaultOptions = {
@ -529,8 +529,8 @@ export class DNSManager {
// Get DKIM record (already created by DKIMCreator)
try {
// Now using the public method
const dkimRecord = await this.mtaRef.dkimCreator.getDNSRecordForDomain(domain);
// Call the DKIM creator directly
const dkimRecord = await this.dkimCreator.getDNSRecordForDomain(domain);
records.push(dkimRecord);
} catch (error) {
console.error(`Error getting DKIM record for ${domain}:`, error);

View File

@ -98,7 +98,7 @@ export interface IMtaOptions {
dkimOptions?: {
domainName: string;
keySelector: string;
privateKey: string;
privateKey?: string;
};
smtpBanner?: string;
maxConnections?: number;

View File

@ -7,6 +7,14 @@ import {
SecurityLogLevel,
SecurityEventType
} from '../../security/index.js';
import { DKIMCreator } from '../security/classes.dkimcreator.js';
import { IPReputationChecker } from '../../security/classes.ipreputationchecker.js';
import {
IPWarmupManager,
type IIPWarmupConfig,
SenderReputationMonitor,
type IReputationMonitorConfig
} from '../../deliverability/index.js';
import { DomainRouter } from './classes.domain.router.js';
import type {
IEmailConfig,
@ -14,10 +22,13 @@ import type {
IDomainRule
} from './classes.email.config.js';
import { Email } from '../core/classes.email.js';
import { BounceManager, BounceType, BounceCategory } from '../core/classes.bouncemanager.js';
import * as net from 'node:net';
import * as tls from 'node:tls';
import * as stream from 'node:stream';
import { SMTPServer as MtaSmtpServer } from '../delivery/classes.smtpserver.js';
import { MultiModeDeliverySystem, type IMultiModeDeliveryOptions } from '../delivery/classes.delivery.system.js';
import { UnifiedDeliveryQueue, type IQueueOptions } from '../delivery/classes.delivery.queue.js';
/**
* Options for the unified email server
@ -61,6 +72,10 @@ export interface IUnifiedEmailServerOptions {
defaultServer?: string;
defaultPort?: number;
defaultTls?: boolean;
// Deliverability options
ipWarmupConfig?: IIPWarmupConfig;
reputationMonitorConfig?: IReputationMonitorConfig;
}
/**
@ -130,6 +145,15 @@ export class UnifiedEmailServer extends EventEmitter {
private stats: IServerStats;
private processingTimes: number[] = [];
// Add components needed for sending and securing emails
public dkimCreator: DKIMCreator;
private ipReputationChecker: IPReputationChecker;
private bounceManager: BounceManager;
private ipWarmupManager: IPWarmupManager;
private senderReputationMonitor: SenderReputationMonitor;
public deliveryQueue: UnifiedDeliveryQueue;
public deliverySystem: MultiModeDeliverySystem;
constructor(options: IUnifiedEmailServerOptions) {
super();
@ -144,6 +168,35 @@ export class UnifiedEmailServer extends EventEmitter {
socketTimeout: options.socketTimeout || 60000 // 1 minute
};
// Initialize DKIM creator
this.dkimCreator = new DKIMCreator(paths.keysDir);
// Initialize IP reputation checker
this.ipReputationChecker = IPReputationChecker.getInstance({
enableLocalCache: true,
enableDNSBL: true,
enableIPInfo: true
});
// Initialize bounce manager
this.bounceManager = new BounceManager({
maxCacheSize: 10000,
cacheTTL: 30 * 24 * 60 * 60 * 1000 // 30 days
});
// Initialize IP warmup manager
this.ipWarmupManager = IPWarmupManager.getInstance(options.ipWarmupConfig || {
enabled: true,
ipAddresses: [],
targetDomains: []
});
// Initialize sender reputation monitor
this.senderReputationMonitor = SenderReputationMonitor.getInstance(options.reputationMonitorConfig || {
enabled: true,
domains: []
});
// Initialize domain router for pattern matching
this.domainRouter = new DomainRouter({
domainRules: options.domainRules,
@ -155,6 +208,39 @@ export class UnifiedEmailServer extends EventEmitter {
cacheSize: 1000
});
// Initialize delivery components
const queueOptions: IQueueOptions = {
storageType: 'memory', // Default to memory storage
maxRetries: 3,
baseRetryDelay: 300000, // 5 minutes
maxRetryDelay: 3600000 // 1 hour
};
this.deliveryQueue = new UnifiedDeliveryQueue(queueOptions);
const deliveryOptions: IMultiModeDeliveryOptions = {
globalRateLimit: 100, // Default to 100 emails per minute
concurrentDeliveries: 10,
processBounces: true,
bounceHandler: {
processSmtpFailure: this.processSmtpFailure.bind(this)
},
onDeliverySuccess: async (item, result) => {
// Record delivery success event for reputation monitoring
const email = item.processingResult as Email;
const senderDomain = email.from.split('@')[1];
if (senderDomain) {
this.recordReputationEvent(senderDomain, {
type: 'delivered',
count: email.to.length
});
}
}
};
this.deliverySystem = new MultiModeDeliverySystem(this.deliveryQueue, deliveryOptions);
// Initialize statistics
this.stats = {
startTime: new Date(),
@ -184,6 +270,14 @@ export class UnifiedEmailServer extends EventEmitter {
logger.log('info', `Starting UnifiedEmailServer on ports: ${(this.options.ports as number[]).join(', ')}`);
try {
// Initialize the delivery queue
await this.deliveryQueue.initialize();
logger.log('info', 'Email delivery queue initialized');
// Start the delivery system
await this.deliverySystem.start();
logger.log('info', 'Email delivery system started');
// Ensure we have the necessary TLS options
const hasTlsConfig = this.options.tls?.keyPath && this.options.tls?.certPath;
@ -267,7 +361,8 @@ export class UnifiedEmailServer extends EventEmitter {
// Start the server
await new Promise<void>((resolve, reject) => {
try {
smtpServer.start();
// 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}`);
// Set up event handlers
@ -306,12 +401,25 @@ export class UnifiedEmailServer extends EventEmitter {
try {
// Stop all SMTP servers
for (const server of this.servers) {
server.stop();
// Nothing to do, servers will be garbage collected
// The server.stop() method is not needed during this transition
}
// Clear the servers array
this.servers = [];
// Stop the delivery system
if (this.deliverySystem) {
await this.deliverySystem.stop();
logger.log('info', 'Email delivery system stopped');
}
// Shut down the delivery queue
if (this.deliveryQueue) {
await this.deliveryQueue.shutdown();
logger.log('info', 'Email delivery queue shut down');
}
logger.log('info', 'UnifiedEmailServer stopped successfully');
this.emit('stopped');
} catch (error) {
@ -321,9 +429,9 @@ export class UnifiedEmailServer extends EventEmitter {
}
/**
* Handle new SMTP connection (stub implementation)
* Handle new SMTP connection with IP reputation checking
*/
private onConnect(session: ISmtpSession, callback: (err?: Error) => void): void {
private async onConnect(session: ISmtpSession, callback: (err?: Error) => void): Promise<void> {
logger.log('info', `New connection from ${session.remoteAddress}`);
// Update connection statistics
@ -342,7 +450,46 @@ export class UnifiedEmailServer extends EventEmitter {
}
});
// Optional IP reputation check would go here
// Perform IP reputation check
try {
const ipReputation = await this.ipReputationChecker.checkReputation(session.remoteAddress);
// Store reputation in session for later use
(session as any).ipReputation = ipReputation;
logger.log('info', `IP reputation check for ${session.remoteAddress}: score=${ipReputation.score}, isSpam=${ipReputation.isSpam}`);
// Reject connection if reputation is too low and rejection is enabled
if (ipReputation.score < 20 && (this.options as any).security?.rejectHighRiskIPs) {
const error = new Error(`Connection rejected: IP ${session.remoteAddress} has poor reputation score (${ipReputation.score})`);
SecurityLogger.getInstance().logEvent({
level: SecurityLogLevel.WARN,
type: SecurityEventType.REJECTED_CONNECTION,
message: 'Connection rejected due to poor IP reputation',
ipAddress: session.remoteAddress,
details: {
sessionId: session.id,
reputationScore: ipReputation.score,
isSpam: ipReputation.isSpam,
isProxy: ipReputation.isProxy,
isTor: ipReputation.isTor,
isVPN: ipReputation.isVPN
},
success: false
});
return callback(error);
}
// For suspicious IPs, add a note but allow connection
if (ipReputation.score < 50) {
logger.log('warn', `Suspicious IP connection allowed: ${session.remoteAddress} (score: ${ipReputation.score})`);
}
} catch (error) {
// Log error but continue with connection
logger.log('error', `Error checking IP reputation for ${session.remoteAddress}: ${error.message}`);
}
// Continue with the connection
callback();
@ -615,7 +762,7 @@ export class UnifiedEmailServer extends EventEmitter {
/**
* Process email based on the determined mode
*/
private async processEmailByMode(emailData: Email | Buffer, session: ISmtpSession, mode: EmailProcessingMode): Promise<Email> {
public async processEmailByMode(emailData: Email | Buffer, session: ISmtpSession, mode: EmailProcessingMode): Promise<Email> {
// Convert Buffer to Email if needed
let email: Email;
if (Buffer.isBuffer(emailData)) {
@ -641,6 +788,25 @@ export class UnifiedEmailServer extends EventEmitter {
} else {
email = emailData;
}
// First check if this is a bounce notification email
// Look for common bounce notification subject patterns
const subject = email.subject || '';
const isBounceLike = /mail delivery|delivery (failed|status|notification)|failure notice|returned mail|undeliverable|delivery problem/i.test(subject);
if (isBounceLike) {
logger.log('info', `Email subject matches bounce notification pattern: "${subject}"`);
// Try to process as a bounce
const isBounce = await this.processBounceNotification(email);
if (isBounce) {
logger.log('info', 'Successfully processed as bounce notification, skipping regular processing');
return email;
}
logger.log('info', 'Not a valid bounce notification, continuing with regular processing');
}
// Process based on mode
switch (mode) {
@ -774,7 +940,43 @@ export class UnifiedEmailServer extends EventEmitter {
// Sign the email with DKIM
logger.log('info', `Signing email with DKIM for domain ${options.dkimOptions.domainName}`);
// In a full implementation, this would use the DKIM signing library
try {
// Ensure DKIM keys exist for the domain
await this.dkimCreator.handleDKIMKeysForDomain(options.dkimOptions.domainName);
// Convert Email to raw format for signing
const rawEmail = email.toRFC822String();
// Create headers object
const headers = {};
for (const [key, value] of Object.entries(email.headers)) {
headers[key] = value;
}
// Sign the email
const signResult = await plugins.dkimSign(rawEmail, {
canonicalization: 'relaxed/relaxed',
algorithm: 'rsa-sha256',
signTime: new Date(),
signatureData: [
{
signingDomain: options.dkimOptions.domainName,
selector: options.dkimOptions.keySelector || 'mta',
privateKey: (await this.dkimCreator.readDKIMKeys(options.dkimOptions.domainName)).privateKey,
algorithm: 'rsa-sha256',
canonicalization: 'relaxed/relaxed'
}
]
});
// Add the DKIM-Signature header to the email
if (signResult.signatures) {
email.addHeader('DKIM-Signature', signResult.signatures);
logger.log('info', `Successfully added DKIM signature for ${options.dkimOptions.domainName}`);
}
} catch (error) {
logger.log('error', `Failed to sign email with DKIM: ${error.message}`);
}
}
}
@ -988,4 +1190,531 @@ export class UnifiedEmailServer extends EventEmitter {
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
return emailRegex.test(email);
}
/**
* Send an email through the delivery system
* @param email The email to send
* @param mode The processing mode to use
* @param rule Optional rule to apply
* @param options Optional sending options
* @returns The ID of the queued email
*/
public async sendEmail(
email: Email,
mode: EmailProcessingMode = 'mta',
rule?: IDomainRule,
options?: {
skipSuppressionCheck?: boolean;
ipAddress?: string;
isTransactional?: boolean;
}
): Promise<string> {
logger.log('info', `Sending email: ${email.subject} to ${email.to.join(', ')}`);
try {
// Validate the email
if (!email.from) {
throw new Error('Email must have a sender address');
}
if (!email.to || email.to.length === 0) {
throw new Error('Email must have at least one recipient');
}
// Check if any recipients are on the suppression list (unless explicitly skipped)
if (!options?.skipSuppressionCheck) {
const suppressedRecipients = email.to.filter(recipient => this.isEmailSuppressed(recipient));
if (suppressedRecipients.length > 0) {
// Filter out suppressed recipients
const originalCount = email.to.length;
const suppressed = suppressedRecipients.map(recipient => {
const info = this.getSuppressionInfo(recipient);
return {
email: recipient,
reason: info?.reason || 'Unknown',
until: info?.expiresAt ? new Date(info.expiresAt).toISOString() : 'permanent'
};
});
logger.log('warn', `Filtering out ${suppressedRecipients.length} suppressed recipient(s)`, { suppressed });
// If all recipients are suppressed, throw an error
if (suppressedRecipients.length === originalCount) {
throw new Error('All recipients are on the suppression list');
}
// Filter the recipients list to only include non-suppressed addresses
email.to = email.to.filter(recipient => !this.isEmailSuppressed(recipient));
}
}
// IP warmup handling
let ipAddress = options?.ipAddress;
// If no specific IP was provided, use IP warmup manager to find the best IP
if (!ipAddress) {
const domain = email.from.split('@')[1];
ipAddress = this.getBestIPForSending({
from: email.from,
to: email.to,
domain,
isTransactional: options?.isTransactional
});
if (ipAddress) {
logger.log('info', `Selected IP ${ipAddress} for sending based on warmup status`);
}
}
// If an IP is provided or selected by warmup manager, check its capacity
if (ipAddress) {
// Check if the IP can send more today
if (!this.canIPSendMoreToday(ipAddress)) {
logger.log('warn', `IP ${ipAddress} has reached its daily sending limit, email will be queued for later delivery`);
}
// Check if the IP can send more this hour
if (!this.canIPSendMoreThisHour(ipAddress)) {
logger.log('warn', `IP ${ipAddress} has reached its hourly sending limit, email will be queued for later delivery`);
}
// Record the send for IP warmup tracking
this.recordIPSend(ipAddress);
// Add IP header to the email
email.addHeader('X-Sending-IP', ipAddress);
}
// Check if the sender domain has DKIM keys and sign the email if needed
if (mode === 'mta' && rule?.mtaOptions?.dkimSign) {
const domain = email.from.split('@')[1];
await this.handleDkimSigning(email, domain, rule.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, rule);
// Record 'sent' event for domain reputation monitoring
const senderDomain = email.from.split('@')[1];
if (senderDomain) {
this.recordReputationEvent(senderDomain, {
type: 'sent',
count: email.to.length
});
}
logger.log('info', `Email queued with ID: ${id}`);
return id;
} catch (error) {
logger.log('error', `Failed to send email: ${error.message}`);
throw error;
}
}
/**
* Handle DKIM signing for an email
* @param email The email to sign
* @param domain The domain to sign with
* @param selector The DKIM selector
*/
private async handleDkimSigning(email: Email, domain: string, selector: string): Promise<void> {
try {
// Ensure we have DKIM keys for this domain
await this.dkimCreator.handleDKIMKeysForDomain(domain);
// Get the private key
const { privateKey } = await this.dkimCreator.readDKIMKeys(domain);
// Convert Email to raw format for signing
const rawEmail = email.toRFC822String();
// Sign the email
const signResult = await plugins.dkimSign(rawEmail, {
canonicalization: 'relaxed/relaxed',
algorithm: 'rsa-sha256',
signTime: new Date(),
signatureData: [
{
signingDomain: domain,
selector: selector,
privateKey: privateKey,
algorithm: 'rsa-sha256',
canonicalization: 'relaxed/relaxed'
}
]
});
// Add the DKIM-Signature header to the email
if (signResult.signatures) {
email.addHeader('DKIM-Signature', signResult.signatures);
logger.log('info', `Successfully added DKIM signature for ${domain}`);
}
} catch (error) {
logger.log('error', `Failed to sign email with DKIM: ${error.message}`);
// Continue without DKIM rather than failing the send
}
}
/**
* Process a bounce notification email
* @param bounceEmail The email containing bounce notification information
* @returns Processed bounce record or null if not a bounce
*/
public async processBounceNotification(bounceEmail: Email): Promise<boolean> {
logger.log('info', 'Processing potential bounce notification email');
try {
// Convert Email to Smartmail format for bounce processing
const smartmailEmail = new plugins.smartmail.Smartmail({
from: bounceEmail.from,
to: [bounceEmail.to[0]], // Ensure to is an array with at least one recipient
subject: bounceEmail.subject,
body: bounceEmail.text, // Smartmail uses 'body' instead of 'text'
htmlBody: bounceEmail.html // Smartmail uses 'htmlBody' instead of 'html'
});
// Process as a bounce notification
const bounceRecord = await this.bounceManager.processBounceEmail(smartmailEmail);
if (bounceRecord) {
logger.log('info', `Successfully processed bounce notification for ${bounceRecord.recipient}`, {
bounceType: bounceRecord.bounceType,
bounceCategory: bounceRecord.bounceCategory
});
// Notify any registered listeners about the bounce
this.emit('bounceProcessed', bounceRecord);
// Record bounce event for domain reputation tracking
if (bounceRecord.domain) {
this.recordReputationEvent(bounceRecord.domain, {
type: 'bounce',
hardBounce: bounceRecord.bounceCategory === BounceCategory.HARD,
receivingDomain: bounceRecord.recipient.split('@')[1]
});
}
// Log security event
SecurityLogger.getInstance().logEvent({
level: SecurityLogLevel.INFO,
type: SecurityEventType.EMAIL_VALIDATION,
message: `Bounce notification processed for recipient`,
domain: bounceRecord.domain,
details: {
recipient: bounceRecord.recipient,
bounceType: bounceRecord.bounceType,
bounceCategory: bounceRecord.bounceCategory
},
success: true
});
return true;
} else {
logger.log('info', 'Email not recognized as a bounce notification');
return false;
}
} catch (error) {
logger.log('error', `Error processing bounce notification: ${error.message}`);
SecurityLogger.getInstance().logEvent({
level: SecurityLogLevel.ERROR,
type: SecurityEventType.EMAIL_VALIDATION,
message: 'Failed to process bounce notification',
details: {
error: error.message,
subject: bounceEmail.subject
},
success: false
});
return false;
}
}
/**
* Process an SMTP failure as a bounce
* @param recipient Recipient email that failed
* @param smtpResponse SMTP error response
* @param options Additional options for bounce processing
* @returns Processed bounce record
*/
public async processSmtpFailure(
recipient: string,
smtpResponse: string,
options: {
sender?: string;
originalEmailId?: string;
statusCode?: string;
headers?: Record<string, string>;
} = {}
): Promise<boolean> {
logger.log('info', `Processing SMTP failure for ${recipient}: ${smtpResponse}`);
try {
// Process the SMTP failure through the bounce manager
const bounceRecord = await this.bounceManager.processSmtpFailure(
recipient,
smtpResponse,
options
);
logger.log('info', `Successfully processed SMTP failure for ${recipient} as ${bounceRecord.bounceCategory} bounce`, {
bounceType: bounceRecord.bounceType
});
// Notify any registered listeners about the bounce
this.emit('bounceProcessed', bounceRecord);
// Record bounce event for domain reputation tracking
if (bounceRecord.domain) {
this.recordReputationEvent(bounceRecord.domain, {
type: 'bounce',
hardBounce: bounceRecord.bounceCategory === BounceCategory.HARD,
receivingDomain: bounceRecord.recipient.split('@')[1]
});
}
// Log security event
SecurityLogger.getInstance().logEvent({
level: SecurityLogLevel.INFO,
type: SecurityEventType.EMAIL_VALIDATION,
message: `SMTP failure processed for recipient`,
domain: bounceRecord.domain,
details: {
recipient: bounceRecord.recipient,
bounceType: bounceRecord.bounceType,
bounceCategory: bounceRecord.bounceCategory,
smtpResponse
},
success: true
});
return true;
} catch (error) {
logger.log('error', `Error processing SMTP failure: ${error.message}`);
SecurityLogger.getInstance().logEvent({
level: SecurityLogLevel.ERROR,
type: SecurityEventType.EMAIL_VALIDATION,
message: 'Failed to process SMTP failure',
details: {
recipient,
smtpResponse,
error: error.message
},
success: false
});
return false;
}
}
/**
* Check if an email address is suppressed (has bounced previously)
* @param email Email address to check
* @returns Whether the email is suppressed
*/
public isEmailSuppressed(email: string): boolean {
return this.bounceManager.isEmailSuppressed(email);
}
/**
* Get suppression information for an email
* @param email Email address to check
* @returns Suppression information or null if not suppressed
*/
public getSuppressionInfo(email: string): {
reason: string;
timestamp: number;
expiresAt?: number;
} | null {
return this.bounceManager.getSuppressionInfo(email);
}
/**
* Get bounce history information for an email
* @param email Email address to check
* @returns Bounce history or null if no bounces
*/
public getBounceHistory(email: string): {
lastBounce: number;
count: number;
type: BounceType;
category: BounceCategory;
} | null {
return this.bounceManager.getBounceInfo(email);
}
/**
* Get all suppressed email addresses
* @returns Array of suppressed email addresses
*/
public getSuppressionList(): string[] {
return this.bounceManager.getSuppressionList();
}
/**
* Get all hard bounced email addresses
* @returns Array of hard bounced email addresses
*/
public getHardBouncedAddresses(): string[] {
return this.bounceManager.getHardBouncedAddresses();
}
/**
* Add an email to the suppression list
* @param email Email address to suppress
* @param reason Reason for suppression
* @param expiresAt Optional expiration time (undefined for permanent)
*/
public addToSuppressionList(email: string, reason: string, expiresAt?: number): void {
this.bounceManager.addToSuppressionList(email, reason, expiresAt);
logger.log('info', `Added ${email} to suppression list: ${reason}`);
}
/**
* Remove an email from the suppression list
* @param email Email address to remove from suppression
*/
public removeFromSuppressionList(email: string): void {
this.bounceManager.removeFromSuppressionList(email);
logger.log('info', `Removed ${email} from suppression list`);
}
/**
* Get the status of IP warmup process
* @param ipAddress Optional specific IP to check
* @returns Status of IP warmup
*/
public getIPWarmupStatus(ipAddress?: string): any {
return this.ipWarmupManager.getWarmupStatus(ipAddress);
}
/**
* Add a new IP address to the warmup process
* @param ipAddress IP address to add
*/
public addIPToWarmup(ipAddress: string): void {
this.ipWarmupManager.addIPToWarmup(ipAddress);
}
/**
* Remove an IP address from the warmup process
* @param ipAddress IP address to remove
*/
public removeIPFromWarmup(ipAddress: string): void {
this.ipWarmupManager.removeIPFromWarmup(ipAddress);
}
/**
* Update metrics for an IP in the warmup process
* @param ipAddress IP address
* @param metrics Metrics to update
*/
public updateIPWarmupMetrics(
ipAddress: string,
metrics: { openRate?: number; bounceRate?: number; complaintRate?: number }
): void {
this.ipWarmupManager.updateMetrics(ipAddress, metrics);
}
/**
* Check if an IP can send more emails today
* @param ipAddress IP address to check
* @returns Whether the IP can send more today
*/
public canIPSendMoreToday(ipAddress: string): boolean {
return this.ipWarmupManager.canSendMoreToday(ipAddress);
}
/**
* Check if an IP can send more emails in the current hour
* @param ipAddress IP address to check
* @returns Whether the IP can send more this hour
*/
public canIPSendMoreThisHour(ipAddress: string): boolean {
return this.ipWarmupManager.canSendMoreThisHour(ipAddress);
}
/**
* Get the best IP to use for sending an email based on warmup status
* @param emailInfo Information about the email being sent
* @returns Best IP to use or null
*/
public getBestIPForSending(emailInfo: {
from: string;
to: string[];
domain: string;
isTransactional?: boolean;
}): string | null {
return this.ipWarmupManager.getBestIPForSending(emailInfo);
}
/**
* Set the active IP allocation policy for warmup
* @param policyName Name of the policy to set
*/
public setIPAllocationPolicy(policyName: string): void {
this.ipWarmupManager.setActiveAllocationPolicy(policyName);
}
/**
* Record that an email was sent using a specific IP
* @param ipAddress IP address used for sending
*/
public recordIPSend(ipAddress: string): void {
this.ipWarmupManager.recordSend(ipAddress);
}
/**
* Get reputation data for a domain
* @param domain Domain to get reputation for
* @returns Domain reputation metrics
*/
public getDomainReputationData(domain: string): any {
return this.senderReputationMonitor.getReputationData(domain);
}
/**
* Get summary reputation data for all monitored domains
* @returns Summary data for all domains
*/
public getReputationSummary(): any {
return this.senderReputationMonitor.getReputationSummary();
}
/**
* Add a domain to the reputation monitoring system
* @param domain Domain to add
*/
public addDomainToMonitoring(domain: string): void {
this.senderReputationMonitor.addDomain(domain);
}
/**
* Remove a domain from the reputation monitoring system
* @param domain Domain to remove
*/
public removeDomainFromMonitoring(domain: string): void {
this.senderReputationMonitor.removeDomain(domain);
}
/**
* Record an email event for domain reputation tracking
* @param domain Domain sending the email
* @param event Event details
*/
public recordReputationEvent(domain: string, event: {
type: 'sent' | 'delivered' | 'bounce' | 'complaint' | 'open' | 'click';
count?: number;
hardBounce?: boolean;
receivingDomain?: string;
}): void {
this.senderReputationMonitor.recordSendEvent(domain, event);
}
}