fix(mail): align queue, outbound hostname, and DKIM selector behavior across the mail server APIs
This commit is contained in:
@@ -8,17 +8,18 @@ import {
|
||||
SecurityEventType
|
||||
} from '../../security/index.js';
|
||||
import { DKIMCreator } from '../security/classes.dkimcreator.js';
|
||||
import { hasStorageManagerMethods, type IStorageManagerLike } from '../interfaces.storage.js';
|
||||
import { RustSecurityBridge } from '../../security/classes.rustsecuritybridge.js';
|
||||
import type { IEmailReceivedEvent, IAuthRequestEvent, IEmailData } from '../../security/classes.rustsecuritybridge.js';
|
||||
import type { IEmailReceivedEvent, IAuthRequestEvent } from '../../security/classes.rustsecuritybridge.js';
|
||||
import { EmailRouter } from './classes.email.router.js';
|
||||
import type { IEmailRoute, IEmailAction, IEmailContext, IEmailDomainConfig } from './interfaces.js';
|
||||
import type { IEmailRoute, IEmailContext, IEmailDomainConfig } from './interfaces.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 type { ISmtpSendResult, IOutboundEmail } from '../../security/classes.rustsecuritybridge.js';
|
||||
import { MultiModeDeliverySystem, type IMultiModeDeliveryOptions } from '../delivery/classes.delivery.system.js';
|
||||
import { UnifiedDeliveryQueue, type IQueueOptions } from '../delivery/classes.delivery.queue.js';
|
||||
import { MultiModeDeliverySystem, type IDeliveryStats, type IMultiModeDeliveryOptions } from '../delivery/classes.delivery.system.js';
|
||||
import { UnifiedDeliveryQueue, type IQueueItem, type IQueueOptions, type IQueueStats } from '../delivery/classes.delivery.queue.js';
|
||||
import { UnifiedRateLimiter, type IHierarchicalRateLimits } from '../delivery/classes.unified.rate.limiter.js';
|
||||
import { SmtpState } from '../delivery/interfaces.js';
|
||||
import type { EmailProcessingMode, ISmtpSession as IBaseSmtpSession } from '../delivery/interfaces.js';
|
||||
@@ -28,7 +29,7 @@ import { DkimManager } from './classes.dkim.manager.js';
|
||||
|
||||
/** External DcRouter interface shape used by UnifiedEmailServer */
|
||||
interface DcRouter {
|
||||
storageManager: any;
|
||||
storageManager: IStorageManagerLike;
|
||||
dnsServer?: any;
|
||||
options?: any;
|
||||
}
|
||||
@@ -49,11 +50,14 @@ export interface IExtendedSmtpSession extends ISmtpSession {
|
||||
export interface IUnifiedEmailServerOptions {
|
||||
// Base server options
|
||||
ports: number[];
|
||||
/** Public SMTP hostname used for greeting/banner and as the default outbound identity. */
|
||||
hostname: string;
|
||||
domains: IEmailDomainConfig[]; // Domain configurations
|
||||
banner?: string;
|
||||
debug?: boolean;
|
||||
useSocketHandler?: boolean; // Use socket-handler mode instead of port listening
|
||||
/** Persist router changes back into storage when a storage manager is available. */
|
||||
persistRoutes?: boolean;
|
||||
|
||||
// Authentication options
|
||||
auth?: {
|
||||
@@ -92,6 +96,8 @@ export interface IUnifiedEmailServerOptions {
|
||||
|
||||
// Outbound settings
|
||||
outbound?: {
|
||||
/** Override the SMTP identity used for outbound delivery. Defaults to `hostname`. */
|
||||
hostname?: string;
|
||||
maxConnections?: number;
|
||||
connectionTimeout?: number;
|
||||
socketTimeout?: number;
|
||||
@@ -99,6 +105,9 @@ export interface IUnifiedEmailServerOptions {
|
||||
defaultFrom?: string;
|
||||
};
|
||||
|
||||
// Delivery queue
|
||||
queue?: IQueueOptions;
|
||||
|
||||
// Rate limiting (global limits, can be overridden per domain)
|
||||
rateLimits?: IHierarchicalRateLimits;
|
||||
}
|
||||
@@ -206,7 +215,7 @@ export class UnifiedEmailServer extends EventEmitter {
|
||||
// Initialize email router with routes and storage manager
|
||||
this.emailRouter = new EmailRouter(options.routes || [], {
|
||||
storageManager: dcRouter.storageManager,
|
||||
persistChanges: true
|
||||
persistChanges: options.persistRoutes ?? hasStorageManagerMethods(dcRouter.storageManager, ['get', 'set'])
|
||||
});
|
||||
|
||||
// Initialize rate limiter
|
||||
@@ -226,7 +235,8 @@ export class UnifiedEmailServer extends EventEmitter {
|
||||
storageType: 'memory', // Default to memory storage
|
||||
maxRetries: 3,
|
||||
baseRetryDelay: 300000, // 5 minutes
|
||||
maxRetryDelay: 3600000 // 1 hour
|
||||
maxRetryDelay: 3600000, // 1 hour
|
||||
...options.queue,
|
||||
};
|
||||
|
||||
this.deliveryQueue = new UnifiedDeliveryQueue(queueOptions);
|
||||
@@ -277,6 +287,14 @@ export class UnifiedEmailServer extends EventEmitter {
|
||||
// We'll create the SMTP servers during the start() method
|
||||
}
|
||||
|
||||
private getAdvertisedHostname(): string {
|
||||
return this.options.hostname;
|
||||
}
|
||||
|
||||
private getOutboundHostname(): string {
|
||||
return this.options.outbound?.hostname || this.options.hostname;
|
||||
}
|
||||
|
||||
/**
|
||||
* Send an outbound email via the Rust SMTP client.
|
||||
* Uses connection pooling in the Rust binary for efficiency.
|
||||
@@ -314,7 +332,7 @@ export class UnifiedEmailServer extends EventEmitter {
|
||||
host,
|
||||
port,
|
||||
secure: port === 465,
|
||||
domain: this.options.hostname,
|
||||
domain: this.getOutboundHostname(),
|
||||
auth: options?.auth,
|
||||
email: outboundEmail,
|
||||
dkim,
|
||||
@@ -455,7 +473,7 @@ export class UnifiedEmailServer extends EventEmitter {
|
||||
const securePort = (this.options.ports as number[]).find(p => p === 465);
|
||||
|
||||
const started = await this.rustBridge.startSmtpServer({
|
||||
hostname: this.options.hostname,
|
||||
hostname: this.getAdvertisedHostname(),
|
||||
ports: smtpPorts,
|
||||
securePort: securePort,
|
||||
tlsCertPem,
|
||||
@@ -518,6 +536,9 @@ export class UnifiedEmailServer extends EventEmitter {
|
||||
logger.log('info', 'Email delivery queue shut down');
|
||||
}
|
||||
|
||||
this.bounceManager.stop();
|
||||
logger.log('info', 'Bounce manager stopped');
|
||||
|
||||
// Close all Rust SMTP client connection pools
|
||||
try {
|
||||
await this.rustBridge.closeSmtpPool();
|
||||
@@ -973,6 +994,10 @@ export class UnifiedEmailServer extends EventEmitter {
|
||||
this.emailRouter.updateRoutes(routes);
|
||||
}
|
||||
|
||||
public getEmailRoutes(): IEmailRoute[] {
|
||||
return this.emailRouter.getRoutes();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get server statistics
|
||||
*/
|
||||
@@ -980,6 +1005,22 @@ export class UnifiedEmailServer extends EventEmitter {
|
||||
return { ...this.stats };
|
||||
}
|
||||
|
||||
public getQueueStats(): IQueueStats {
|
||||
return this.deliveryQueue.getStats();
|
||||
}
|
||||
|
||||
public getQueueItems(): IQueueItem[] {
|
||||
return this.deliveryQueue.listItems();
|
||||
}
|
||||
|
||||
public getQueueItem(id: string): IQueueItem | undefined {
|
||||
return this.deliveryQueue.getItem(id);
|
||||
}
|
||||
|
||||
public getDeliveryStats(): IDeliveryStats {
|
||||
return this.deliverySystem.getStats();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get domain registry
|
||||
*/
|
||||
@@ -1039,11 +1080,10 @@ export class UnifiedEmailServer extends EventEmitter {
|
||||
// Sign with DKIM if configured
|
||||
if (mode === 'mta' && route?.action.options?.mtaOptions?.dkimSign) {
|
||||
const domain = email.from.split('@')[1];
|
||||
await this.dkimManager.handleDkimSigning(email, domain, route.action.options.mtaOptions.dkimOptions?.keySelector || 'mta');
|
||||
await this.dkimManager.handleDkimSigning(email, domain, route.action.options.mtaOptions.dkimOptions?.keySelector || 'default');
|
||||
}
|
||||
|
||||
const id = plugins.uuid.v4();
|
||||
await this.deliveryQueue.enqueue(email, mode, route);
|
||||
const id = await this.deliveryQueue.enqueue(email, mode, route);
|
||||
|
||||
logger.log('info', `Email queued with ID: ${id}`);
|
||||
return id;
|
||||
|
||||
Reference in New Issue
Block a user