feat(plan): simplify structure
This commit is contained in:
@@ -423,4 +423,33 @@ tap.start();
|
|||||||
- **Connection Reuse**: Don't expect reuse to always be faster than fresh connections
|
- **Connection Reuse**: Don't expect reuse to always be faster than fresh connections
|
||||||
- **Pooled Clients**: Remove usage - tests expect direct client behavior
|
- **Pooled Clients**: Remove usage - tests expect direct client behavior
|
||||||
- **Port Conflicts**: Use different ports for each test to avoid conflicts
|
- **Port Conflicts**: Use different ports for each test to avoid conflicts
|
||||||
- **Resource Cleanup**: Simplified tests that were too complex and timing-dependent
|
- **Resource Cleanup**: Simplified tests that were too complex and timing-dependent
|
||||||
|
|
||||||
|
## Email Architecture Analysis (2025-05-27)
|
||||||
|
|
||||||
|
### Current Architecture Issues
|
||||||
|
1. **Scattered Components**: Email functionality spread across multiple DcRouter properties
|
||||||
|
- `domainRouter`, `unifiedEmailServer`, `deliveryQueue`, `deliverySystem`, `rateLimiter`
|
||||||
|
2. **Duplicate SMTP Implementations**:
|
||||||
|
- EmailSendJob implements raw socket SMTP protocol
|
||||||
|
- SmtpClient module exists but isn't used for outbound delivery
|
||||||
|
3. **Complex Setup**: setupUnifiedEmailHandling() is 150+ lines
|
||||||
|
4. **No Connection Pooling**: Each outbound email creates new connection
|
||||||
|
5. **Orphaned Code**: SmtpPortConfig class exists but is never used
|
||||||
|
|
||||||
|
### Email Traffic Flow
|
||||||
|
```
|
||||||
|
External Port → SmartProxy → Internal Port → UnifiedEmailServer → Processing
|
||||||
|
25 ↓ 10025 ↓ ↓
|
||||||
|
587 Routes 10587 DomainRouter DeliverySystem
|
||||||
|
465 10465 ↓
|
||||||
|
Queue → SmtpClient*
|
||||||
|
(*should be used)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Planned Refactoring
|
||||||
|
- Consolidate all email components under UnifiedEmailServer
|
||||||
|
- Use single SmtpClient instance for all outbound mail
|
||||||
|
- Simplify DcRouter to just manage high-level services
|
||||||
|
- Add connection pooling for better performance
|
||||||
|
- See readme.plan.md for detailed implementation plan
|
BIN
readme.plan.md
BIN
readme.plan.md
Binary file not shown.
@@ -1,285 +0,0 @@
|
|||||||
import * as plugins from './plugins.js';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Configuration options for TLS in SMTP connections
|
|
||||||
*/
|
|
||||||
export interface ISmtpTlsOptions {
|
|
||||||
/** Enable TLS for this SMTP port */
|
|
||||||
enabled: boolean;
|
|
||||||
/** Whether to use STARTTLS (upgrade plain connection) or implicit TLS */
|
|
||||||
useStartTls?: boolean;
|
|
||||||
/** Required TLS protocol version (defaults to TLSv1.2) */
|
|
||||||
minTlsVersion?: 'TLSv1.0' | 'TLSv1.1' | 'TLSv1.2' | 'TLSv1.3';
|
|
||||||
/** TLS ciphers to allow (comma-separated list) */
|
|
||||||
allowedCiphers?: string;
|
|
||||||
/** Whether to require client certificate for authentication */
|
|
||||||
requireClientCert?: boolean;
|
|
||||||
/** Whether to verify client certificate if provided */
|
|
||||||
verifyClientCert?: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Rate limiting options for SMTP connections
|
|
||||||
*/
|
|
||||||
export interface ISmtpRateLimitOptions {
|
|
||||||
/** Maximum connections per minute from a single IP */
|
|
||||||
maxConnectionsPerMinute?: number;
|
|
||||||
/** Maximum concurrent connections from a single IP */
|
|
||||||
maxConcurrentConnections?: number;
|
|
||||||
/** Maximum emails per minute from a single IP */
|
|
||||||
maxEmailsPerMinute?: number;
|
|
||||||
/** Maximum recipients per email */
|
|
||||||
maxRecipientsPerEmail?: number;
|
|
||||||
/** Maximum email size in bytes */
|
|
||||||
maxEmailSize?: number;
|
|
||||||
/** Action to take when rate limit is exceeded (default: 'tempfail') */
|
|
||||||
rateLimitAction?: 'tempfail' | 'drop' | 'delay';
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Configuration for a specific SMTP port
|
|
||||||
*/
|
|
||||||
export interface ISmtpPortSettings {
|
|
||||||
/** The port number to listen on */
|
|
||||||
port: number;
|
|
||||||
/** Whether this port is enabled */
|
|
||||||
enabled?: boolean;
|
|
||||||
/** Port description (e.g., "Submission Port") */
|
|
||||||
description?: string;
|
|
||||||
/** Whether to require authentication for this port */
|
|
||||||
requireAuth?: boolean;
|
|
||||||
/** TLS options for this port */
|
|
||||||
tls?: ISmtpTlsOptions;
|
|
||||||
/** Rate limiting settings for this port */
|
|
||||||
rateLimit?: ISmtpRateLimitOptions;
|
|
||||||
/** Maximum message size in bytes for this port */
|
|
||||||
maxMessageSize?: number;
|
|
||||||
/** Whether to enable SMTP extensions like PIPELINING, 8BITMIME, etc. */
|
|
||||||
smtpExtensions?: {
|
|
||||||
/** Enable PIPELINING extension */
|
|
||||||
pipelining?: boolean;
|
|
||||||
/** Enable 8BITMIME extension */
|
|
||||||
eightBitMime?: boolean;
|
|
||||||
/** Enable SIZE extension */
|
|
||||||
size?: boolean;
|
|
||||||
/** Enable ENHANCEDSTATUSCODES extension */
|
|
||||||
enhancedStatusCodes?: boolean;
|
|
||||||
/** Enable DSN extension */
|
|
||||||
dsn?: boolean;
|
|
||||||
};
|
|
||||||
/** Custom SMTP greeting banner */
|
|
||||||
banner?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Configuration manager for SMTP ports
|
|
||||||
*/
|
|
||||||
export class SmtpPortConfig {
|
|
||||||
/** Port configurations */
|
|
||||||
private portConfigs: Map<number, ISmtpPortSettings> = new Map();
|
|
||||||
|
|
||||||
/** Default port configurations */
|
|
||||||
private static readonly DEFAULT_CONFIGS: Record<number, Partial<ISmtpPortSettings>> = {
|
|
||||||
// Port 25: Standard SMTP (modified from port 25)
|
|
||||||
25: {
|
|
||||||
description: 'Standard SMTP',
|
|
||||||
requireAuth: false,
|
|
||||||
tls: {
|
|
||||||
enabled: true,
|
|
||||||
useStartTls: true,
|
|
||||||
minTlsVersion: 'TLSv1.2'
|
|
||||||
},
|
|
||||||
rateLimit: {
|
|
||||||
maxConnectionsPerMinute: 60,
|
|
||||||
maxConcurrentConnections: 10,
|
|
||||||
maxEmailsPerMinute: 30
|
|
||||||
},
|
|
||||||
maxMessageSize: 20 * 1024 * 1024 // 20MB
|
|
||||||
},
|
|
||||||
// Port 587: Submission
|
|
||||||
587: {
|
|
||||||
description: 'Submission Port',
|
|
||||||
requireAuth: true,
|
|
||||||
tls: {
|
|
||||||
enabled: true,
|
|
||||||
useStartTls: true,
|
|
||||||
minTlsVersion: 'TLSv1.2'
|
|
||||||
},
|
|
||||||
rateLimit: {
|
|
||||||
maxConnectionsPerMinute: 100,
|
|
||||||
maxConcurrentConnections: 20,
|
|
||||||
maxEmailsPerMinute: 60
|
|
||||||
},
|
|
||||||
maxMessageSize: 50 * 1024 * 1024 // 50MB
|
|
||||||
},
|
|
||||||
// Port 465: SMTPS (Legacy Implicit TLS)
|
|
||||||
465: {
|
|
||||||
description: 'SMTPS (Implicit TLS)',
|
|
||||||
requireAuth: true,
|
|
||||||
tls: {
|
|
||||||
enabled: true,
|
|
||||||
useStartTls: false,
|
|
||||||
minTlsVersion: 'TLSv1.2'
|
|
||||||
},
|
|
||||||
rateLimit: {
|
|
||||||
maxConnectionsPerMinute: 100,
|
|
||||||
maxConcurrentConnections: 20,
|
|
||||||
maxEmailsPerMinute: 60
|
|
||||||
},
|
|
||||||
maxMessageSize: 50 * 1024 * 1024 // 50MB
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a new SmtpPortConfig
|
|
||||||
* @param initialConfigs Optional initial port configurations
|
|
||||||
*/
|
|
||||||
constructor(initialConfigs?: ISmtpPortSettings[]) {
|
|
||||||
// Initialize with default configurations for standard SMTP ports
|
|
||||||
this.initializeDefaults();
|
|
||||||
|
|
||||||
// Apply custom configurations if provided
|
|
||||||
if (initialConfigs) {
|
|
||||||
for (const config of initialConfigs) {
|
|
||||||
this.setPortConfig(config);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Initialize port configurations with defaults
|
|
||||||
*/
|
|
||||||
private initializeDefaults(): void {
|
|
||||||
// Set up default configurations for standard SMTP ports: 25, 587, 465
|
|
||||||
Object.entries(SmtpPortConfig.DEFAULT_CONFIGS).forEach(([portStr, defaults]) => {
|
|
||||||
const port = parseInt(portStr, 10);
|
|
||||||
this.portConfigs.set(port, {
|
|
||||||
port,
|
|
||||||
enabled: true,
|
|
||||||
...defaults
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get configuration for a specific port
|
|
||||||
* @param port Port number
|
|
||||||
* @returns Port configuration or null if not found
|
|
||||||
*/
|
|
||||||
public getPortConfig(port: number): ISmtpPortSettings | null {
|
|
||||||
return this.portConfigs.get(port) || null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get all configured ports
|
|
||||||
* @returns Array of port configurations
|
|
||||||
*/
|
|
||||||
public getAllPortConfigs(): ISmtpPortSettings[] {
|
|
||||||
return Array.from(this.portConfigs.values());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get only enabled port configurations
|
|
||||||
* @returns Array of enabled port configurations
|
|
||||||
*/
|
|
||||||
public getEnabledPortConfigs(): ISmtpPortSettings[] {
|
|
||||||
return this.getAllPortConfigs().filter(config => config.enabled !== false);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set configuration for a specific port
|
|
||||||
* @param config Port configuration
|
|
||||||
*/
|
|
||||||
public setPortConfig(config: ISmtpPortSettings): void {
|
|
||||||
// Get existing config if any
|
|
||||||
const existingConfig = this.portConfigs.get(config.port) || { port: config.port };
|
|
||||||
|
|
||||||
// Merge with new configuration
|
|
||||||
this.portConfigs.set(config.port, {
|
|
||||||
...existingConfig,
|
|
||||||
...config
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Remove configuration for a specific port
|
|
||||||
* @param port Port number
|
|
||||||
* @returns Whether the configuration was removed
|
|
||||||
*/
|
|
||||||
public removePortConfig(port: number): boolean {
|
|
||||||
return this.portConfigs.delete(port);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Disable a specific port
|
|
||||||
* @param port Port number
|
|
||||||
* @returns Whether the port was disabled
|
|
||||||
*/
|
|
||||||
public disablePort(port: number): boolean {
|
|
||||||
const config = this.portConfigs.get(port);
|
|
||||||
if (config) {
|
|
||||||
config.enabled = false;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Enable a specific port
|
|
||||||
* @param port Port number
|
|
||||||
* @returns Whether the port was enabled
|
|
||||||
*/
|
|
||||||
public enablePort(port: number): boolean {
|
|
||||||
const config = this.portConfigs.get(port);
|
|
||||||
if (config) {
|
|
||||||
config.enabled = true;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Convert port configurations to SmartProxy routes
|
|
||||||
* @returns Array of SmartProxy routes
|
|
||||||
*/
|
|
||||||
public toSmartProxyRoutes(): plugins.smartproxy.IRouteConfig[] {
|
|
||||||
const enabledPorts = this.getEnabledPortConfigs();
|
|
||||||
const routes: plugins.smartproxy.IRouteConfig[] = [];
|
|
||||||
|
|
||||||
// Add configured ports as routes
|
|
||||||
for (const portConfig of enabledPorts) {
|
|
||||||
// Create a route for each SMTP port
|
|
||||||
const route: plugins.smartproxy.IRouteConfig = {
|
|
||||||
name: `smtp-port-${portConfig.port}`,
|
|
||||||
match: {
|
|
||||||
ports: [portConfig.port]
|
|
||||||
},
|
|
||||||
action: {
|
|
||||||
type: 'forward',
|
|
||||||
target: {
|
|
||||||
host: 'localhost',
|
|
||||||
port: portConfig.port === 29200 ? 39200 : portConfig.port + 10000 // Map to internal port (29200 -> 39200, 587 -> 10587, etc.)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Apply TLS settings
|
|
||||||
if (portConfig.port === 465 && portConfig.tls?.enabled) {
|
|
||||||
// For implicit TLS on port 465
|
|
||||||
route.action.tls = {
|
|
||||||
mode: 'terminate',
|
|
||||||
certificate: 'auto'
|
|
||||||
};
|
|
||||||
} else if (portConfig.tls?.useStartTls) {
|
|
||||||
// For STARTTLS on ports 25 and 587
|
|
||||||
route.action.tls = {
|
|
||||||
mode: 'passthrough'
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
routes.push(route);
|
|
||||||
}
|
|
||||||
|
|
||||||
return routes;
|
|
||||||
}
|
|
||||||
}
|
|
Reference in New Issue
Block a user