fix(mail options): simplify mail options

This commit is contained in:
Philipp Kunz 2025-05-27 19:28:12 +00:00
parent 2aeb52bf13
commit 6aa54d974e
5 changed files with 250 additions and 19 deletions

Binary file not shown.

View File

@ -3,10 +3,9 @@ import * as paths from './paths.js';
// Certificate types are available via plugins.tsclass
// Import the consolidated email config
import type { IEmailConfig, IDomainRule } from './mail/routing/classes.email.config.js';
import type { EmailProcessingMode } from './mail/delivery/interfaces.js';
import { UnifiedEmailServer } from './mail/routing/classes.unified.email.server.js';
// Import the email server and its configuration
import { UnifiedEmailServer, type IUnifiedEmailServerOptions } from './mail/routing/classes.unified.email.server.js';
import type { IDomainRule, EmailProcessingMode } from './mail/routing/classes.email.config.js';
import { logger } from './logger.js';
// Import the email configuration helpers directly from mail/delivery
import { configureEmailStorage, configureEmailServer } from './mail/delivery/index.js';
@ -19,10 +18,10 @@ export interface IDcRouterOptions {
smartProxyConfig?: plugins.smartproxy.ISmartProxyOptions;
/**
* Consolidated email configuration
* Email server configuration
* This enables all email handling with pattern-based routing
*/
emailConfig?: IEmailConfig;
emailConfig?: IUnifiedEmailServerOptions;
/**
* Custom email port configuration
@ -238,7 +237,7 @@ export class DcRouter {
/**
* Generate SmartProxy routes for email configuration
*/
private generateEmailRoutes(emailConfig: IEmailConfig): plugins.smartproxy.IRouteConfig[] {
private generateEmailRoutes(emailConfig: IUnifiedEmailServerOptions): plugins.smartproxy.IRouteConfig[] {
const emailRoutes: plugins.smartproxy.IRouteConfig[] = [];
// Get the custom port mapping if available, otherwise use defaults
@ -442,20 +441,22 @@ export class DcRouter {
throw new Error('Email configuration is required for unified email handling');
}
const emailConfig = this.options.emailConfig;
// Apply port mapping if behind SmartProxy
const portMapping = this.options.emailPortConfig?.portMapping || {
25: 10025, // SMTP
587: 10587, // Submission
465: 10465 // SMTPS
};
// Create unified email server with mapped internal ports
this.emailServer = new UnifiedEmailServer(this, {
...emailConfig,
domains: emailConfig.domains || [], // Provide default empty array
ports: emailConfig.ports.map(port => portMapping[port] || port + 10000),
// Create config with mapped ports
const emailConfig: IUnifiedEmailServerOptions = {
...this.options.emailConfig,
ports: this.options.emailConfig.ports.map(port => portMapping[port] || port + 10000),
hostname: 'localhost' // Listen on localhost for SmartProxy forwarding
});
};
// Create unified email server
this.emailServer = new UnifiedEmailServer(this, emailConfig);
// Set up error handling
this.emailServer.on('error', (err: Error) => {
@ -465,14 +466,14 @@ export class DcRouter {
// Start the server
await this.emailServer.start();
logger.log('info', `Email server started on ports: ${this.emailServer['options'].ports.join(', ')}`);
logger.log('info', `Email server started on ports: ${emailConfig.ports.join(', ')}`);
}
/**
* Update the unified email configuration
* @param config New email configuration
*/
public async updateEmailConfig(config: IEmailConfig): Promise<void> {
public async updateEmailConfig(config: IUnifiedEmailServerOptions): Promise<void> {
// Stop existing email components
await this.stopUnifiedEmailComponents();
@ -598,7 +599,7 @@ export class DcRouter {
}
}
// Re-export types for convenience
export type { IEmailConfig, IDomainRule, EmailProcessingMode };
// Re-export email server types for convenience
export type { IUnifiedEmailServerOptions, IDomainRule, EmailProcessingMode };
export default DcRouter;

View File

@ -1,5 +1,37 @@
import type { IBaseConfig, ITlsConfig, IQueueConfig, IRateLimitConfig, IMonitoringConfig } from './base.config.js';
/**
* MIGRATION GUIDE:
* ================
* The IEmailConfig and IMtaConfig interfaces are deprecated.
* Please use IUnifiedEmailServerOptions from '../mail/routing/classes.unified.email.server.js' instead.
*
* Example migration:
*
* OLD:
* ```typescript
* const config: IEmailConfig = {
* ports: [25, 587],
* hostname: 'mail.example.com',
* domainRules: [...],
* defaultMode: 'forward'
* };
* ```
*
* NEW:
* ```typescript
* const config: IUnifiedEmailServerOptions = {
* ports: [25, 587],
* hostname: 'mail.example.com',
* domains: ['example.com'],
* domainRules: [...],
* defaultMode: 'forward'
* };
* ```
*
* The new interface consolidates all email configuration into a single, cleaner structure.
*/
/**
* Email processing modes
*/
@ -127,6 +159,8 @@ export interface IDomainRule {
/**
* Email service configuration
* @deprecated Use IUnifiedEmailServerOptions from mail/routing/classes.unified.email.server.ts instead
* This interface is kept for backward compatibility only
*/
export interface IEmailConfig extends IBaseConfig {
/**
@ -262,6 +296,8 @@ export interface IEmailConfig extends IBaseConfig {
/**
* MTA configuration
* @deprecated Use IUnifiedEmailServerOptions from mail/routing/classes.unified.email.server.ts instead
* This interface is kept for backward compatibility only
*/
export interface IMtaConfig {
/**

View File

@ -0,0 +1,194 @@
/**
* Email Port Mapping Configuration
*
* Centralizes the logic for mapping external email ports to internal ports
* when running behind SmartProxy
*/
/**
* Default email port mapping
* Maps external ports to internal ports for SmartProxy forwarding
*/
export const DEFAULT_EMAIL_PORT_MAPPING: Record<number, number> = {
25: 10025, // SMTP - Standard email delivery port
587: 10587, // Submission - Authenticated submission port
465: 10465, // SMTPS - Implicit TLS submission port
2525: 12525 // Alternative SMTP port (often used for testing)
};
/**
* Email port configuration
*/
export interface IEmailPortConfig {
/**
* External port number
*/
external: number;
/**
* Internal port number (where the email server actually listens)
*/
internal: number;
/**
* Port description
*/
description: string;
/**
* TLS mode for this port
*/
tlsMode: 'none' | 'starttls' | 'implicit';
/**
* Whether authentication is required on this port
*/
requireAuth?: boolean;
}
/**
* Standard email port configurations
*/
export const EMAIL_PORT_CONFIGS: Record<number, Omit<IEmailPortConfig, 'internal'>> = {
25: {
external: 25,
description: 'SMTP - Standard email delivery',
tlsMode: 'starttls',
requireAuth: false
},
587: {
external: 587,
description: 'Submission - Authenticated email submission',
tlsMode: 'starttls',
requireAuth: true
},
465: {
external: 465,
description: 'SMTPS - Implicit TLS submission',
tlsMode: 'implicit',
requireAuth: true
},
2525: {
external: 2525,
description: 'Alternative SMTP - Often used for testing',
tlsMode: 'starttls',
requireAuth: false
}
};
/**
* Map external ports to internal ports
* @param externalPorts Array of external ports
* @param customMapping Optional custom port mapping
* @returns Array of internal ports
*/
export function mapEmailPorts(
externalPorts: number[],
customMapping?: Record<number, number>
): number[] {
const mapping = { ...DEFAULT_EMAIL_PORT_MAPPING, ...customMapping };
return externalPorts.map(port => {
// Use custom mapping if available, otherwise add 10000 to the port
return mapping[port] || port + 10000;
});
}
/**
* Get full port configuration including internal port mapping
* @param externalPort External port number
* @param customMapping Optional custom port mapping
* @returns Full port configuration
*/
export function getEmailPortConfig(
externalPort: number,
customMapping?: Record<number, number>
): IEmailPortConfig | undefined {
const config = EMAIL_PORT_CONFIGS[externalPort];
if (!config) {
return undefined;
}
const mapping = { ...DEFAULT_EMAIL_PORT_MAPPING, ...customMapping };
const internalPort = mapping[externalPort] || externalPort + 10000;
return {
...config,
internal: internalPort
};
}
/**
* Generate SmartProxy routes for email ports
* @param externalPorts Array of external ports to handle
* @param hostname Hostname for the email server
* @param customMapping Optional custom port mapping
* @returns Array of SmartProxy route configurations
*/
export function generateEmailProxyRoutes(
externalPorts: number[],
_hostname: string,
customMapping?: Record<number, number>
): plugins.smartproxy.IRouteConfig[] {
const routes: plugins.smartproxy.IRouteConfig[] = [];
for (const externalPort of externalPorts) {
const config = getEmailPortConfig(externalPort, customMapping);
if (!config) {
continue;
}
// Create route for this email port
const route: plugins.smartproxy.IRouteConfig = {
name: `email-${config.description.toLowerCase().replace(/\s+/g, '-')}`,
match: {
ports: [externalPort]
},
action: {
type: 'forward',
target: {
host: 'localhost',
port: config.internal
},
tls: {
// For SMTP/Submission ports, use passthrough to let email server handle STARTTLS
// For SMTPS (465), SmartProxy handles TLS termination
mode: config.tlsMode === 'implicit' ? 'terminate' : 'passthrough',
certificate: config.tlsMode === 'implicit' ? 'auto' : undefined
}
}
};
routes.push(route);
}
return routes;
}
/**
* Validate email port configuration
* @param ports Array of ports to validate
* @throws Error if invalid ports are specified
*/
export function validateEmailPorts(ports: number[]): void {
const invalidPorts = ports.filter(port => {
// Check if port is a valid number
if (!Number.isInteger(port) || port < 1 || port > 65535) {
return true;
}
// Warn about non-standard ports
if (!EMAIL_PORT_CONFIGS[port]) {
console.warn(`Port ${port} is not a standard email port. Supported standard ports are: ${Object.keys(EMAIL_PORT_CONFIGS).join(', ')}`);
}
return false;
});
if (invalidPorts.length > 0) {
throw new Error(`Invalid email ports specified: ${invalidPorts.join(', ')}`);
}
}
// Type imports to avoid circular dependencies
import type * as plugins from '../plugins.js';

View File

@ -91,7 +91,7 @@ export type {
IPlatformConfig,
IEmailConfig,
IMtaConfig,
ISmsConfig,
ISmsConfig,
IBaseConfig,
ITlsConfig,
IHttpServerConfig,