Compare commits

...

5 Commits

11 changed files with 1951 additions and 407 deletions

View File

@ -1,5 +1,35 @@
# Changelog
## 2025-05-08 - 2.7.0 - feat(dcrouter)
Implement unified email configuration with patternbased routing and consolidated email processing. Migrate SMTP forwarding and storeandforward into a single, configuration-driven system that supports glob pattern matching in domain rules.
- Introduced IEmailConfig interface to consolidate MTA, forwarding, and processing settings.
- Added pattern-based domain routing with glob patterns (e.g., '*@example.com', '*@*.example.net').
- Reworked DcRouter integration to expose unified email handling and updated readme.plan.md and changelog.md accordingly.
- Removed deprecated SMTP forwarding components in favor of the consolidated approach.
## 2025-05-08 - 2.7.0 - feat(dcrouter)
Implement consolidated email configuration with pattern-based routing
- Added new pattern-based email routing with glob patterns (e.g., `*@task.vc`, `*@*.example.net`)
- Consolidated all email functionality (MTA, forwarding, processing) under a unified `emailConfig` interface
- Implemented domain router with pattern specificity calculation for most accurate matching
- Removed deprecated components (SMTP forwarding, Store-and-Forward) in favor of the unified approach
- Updated DcRouter tests to use the new consolidated email configuration pattern
- Enhanced inline documentation with detailed interface definitions and configuration examples
- Updated implementation plan with comprehensive component designs for the unified email system
## 2025-05-07 - 2.6.0 - feat(dcrouter)
Implement integrated DcRouter with comprehensive SmartProxy configuration, enhanced SMTP processing, and robust storeandforward email routing
- Marked completion of tasks in readme.plan.md with [x] flags for SMTP server setup, email processing pipeline, queue management, and delivery system.
- Reworked DcRouter to use direct SmartProxy configuration, separating smtpConfig and smtpForwarding approaches.
- Added new components for delivery queue and delivery system with persistent storage support.
- Improved SMTP server implementation with TLS support, event handlers for connection, authentication, sender/recipient validation, and data processing.
- Refined domain-based routing and transformation logic in EmailProcessor with metrics and logging.
- Updated exported modules in dcrouter index to include SMTP storeandforward components.
- Enhanced inline documentation and code comments for configuration interfaces and integration details.
## 2025-05-07 - 2.5.0 - feat(dcrouter)
Enhance DcRouter configuration and update documentation

View File

@ -1,7 +1,7 @@
{
"name": "@serve.zone/platformservice",
"private": true,
"version": "2.5.0",
"version": "2.7.0",
"description": "A multifaceted platform service handling mail, SMS, letter delivery, and AI services.",
"main": "dist_ts/index.js",
"typings": "dist_ts/index.d.ts",

File diff suppressed because it is too large Load Diff

View File

@ -2,9 +2,10 @@ import { tap, expect } from '@push.rocks/tapbundle';
import * as plugins from '../ts/plugins.js';
import {
DcRouter,
type IDcRouterOptions,
type ISmtpForwardingConfig,
type IDomainRoutingConfig
type IDcRouterOptions,
type IEmailConfig,
type EmailProcessingMode,
type IDomainRule
} from '../ts/dcrouter/index.js';
tap.test('DcRouter class - basic functionality', async () => {
@ -21,71 +22,97 @@ tap.test('DcRouter class - basic functionality', async () => {
expect(router.options.tls.contactEmail).toEqual('test@example.com');
});
tap.test('DcRouter class - HTTP routing configuration', async () => {
// Create HTTP routing configuration
const httpRoutes: IDomainRoutingConfig[] = [
{
domain: 'example.com',
targetServer: '192.168.1.10',
targetPort: 8080,
useTls: true
tap.test('DcRouter class - SmartProxy configuration', async () => {
// Create SmartProxy configuration
const smartProxyConfig: plugins.smartproxy.ISmartProxyOptions = {
fromPort: 443,
toPort: 8080,
targetIP: '10.0.0.10',
sniEnabled: true,
acme: {
port: 80,
enabled: true,
autoRenew: true,
useProduction: false,
renewThresholdDays: 30,
accountEmail: 'admin@example.com'
},
{
domain: '*.example.org',
targetServer: '192.168.1.20',
targetPort: 9000,
useTls: false
}
];
const options: IDcRouterOptions = {
httpDomainRoutes: httpRoutes,
tls: {
contactEmail: 'test@example.com'
}
};
const router = new DcRouter(options);
expect(router.options.httpDomainRoutes.length).toEqual(2);
expect(router.options.httpDomainRoutes[0].domain).toEqual('example.com');
expect(router.options.httpDomainRoutes[1].domain).toEqual('*.example.org');
});
tap.test('DcRouter class - SMTP forwarding configuration', async () => {
// Create SMTP forwarding configuration
const smtpForwarding: ISmtpForwardingConfig = {
enabled: true,
ports: [25, 587, 465],
defaultServer: 'mail.example.com',
defaultPort: 25,
useTls: true,
preserveSourceIp: true,
domainRoutes: [
globalPortRanges: [
{ from: 80, to: 80 },
{ from: 443, to: 443 }
],
domainConfigs: [
{
domain: 'example.com',
server: 'mail1.example.com',
port: 25
},
{
domain: 'example.org',
server: 'mail2.example.org',
port: 587
domains: ['example.com', 'www.example.com'],
allowedIPs: ['0.0.0.0/0'],
targetIPs: ['10.0.0.10'],
portRanges: [
{ from: 80, to: 80 },
{ from: 443, to: 443 }
]
}
]
};
const options: IDcRouterOptions = {
smtpForwarding,
smartProxyConfig,
tls: {
contactEmail: 'test@example.com'
}
};
const router = new DcRouter(options);
expect(router.options.smtpForwarding.enabled).toEqual(true);
expect(router.options.smtpForwarding.ports.length).toEqual(3);
expect(router.options.smtpForwarding.domainRoutes.length).toEqual(2);
expect(router.options.smtpForwarding.domainRoutes[0].domain).toEqual('example.com');
expect(router.options.smartProxyConfig).toBeTruthy();
expect(router.options.smartProxyConfig.domainConfigs.length).toEqual(1);
expect(router.options.smartProxyConfig.domainConfigs[0].domains[0]).toEqual('example.com');
});
tap.test('DcRouter class - Email configuration', async () => {
// Create consolidated email configuration
const emailConfig: IEmailConfig = {
ports: [25, 587, 465],
hostname: 'mail.example.com',
maxMessageSize: 50 * 1024 * 1024, // 50MB
defaultMode: 'forward' as EmailProcessingMode,
defaultServer: 'fallback-mail.example.com',
defaultPort: 25,
defaultTls: true,
domainRules: [
{
pattern: '*@example.com',
mode: 'forward' as EmailProcessingMode,
target: {
server: 'mail1.example.com',
port: 25,
useTls: true
}
},
{
pattern: '*@example.org',
mode: 'mta' as EmailProcessingMode,
mtaOptions: {
domain: 'example.org',
allowLocalDelivery: true
}
}
]
};
const options: IDcRouterOptions = {
emailConfig,
tls: {
contactEmail: 'test@example.com'
}
};
const router = new DcRouter(options);
expect(router.options.emailConfig).toBeTruthy();
expect(router.options.emailConfig.ports.length).toEqual(3);
expect(router.options.emailConfig.domainRules.length).toEqual(2);
expect(router.options.emailConfig.domainRules[0].pattern).toEqual('*@example.com');
expect(router.options.emailConfig.domainRules[1].pattern).toEqual('*@example.org');
});
tap.test('DcRouter class - Domain pattern matching', async () => {

View File

@ -3,6 +3,6 @@
*/
export const commitinfo = {
name: '@serve.zone/platformservice',
version: '2.5.0',
version: '2.7.0',
description: 'A multifaceted platform service handling mail, SMS, letter delivery, and AI services.'
}

View File

@ -3,62 +3,24 @@ import * as paths from '../paths.js';
import { SmtpPortConfig, type ISmtpPortSettings } from './classes.smtp.portconfig.js';
import { EmailDomainRouter, type IEmailDomainRoutingConfig } from './classes.email.domainrouter.js';
import { type IMtaConfig, MtaService } from '../mta/classes.mta.js';
// Certificate types are available via plugins.tsclass
/**
* Configuration for SMTP forwarding functionality
*/
export interface ISmtpForwardingConfig {
/** Whether SMTP forwarding is enabled */
enabled?: boolean;
/** SMTP ports to listen on */
ports?: number[];
/** Default SMTP server hostname */
defaultServer: string;
/** Default SMTP server port */
defaultPort?: number;
/** Whether to use TLS when connecting to the default server */
useTls?: boolean;
/** Preserve source IP address when forwarding */
preserveSourceIp?: boolean;
/** Domain-specific routing rules */
domainRoutes?: Array<{
domain: string;
server: string;
port?: number;
}>;
}
/**
* Simple domain-based routing configuration
*/
export interface IDomainRoutingConfig {
/** The domain name or pattern (e.g., example.com or *.example.com) */
domain: string;
/** Target server hostname or IP */
targetServer: string;
/** Target port */
targetPort: number;
/** Enable HTTPS/TLS for this route */
useTls?: boolean;
/** Allow incoming connections from these IP ranges (default: all) */
allowedIps?: string[];
}
// Import the consolidated email config
import type { IEmailConfig } from './classes.email.config.js';
import { DomainRouter } from './classes.domain.router.js';
export interface IDcRouterOptions {
/** HTTP/HTTPS domain-based routing */
httpDomainRoutes?: IDomainRoutingConfig[];
/**
* Direct SmartProxy configuration - gives full control over HTTP/HTTPS and TCP/SNI traffic
* This is the preferred way to configure HTTP/HTTPS and general TCP/SNI traffic
*/
smartProxyConfig?: plugins.smartproxy.ISmartProxyOptions;
/** SMTP forwarding configuration */
smtpForwarding?: ISmtpForwardingConfig;
/** MTA service configuration (if not using SMTP forwarding) */
mtaConfig?: IMtaConfig;
/** Existing MTA service instance to use (if not using SMTP forwarding) */
mtaServiceInstance?: MtaService;
/**
* Consolidated email configuration
* This enables all email handling with pattern-based routing
*/
emailConfig?: IEmailConfig;
/** TLS/certificate configuration */
tls?: {
@ -89,15 +51,17 @@ export interface PortProxyRuleContext {
proxy: plugins.smartproxy.SmartProxy;
configs: plugins.smartproxy.IPortProxySettings['domainConfigs'];
}
export class DcRouter {
public options: IDcRouterOptions;
// Core services
public smartProxy?: plugins.smartproxy.SmartProxy;
public smtpProxy?: plugins.smartproxy.SmartProxy;
public mta?: MtaService;
public dnsServer?: plugins.smartdns.DnsServer;
// Unified email components
public domainRouter?: DomainRouter;
// Environment access
private qenv = new plugins.qenv.Qenv('./', '.nogit/');
@ -112,14 +76,14 @@ export class DcRouter {
console.log('Starting DcRouter services...');
try {
// 1. Set up HTTP/HTTPS traffic handling with SmartProxy
await this.setupHttpProxy();
// Set up SmartProxy for HTTP/HTTPS and general TCP/SNI traffic
if (this.options.smartProxyConfig) {
await this.setupSmartProxy();
}
// 2. Set up MTA or SMTP forwarding
if (this.options.smtpForwarding?.enabled) {
await this.setupSmtpForwarding();
} else {
await this.setupMtaService();
// Set up unified email handling if configured
if (this.options.emailConfig) {
await this.setupUnifiedEmailHandling();
}
// 3. Set up DNS server if configured
@ -139,119 +103,40 @@ export class DcRouter {
}
/**
* Set up SmartProxy for HTTP/HTTPS traffic
* Set up SmartProxy with direct configuration
*/
private async setupHttpProxy() {
if (!this.options.httpDomainRoutes || this.options.httpDomainRoutes.length === 0) {
console.log('No HTTP domain routes configured, skipping HTTP proxy setup');
private async setupSmartProxy(): Promise<void> {
if (!this.options.smartProxyConfig) {
return;
}
console.log('Setting up SmartProxy for HTTP/HTTPS traffic');
console.log('Setting up SmartProxy with direct configuration');
// Prepare SmartProxy configuration
const smartProxyConfig: plugins.smartproxy.ISmartProxyOptions = {
fromPort: 443,
toPort: this.options.httpDomainRoutes[0].targetPort,
targetIP: this.options.httpDomainRoutes[0].targetServer,
sniEnabled: true,
acme: {
port: 80,
enabled: true,
autoRenew: true,
useProduction: true,
renewThresholdDays: 30,
accountEmail: this.options.tls?.contactEmail || 'admin@example.com' // ACME requires an email
},
globalPortRanges: [{ from: 443, to: 443 }],
domainConfigs: []
};
// Create SmartProxy instance with full configuration
this.smartProxy = new plugins.smartproxy.SmartProxy(this.options.smartProxyConfig);
// Create domain configs from the HTTP routes
smartProxyConfig.domainConfigs = this.options.httpDomainRoutes.map(route => ({
domains: [route.domain],
targetIPs: [route.targetServer],
allowedIPs: route.allowedIps || ['0.0.0.0/0'],
// Skip certificate management for wildcard domains as it's not supported by HTTP-01 challenges
certificateManagement: !route.domain.includes('*')
}));
// Create and start the SmartProxy instance
this.smartProxy = new plugins.smartproxy.SmartProxy(smartProxyConfig);
// Listen for certificate events
this.smartProxy.on('certificate-issued', event => {
console.log(`Certificate issued for ${event.domain}, expires ${event.expiryDate}`);
// Set up event listeners
this.smartProxy.on('error', (err) => {
console.error('SmartProxy error:', err);
});
this.smartProxy.on('certificate-renewed', event => {
console.log(`Certificate renewed for ${event.domain}, expires ${event.expiryDate}`);
});
if (this.options.smartProxyConfig.acme) {
this.smartProxy.on('certificate-issued', (event) => {
console.log(`Certificate issued for ${event.domain}, expires ${event.expiryDate}`);
});
this.smartProxy.on('certificate-renewed', (event) => {
console.log(`Certificate renewed for ${event.domain}, expires ${event.expiryDate}`);
});
}
// Start SmartProxy
await this.smartProxy.start();
console.log(`HTTP/HTTPS proxy configured with ${smartProxyConfig.domainConfigs.length} domain routes`);
console.log('SmartProxy started successfully');
}
/**
* Set up the MTA service
*/
private async setupMtaService() {
// Use existing MTA service if provided
if (this.options.mtaServiceInstance) {
this.mta = this.options.mtaServiceInstance;
console.log('Using provided MTA service instance');
} else if (this.options.mtaConfig) {
// Create new MTA service with the provided configuration
this.mta = new MtaService(undefined, this.options.mtaConfig);
console.log('Created new MTA service instance');
// Start the MTA service
await this.mta.start();
console.log('MTA service started');
}
}
/**
* Set up SMTP forwarding with SmartProxy
*/
private async setupSmtpForwarding() {
if (!this.options.smtpForwarding) {
return;
}
const forwarding = this.options.smtpForwarding;
console.log('Setting up SMTP forwarding');
// Determine which ports to listen on
const smtpPorts = forwarding.ports || [25, 587, 465];
// Create SmartProxy instance for SMTP forwarding
this.smtpProxy = new plugins.smartproxy.SmartProxy({
// Listen on the first SMTP port
fromPort: smtpPorts[0],
// Forward to the default server
toPort: forwarding.defaultPort || 25,
targetIP: forwarding.defaultServer,
// Enable SNI if port 465 is included (implicit TLS)
sniEnabled: smtpPorts.includes(465),
// Preserve source IP if requested
preserveSourceIP: forwarding.preserveSourceIp || false,
// Create domain configs for SMTP routing
domainConfigs: forwarding.domainRoutes?.map(route => ({
domains: [route.domain],
allowedIPs: ['0.0.0.0/0'], // Allow from anywhere by default
targetIPs: [route.server]
})) || [],
// Include all SMTP ports in the global port ranges
globalPortRanges: smtpPorts.map(port => ({ from: port, to: port }))
});
// Start the SMTP proxy
await this.smtpProxy.start();
console.log(`SMTP forwarding configured on ports ${smtpPorts.join(', ')}`);
}
/**
* Check if a domain matches a pattern (including wildcard support)
@ -287,16 +172,11 @@ export class DcRouter {
try {
// Stop all services in parallel for faster shutdown
await Promise.all([
// Stop unified email components if running
this.domainRouter ? this.stopUnifiedEmailComponents().catch(err => console.error('Error stopping unified email components:', err)) : Promise.resolve(),
// Stop HTTP SmartProxy if running
this.smartProxy ? this.smartProxy.stop().catch(err => console.error('Error stopping HTTP SmartProxy:', err)) : Promise.resolve(),
// Stop SMTP SmartProxy if running
this.smtpProxy ? this.smtpProxy.stop().catch(err => console.error('Error stopping SMTP SmartProxy:', err)) : Promise.resolve(),
// Stop MTA service if it's our own (not an external instance)
(this.mta && !this.options.mtaServiceInstance) ?
this.mta.stop().catch(err => console.error('Error stopping MTA service:', err)) :
Promise.resolve(),
this.smartProxy ? this.smartProxy.stop().catch(err => console.error('Error stopping SmartProxy:', err)) : Promise.resolve(),
// Stop DNS server if running
this.dnsServer ?
@ -312,46 +192,84 @@ export class DcRouter {
}
/**
* Update HTTP domain routes
* @param routes New HTTP domain routes
* Update SmartProxy configuration
* @param config New SmartProxy configuration
*/
public async updateHttpRoutes(routes: IDomainRoutingConfig[]): Promise<void> {
this.options.httpDomainRoutes = routes;
// If SmartProxy is already running, we need to restart it with the new configuration
public async updateSmartProxyConfig(config: plugins.smartproxy.ISmartProxyOptions): Promise<void> {
// Stop existing SmartProxy if running
if (this.smartProxy) {
// Stop the existing SmartProxy
await this.smartProxy.stop();
this.smartProxy = undefined;
// Start a new SmartProxy with the updated configuration
await this.setupHttpProxy();
}
console.log(`Updated HTTP routes with ${routes.length} domains`);
}
/**
* Update SMTP forwarding configuration
* @param config New SMTP forwarding configuration
*/
public async updateSmtpForwarding(config: ISmtpForwardingConfig): Promise<void> {
// Stop existing SMTP proxy if running
if (this.smtpProxy) {
await this.smtpProxy.stop();
this.smtpProxy = undefined;
}
// Update configuration
this.options.smtpForwarding = config;
this.options.smartProxyConfig = config;
// Restart SMTP forwarding if enabled
if (config.enabled) {
await this.setupSmtpForwarding();
// Start new SmartProxy with updated configuration
await this.setupSmartProxy();
console.log('SmartProxy configuration updated');
}
/**
* Set up unified email handling with pattern-based routing
* This implements the consolidated emailConfig approach
*/
private async setupUnifiedEmailHandling(): Promise<void> {
console.log('Setting up unified email handling with pattern-based routing');
if (!this.options.emailConfig) {
throw new Error('Email configuration is required for unified email handling');
}
console.log('SMTP forwarding configuration updated');
try {
// Create domain router for pattern matching
this.domainRouter = new DomainRouter({
domainRules: this.options.emailConfig.domainRules,
defaultMode: this.options.emailConfig.defaultMode,
defaultServer: this.options.emailConfig.defaultServer,
defaultPort: this.options.emailConfig.defaultPort,
defaultTls: this.options.emailConfig.defaultTls
});
// TODO: Initialize the full unified email processing pipeline
console.log(`Unified email handling configured with ${this.options.emailConfig.domainRules.length} domain rules`);
} catch (error) {
console.error('Error setting up unified email handling:', error);
throw error;
}
}
/**
* Update the unified email configuration
* @param config New email configuration
*/
public async updateEmailConfig(config: IEmailConfig): Promise<void> {
// Stop existing email components
await this.stopUnifiedEmailComponents();
// Update configuration
this.options.emailConfig = config;
// Start email handling with new configuration
await this.setupUnifiedEmailHandling();
console.log('Unified email configuration updated');
}
/**
* Stop all unified email components
*/
private async stopUnifiedEmailComponents(): Promise<void> {
// TODO: Implement stopping all unified email components
// Clear the domain router
this.domainRouter = undefined;
}
}
export default DcRouter;

View File

@ -0,0 +1,351 @@
import * as plugins from '../plugins.js';
import { EventEmitter } from 'node:events';
import { type IDomainRule, type EmailProcessingMode } from './classes.email.config.js';
/**
* Options for the domain-based router
*/
export interface IDomainRouterOptions {
// Domain rules with glob pattern matching
domainRules: IDomainRule[];
// Default handling for unmatched domains
defaultMode: EmailProcessingMode;
defaultServer?: string;
defaultPort?: number;
defaultTls?: boolean;
// Pattern matching options
caseSensitive?: boolean;
priorityOrder?: 'most-specific' | 'first-match';
// Cache settings for pattern matching
enableCache?: boolean;
cacheSize?: number;
}
/**
* Result of a pattern match operation
*/
export interface IPatternMatchResult {
rule: IDomainRule;
exactMatch: boolean;
wildcardMatch: boolean;
specificity: number; // Higher is more specific
}
/**
* A pattern matching and routing class for email domains
*/
export class DomainRouter extends EventEmitter {
private options: IDomainRouterOptions;
private patternCache: Map<string, IDomainRule | null> = new Map();
/**
* Create a new domain router
* @param options Router options
*/
constructor(options: IDomainRouterOptions) {
super();
this.options = {
// Default options
caseSensitive: false,
priorityOrder: 'most-specific',
enableCache: true,
cacheSize: 1000,
...options
};
}
/**
* Match an email address against defined rules
* @param email Email address to match
* @returns The matching rule or null if no match
*/
public matchRule(email: string): IDomainRule | null {
// Check cache first if enabled
if (this.options.enableCache && this.patternCache.has(email)) {
return this.patternCache.get(email) || null;
}
// Normalize email if case-insensitive
const normalizedEmail = this.options.caseSensitive ? email : email.toLowerCase();
// Get all matching rules
const matches = this.getAllMatchingRules(normalizedEmail);
if (matches.length === 0) {
// Cache the result (null) if caching is enabled
if (this.options.enableCache) {
this.addToCache(email, null);
}
return null;
}
// Sort by specificity or order
let matchedRule: IDomainRule;
if (this.options.priorityOrder === 'most-specific') {
// Sort by specificity (most specific first)
const sortedMatches = matches.sort((a, b) => {
const aSpecificity = this.calculateSpecificity(a.pattern);
const bSpecificity = this.calculateSpecificity(b.pattern);
return bSpecificity - aSpecificity;
});
matchedRule = sortedMatches[0];
} else {
// First match in the list
matchedRule = matches[0];
}
// Cache the result if caching is enabled
if (this.options.enableCache) {
this.addToCache(email, matchedRule);
}
return matchedRule;
}
/**
* Calculate pattern specificity
* Higher is more specific
* @param pattern Pattern to calculate specificity for
*/
private calculateSpecificity(pattern: string): number {
let specificity = 0;
// Exact match is most specific
if (!pattern.includes('*')) {
return 100;
}
// Count characters that aren't wildcards
specificity += pattern.replace(/\*/g, '').length;
// Position of wildcards affects specificity
if (pattern.startsWith('*@')) {
// Wildcard in local part
specificity += 10;
} else if (pattern.includes('@*')) {
// Wildcard in domain part
specificity += 20;
}
return specificity;
}
/**
* Check if email matches a specific pattern
* @param email Email address to check
* @param pattern Pattern to check against
* @returns True if matching, false otherwise
*/
public matchesPattern(email: string, pattern: string): boolean {
// Normalize if case-insensitive
const normalizedEmail = this.options.caseSensitive ? email : email.toLowerCase();
const normalizedPattern = this.options.caseSensitive ? pattern : pattern.toLowerCase();
// Exact match
if (normalizedEmail === normalizedPattern) {
return true;
}
// Convert glob pattern to regex
const regexPattern = this.globToRegExp(normalizedPattern);
return regexPattern.test(normalizedEmail);
}
/**
* Convert a glob pattern to a regular expression
* @param pattern Glob pattern
* @returns Regular expression
*/
private globToRegExp(pattern: string): RegExp {
// Escape special regex characters except * and ?
let regexString = pattern
.replace(/[.+^${}()|[\]\\]/g, '\\$&')
.replace(/\*/g, '.*')
.replace(/\?/g, '.');
return new RegExp(`^${regexString}$`);
}
/**
* Get all rules that match an email address
* @param email Email address to match
* @returns Array of matching rules
*/
public getAllMatchingRules(email: string): IDomainRule[] {
return this.options.domainRules.filter(rule => this.matchesPattern(email, rule.pattern));
}
/**
* Add a new routing rule
* @param rule Domain rule to add
*/
public addRule(rule: IDomainRule): void {
// Validate the rule
this.validateRule(rule);
// Add the rule
this.options.domainRules.push(rule);
// Clear cache since rules have changed
this.clearCache();
// Emit event
this.emit('ruleAdded', rule);
}
/**
* Validate a domain rule
* @param rule Rule to validate
*/
private validateRule(rule: IDomainRule): void {
// Pattern is required
if (!rule.pattern) {
throw new Error('Domain rule pattern is required');
}
// Mode is required
if (!rule.mode) {
throw new Error('Domain rule mode is required');
}
// Forward mode requires target
if (rule.mode === 'forward' && !rule.target) {
throw new Error('Forward mode requires target configuration');
}
// Forward mode target requires server
if (rule.mode === 'forward' && rule.target && !rule.target.server) {
throw new Error('Forward mode target requires server');
}
}
/**
* Update an existing rule
* @param pattern Pattern to update
* @param updates Updates to apply
* @returns True if rule was found and updated, false otherwise
*/
public updateRule(pattern: string, updates: Partial<IDomainRule>): boolean {
const ruleIndex = this.options.domainRules.findIndex(r => r.pattern === pattern);
if (ruleIndex === -1) {
return false;
}
// Get current rule
const currentRule = this.options.domainRules[ruleIndex];
// Create updated rule
const updatedRule: IDomainRule = {
...currentRule,
...updates
};
// Validate the updated rule
this.validateRule(updatedRule);
// Update the rule
this.options.domainRules[ruleIndex] = updatedRule;
// Clear cache since rules have changed
this.clearCache();
// Emit event
this.emit('ruleUpdated', updatedRule);
return true;
}
/**
* Remove a rule
* @param pattern Pattern to remove
* @returns True if rule was found and removed, false otherwise
*/
public removeRule(pattern: string): boolean {
const initialLength = this.options.domainRules.length;
this.options.domainRules = this.options.domainRules.filter(r => r.pattern !== pattern);
const removed = initialLength > this.options.domainRules.length;
if (removed) {
// Clear cache since rules have changed
this.clearCache();
// Emit event
this.emit('ruleRemoved', pattern);
}
return removed;
}
/**
* Get rule by pattern
* @param pattern Pattern to find
* @returns Rule with matching pattern or null if not found
*/
public getRule(pattern: string): IDomainRule | null {
return this.options.domainRules.find(r => r.pattern === pattern) || null;
}
/**
* Get all rules
* @returns Array of all domain rules
*/
public getRules(): IDomainRule[] {
return [...this.options.domainRules];
}
/**
* Update options
* @param options New options
*/
public updateOptions(options: Partial<IDomainRouterOptions>): void {
this.options = {
...this.options,
...options
};
// Clear cache if cache settings changed
if ('enableCache' in options || 'cacheSize' in options) {
this.clearCache();
}
// Emit event
this.emit('optionsUpdated', this.options);
}
/**
* Add an item to the pattern cache
* @param email Email address
* @param rule Matching rule or null
*/
private addToCache(email: string, rule: IDomainRule | null): void {
// If cache is disabled, do nothing
if (!this.options.enableCache) {
return;
}
// Add to cache
this.patternCache.set(email, rule);
// Check if cache size exceeds limit
if (this.patternCache.size > (this.options.cacheSize || 1000)) {
// Remove oldest entry (first in the Map)
const firstKey = this.patternCache.keys().next().value;
this.patternCache.delete(firstKey);
}
}
/**
* Clear pattern matching cache
*/
public clearCache(): void {
this.patternCache.clear();
this.emit('cacheCleared');
}
}

View File

@ -0,0 +1,129 @@
import * as plugins from '../plugins.js';
/**
* Email processing modes
*/
export type EmailProcessingMode = 'forward' | 'mta' | 'process';
/**
* Consolidated email configuration interface
*/
export interface IEmailConfig {
// Email server settings
ports: number[];
hostname: string;
maxMessageSize?: number;
// TLS configuration for email server
tls?: {
certPath?: string;
keyPath?: string;
caPath?: string;
minVersion?: string;
};
// Authentication for inbound connections
auth?: {
required?: boolean;
methods?: ('PLAIN' | 'LOGIN' | 'OAUTH2')[];
users?: Array<{username: string, password: string}>;
};
// Default routing for unmatched domains
defaultMode: EmailProcessingMode;
defaultServer?: string;
defaultPort?: number;
defaultTls?: boolean;
// Domain rules with glob pattern support
domainRules: IDomainRule[];
// Queue configuration for all email processing
queue?: {
storageType?: 'memory' | 'disk';
persistentPath?: string;
maxRetries?: number;
baseRetryDelay?: number;
maxRetryDelay?: number;
};
// Advanced MTA settings
mtaGlobalOptions?: IMtaOptions;
}
/**
* Domain rule interface for pattern-based routing
*/
export interface IDomainRule {
// Domain pattern (e.g., "*@example.com", "*@*.example.net")
pattern: string;
// Handling mode for this pattern
mode: EmailProcessingMode;
// Forward mode configuration
target?: {
server: string;
port?: number;
useTls?: boolean;
authentication?: {
user?: string;
pass?: string;
};
};
// MTA mode configuration
mtaOptions?: IMtaOptions;
// Process mode configuration
contentScanning?: boolean;
scanners?: IContentScanner[];
transformations?: ITransformation[];
// Rate limits for this domain
rateLimits?: {
maxMessagesPerMinute?: number;
maxRecipientsPerMessage?: number;
};
}
/**
* MTA options interface
*/
export interface IMtaOptions {
domain?: string;
allowLocalDelivery?: boolean;
localDeliveryPath?: string;
dkimSign?: boolean;
dkimOptions?: {
domainName: string;
keySelector: string;
privateKey: string;
};
smtpBanner?: string;
maxConnections?: number;
connTimeout?: number;
spoolDir?: string;
}
/**
* Content scanner interface
*/
export interface IContentScanner {
type: 'spam' | 'virus' | 'attachment';
threshold?: number;
action: 'tag' | 'reject';
blockedExtensions?: string[];
}
/**
* Transformation interface
*/
export interface ITransformation {
type: string;
header?: string;
value?: string;
domains?: string[];
append?: boolean;
[key: string]: any;
}

View File

@ -1,3 +1,8 @@
// Core DcRouter components
export * from './classes.dcrouter.js';
export * from './classes.smtp.portconfig.js';
export * from './classes.email.domainrouter.js';
// Unified Email Configuration
export * from './classes.email.config.js';
export * from './classes.domain.router.js';

View File

@ -3,6 +3,6 @@
*/
export const commitinfo = {
name: '@serve.zone/platformservice',
version: '2.5.0',
version: '2.7.0',
description: 'A multifaceted platform service handling mail, SMS, letter delivery, and AI services.'
}

Binary file not shown.