fix(mail): align queue, outbound hostname, and DKIM selector behavior across the mail server APIs

This commit is contained in:
2026-04-14 12:17:50 +00:00
parent 04e73c366c
commit 65ecd94540
15 changed files with 387 additions and 147 deletions

View File

@@ -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;