update
This commit is contained in:
122
readme.hints.md
122
readme.hints.md
@@ -453,3 +453,125 @@ External Port → SmartProxy → Internal Port → UnifiedEmailServer → Proces
|
|||||||
- Simplify DcRouter to just manage high-level services
|
- Simplify DcRouter to just manage high-level services
|
||||||
- Add connection pooling for better performance
|
- Add connection pooling for better performance
|
||||||
- See readme.plan.md for detailed implementation plan
|
- See readme.plan.md for detailed implementation plan
|
||||||
|
|
||||||
|
## SMTP Client Management (2025-05-27)
|
||||||
|
|
||||||
|
### Centralized SMTP Client in UnifiedEmailServer
|
||||||
|
- SMTP clients are now managed centrally in UnifiedEmailServer
|
||||||
|
- Uses connection pooling for efficiency (one pool per destination host:port)
|
||||||
|
- Classes using UnifiedEmailServer get SMTP clients via `getSmtpClient(host, port)`
|
||||||
|
|
||||||
|
### Implementation Details
|
||||||
|
```typescript
|
||||||
|
// In UnifiedEmailServer
|
||||||
|
private smtpClients: Map<string, SmtpClient> = new Map(); // host:port -> client
|
||||||
|
|
||||||
|
public getSmtpClient(host: string, port: number = 25): SmtpClient {
|
||||||
|
const clientKey = `${host}:${port}`;
|
||||||
|
let client = this.smtpClients.get(clientKey);
|
||||||
|
|
||||||
|
if (!client) {
|
||||||
|
client = createPooledSmtpClient({
|
||||||
|
host,
|
||||||
|
port,
|
||||||
|
secure: port === 465,
|
||||||
|
connectionTimeout: 30000,
|
||||||
|
socketTimeout: 120000,
|
||||||
|
maxConnections: 10,
|
||||||
|
maxMessages: 1000,
|
||||||
|
pool: true
|
||||||
|
});
|
||||||
|
this.smtpClients.set(clientKey, client);
|
||||||
|
}
|
||||||
|
|
||||||
|
return client;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Usage Pattern
|
||||||
|
- EmailSendJob and DeliverySystem now use `this.emailServerRef.getSmtpClient(host, port)`
|
||||||
|
- Connection pooling happens automatically
|
||||||
|
- Connections are reused across multiple send jobs
|
||||||
|
- All SMTP clients are closed when UnifiedEmailServer stops
|
||||||
|
|
||||||
|
### Dependency Injection Pattern
|
||||||
|
- Classes that need UnifiedEmailServer functionality receive it as constructor argument
|
||||||
|
- This provides access to SMTP clients, DKIM signing, and other shared functionality
|
||||||
|
- Example: `new EmailSendJob(emailServerRef, email, options)`
|
||||||
|
|
||||||
|
## Email Class Design Pattern (2025-05-27)
|
||||||
|
|
||||||
|
### Three-Interface Pattern for Email
|
||||||
|
The Email system uses three distinct interfaces for clarity and type safety:
|
||||||
|
|
||||||
|
1. **IEmailOptions** - The flexible input interface:
|
||||||
|
```typescript
|
||||||
|
interface IEmailOptions {
|
||||||
|
to: string | string[]; // Flexible: single or array
|
||||||
|
cc?: string | string[]; // Optional
|
||||||
|
attachments?: IAttachment[]; // Optional
|
||||||
|
skipAdvancedValidation?: boolean; // Constructor-only option
|
||||||
|
}
|
||||||
|
```
|
||||||
|
- Used as constructor parameter
|
||||||
|
- Allows flexible input formats
|
||||||
|
- Has constructor-only options (like skipAdvancedValidation)
|
||||||
|
|
||||||
|
2. **INormalizedEmail** - The normalized runtime interface:
|
||||||
|
```typescript
|
||||||
|
interface INormalizedEmail {
|
||||||
|
to: string[]; // Always an array
|
||||||
|
cc: string[]; // Always an array (empty if not provided)
|
||||||
|
attachments: IAttachment[]; // Always an array (empty if not provided)
|
||||||
|
mightBeSpam: boolean; // Always has a value (defaults to false)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
- Represents the guaranteed internal structure
|
||||||
|
- No optional arrays - everything has a default
|
||||||
|
- Email class implements this interface
|
||||||
|
|
||||||
|
3. **Email class** - The implementation:
|
||||||
|
```typescript
|
||||||
|
export class Email implements INormalizedEmail {
|
||||||
|
// All INormalizedEmail properties
|
||||||
|
to: string[];
|
||||||
|
cc: string[];
|
||||||
|
// ... etc
|
||||||
|
|
||||||
|
// Additional runtime properties
|
||||||
|
private messageId: string;
|
||||||
|
private envelopeFrom: string;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
- Implements INormalizedEmail
|
||||||
|
- Adds behavior methods and computed properties
|
||||||
|
- Handles validation and normalization
|
||||||
|
|
||||||
|
### Benefits of This Pattern:
|
||||||
|
- **Type Safety**: Email class explicitly implements INormalizedEmail
|
||||||
|
- **Clear Contracts**: Input vs. runtime structure is explicit
|
||||||
|
- **Flexibility**: IEmailOptions allows various input formats
|
||||||
|
- **Consistency**: INormalizedEmail guarantees structure
|
||||||
|
- **Validation**: Constructor validates and normalizes
|
||||||
|
|
||||||
|
### Usage:
|
||||||
|
```typescript
|
||||||
|
// Input with flexible options
|
||||||
|
const options: IEmailOptions = {
|
||||||
|
from: 'sender@example.com',
|
||||||
|
to: 'recipient@example.com', // Single string
|
||||||
|
subject: 'Hello',
|
||||||
|
text: 'World'
|
||||||
|
};
|
||||||
|
|
||||||
|
// Creates normalized Email instance
|
||||||
|
const email = new Email(options);
|
||||||
|
|
||||||
|
// email.to is guaranteed to be string[]
|
||||||
|
email.to.forEach(recipient => {
|
||||||
|
// No need to check if it's an array
|
||||||
|
});
|
||||||
|
|
||||||
|
// Convert back to options format
|
||||||
|
const optionsAgain = email.toEmailOptions();
|
||||||
|
```
|
@@ -25,7 +25,16 @@ export interface IEmailOptions {
|
|||||||
variables?: Record<string, any>; // Template variables for placeholder replacement
|
variables?: Record<string, any>; // Template variables for placeholder replacement
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Email class represents a complete email message.
|
||||||
|
*
|
||||||
|
* This class takes IEmailOptions in the constructor and normalizes the data:
|
||||||
|
* - 'to', 'cc', 'bcc' are always converted to arrays
|
||||||
|
* - Optional properties get default values
|
||||||
|
* - Additional properties like messageId and envelopeFrom are generated
|
||||||
|
*/
|
||||||
export class Email {
|
export class Email {
|
||||||
|
// INormalizedEmail properties
|
||||||
from: string;
|
from: string;
|
||||||
to: string[];
|
to: string[];
|
||||||
cc: string[];
|
cc: string[];
|
||||||
@@ -38,6 +47,8 @@ export class Email {
|
|||||||
mightBeSpam: boolean;
|
mightBeSpam: boolean;
|
||||||
priority: 'high' | 'normal' | 'low';
|
priority: 'high' | 'normal' | 'low';
|
||||||
variables: Record<string, any>;
|
variables: Record<string, any>;
|
||||||
|
|
||||||
|
// Additional Email-specific properties
|
||||||
private envelopeFrom: string;
|
private envelopeFrom: string;
|
||||||
private messageId: string;
|
private messageId: string;
|
||||||
|
|
||||||
@@ -637,6 +648,57 @@ export class Email {
|
|||||||
return this.messageId;
|
return this.messageId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert the Email instance back to IEmailOptions format.
|
||||||
|
* Useful for serialization or passing to APIs that expect IEmailOptions.
|
||||||
|
* Note: This loses some Email-specific properties like messageId and envelopeFrom.
|
||||||
|
*
|
||||||
|
* @returns IEmailOptions representation of this email
|
||||||
|
*/
|
||||||
|
public toEmailOptions(): IEmailOptions {
|
||||||
|
const options: IEmailOptions = {
|
||||||
|
from: this.from,
|
||||||
|
to: this.to.length === 1 ? this.to[0] : this.to,
|
||||||
|
subject: this.subject,
|
||||||
|
text: this.text
|
||||||
|
};
|
||||||
|
|
||||||
|
// Add optional properties only if they have values
|
||||||
|
if (this.cc && this.cc.length > 0) {
|
||||||
|
options.cc = this.cc.length === 1 ? this.cc[0] : this.cc;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.bcc && this.bcc.length > 0) {
|
||||||
|
options.bcc = this.bcc.length === 1 ? this.bcc[0] : this.bcc;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.html) {
|
||||||
|
options.html = this.html;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.attachments && this.attachments.length > 0) {
|
||||||
|
options.attachments = this.attachments;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.headers && Object.keys(this.headers).length > 0) {
|
||||||
|
options.headers = this.headers;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.mightBeSpam) {
|
||||||
|
options.mightBeSpam = this.mightBeSpam;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.priority !== 'normal') {
|
||||||
|
options.priority = this.priority;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.variables && Object.keys(this.variables).length > 0) {
|
||||||
|
options.variables = this.variables;
|
||||||
|
}
|
||||||
|
|
||||||
|
return options;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set a custom message ID
|
* Set a custom message ID
|
||||||
* @param id The message ID to set
|
* @param id The message ID to set
|
||||||
|
@@ -455,7 +455,7 @@ export class MultiModeDeliverySystem extends EventEmitter {
|
|||||||
return this.handleForwardDeliveryLegacy(item);
|
return this.handleForwardDeliveryLegacy(item);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get or create SMTP client for the target server
|
// Get SMTP client from UnifiedEmailServer
|
||||||
const smtpClient = this.emailServer.getSmtpClient(targetServer, targetPort);
|
const smtpClient = this.emailServer.getSmtpClient(targetServer, targetPort);
|
||||||
|
|
||||||
// Send the email using SmtpClient
|
// Send the email using SmtpClient
|
||||||
|
@@ -22,16 +22,15 @@ import type {
|
|||||||
} from './classes.email.config.js';
|
} from './classes.email.config.js';
|
||||||
import { Email } from '../core/classes.email.js';
|
import { Email } from '../core/classes.email.js';
|
||||||
import { BounceManager, BounceType, BounceCategory } from '../core/classes.bouncemanager.js';
|
import { BounceManager, BounceType, BounceCategory } from '../core/classes.bouncemanager.js';
|
||||||
import * as net from 'node:net';
|
|
||||||
import * as tls from 'node:tls';
|
import * as tls from 'node:tls';
|
||||||
import * as stream from 'node:stream';
|
|
||||||
import { createSmtpServer } from '../delivery/smtpserver/index.js';
|
import { createSmtpServer } from '../delivery/smtpserver/index.js';
|
||||||
|
import { createPooledSmtpClient } from '../delivery/smtpclient/create-client.js';
|
||||||
|
import type { SmtpClient } from '../delivery/smtpclient/smtp-client.js';
|
||||||
import { MultiModeDeliverySystem, type IMultiModeDeliveryOptions } from '../delivery/classes.delivery.system.js';
|
import { MultiModeDeliverySystem, type IMultiModeDeliveryOptions } from '../delivery/classes.delivery.system.js';
|
||||||
import { UnifiedDeliveryQueue, type IQueueOptions } from '../delivery/classes.delivery.queue.js';
|
import { UnifiedDeliveryQueue, type IQueueOptions } from '../delivery/classes.delivery.queue.js';
|
||||||
import { UnifiedRateLimiter, type IHierarchicalRateLimits } from '../delivery/classes.unified.rate.limiter.js';
|
import { UnifiedRateLimiter, type IHierarchicalRateLimits } from '../delivery/classes.unified.rate.limiter.js';
|
||||||
import { SmtpState } from '../delivery/interfaces.js';
|
import { SmtpState } from '../delivery/interfaces.js';
|
||||||
import type { EmailProcessingMode, ISmtpSession as IBaseSmtpSession } from '../delivery/interfaces.js';
|
import type { EmailProcessingMode, ISmtpSession as IBaseSmtpSession } from '../delivery/interfaces.js';
|
||||||
import { smtpClientMod } from '../delivery/index.js';
|
|
||||||
import type { DcRouter } from '../../classes.dcrouter.js';
|
import type { DcRouter } from '../../classes.dcrouter.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -180,6 +179,7 @@ export class UnifiedEmailServer extends EventEmitter {
|
|||||||
public deliverySystem: MultiModeDeliverySystem;
|
public deliverySystem: MultiModeDeliverySystem;
|
||||||
private rateLimiter: UnifiedRateLimiter;
|
private rateLimiter: UnifiedRateLimiter;
|
||||||
private dkimKeys: Map<string, string> = new Map(); // domain -> private key
|
private dkimKeys: Map<string, string> = new Map(); // domain -> private key
|
||||||
|
private smtpClients: Map<string, SmtpClient> = new Map(); // host:port -> client
|
||||||
|
|
||||||
constructor(dcRouter: DcRouter, options: IUnifiedEmailServerOptions) {
|
constructor(dcRouter: DcRouter, options: IUnifiedEmailServerOptions) {
|
||||||
super();
|
super();
|
||||||
@@ -303,6 +303,37 @@ export class UnifiedEmailServer extends EventEmitter {
|
|||||||
// We'll create the SMTP servers during the start() method
|
// We'll create the SMTP servers during the start() method
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get or create an SMTP client for the given host and port
|
||||||
|
* Uses connection pooling for efficiency
|
||||||
|
*/
|
||||||
|
public getSmtpClient(host: string, port: number = 25): SmtpClient {
|
||||||
|
const clientKey = `${host}:${port}`;
|
||||||
|
|
||||||
|
// Check if we already have a client for this destination
|
||||||
|
let client = this.smtpClients.get(clientKey);
|
||||||
|
|
||||||
|
if (!client) {
|
||||||
|
// Create a new pooled SMTP client
|
||||||
|
client = createPooledSmtpClient({
|
||||||
|
host,
|
||||||
|
port,
|
||||||
|
secure: port === 465,
|
||||||
|
connectionTimeout: this.options.outbound?.connectionTimeout || 30000,
|
||||||
|
socketTimeout: this.options.outbound?.socketTimeout || 120000,
|
||||||
|
maxConnections: this.options.outbound?.maxConnections || 10,
|
||||||
|
maxMessages: 1000, // Messages per connection before reconnect
|
||||||
|
pool: true,
|
||||||
|
debug: false
|
||||||
|
});
|
||||||
|
|
||||||
|
this.smtpClients.set(clientKey, client);
|
||||||
|
logger.log('info', `Created new SMTP client pool for ${clientKey}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return client;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Start the unified email server
|
* Start the unified email server
|
||||||
*/
|
*/
|
||||||
@@ -469,6 +500,17 @@ export class UnifiedEmailServer extends EventEmitter {
|
|||||||
logger.log('info', 'Email delivery queue shut down');
|
logger.log('info', 'Email delivery queue shut down');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Close all SMTP client connections
|
||||||
|
for (const [clientKey, client] of this.smtpClients) {
|
||||||
|
try {
|
||||||
|
await client.close();
|
||||||
|
logger.log('info', `Closed SMTP client pool for ${clientKey}`);
|
||||||
|
} catch (error) {
|
||||||
|
logger.log('warn', `Error closing SMTP client for ${clientKey}: ${error.message}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.smtpClients.clear();
|
||||||
|
|
||||||
logger.log('info', 'UnifiedEmailServer stopped successfully');
|
logger.log('info', 'UnifiedEmailServer stopped successfully');
|
||||||
this.emit('stopped');
|
this.emit('stopped');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -480,89 +522,6 @@ export class UnifiedEmailServer extends EventEmitter {
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handle incoming email data (stub implementation)
|
|
||||||
*/
|
|
||||||
private onData(stream: stream.Readable, session: IExtendedSmtpSession, callback: (err?: Error) => void): void {
|
|
||||||
logger.log('info', `Processing email data for session ${session.id}`);
|
|
||||||
|
|
||||||
const startTime = Date.now();
|
|
||||||
const chunks: Buffer[] = [];
|
|
||||||
|
|
||||||
stream.on('data', (chunk: Buffer) => {
|
|
||||||
chunks.push(chunk);
|
|
||||||
});
|
|
||||||
|
|
||||||
stream.on('end', async () => {
|
|
||||||
try {
|
|
||||||
const data = Buffer.concat(chunks);
|
|
||||||
const mode = session.processingMode || this.options.defaultMode;
|
|
||||||
|
|
||||||
// Determine processing mode based on matched rule
|
|
||||||
const processedEmail = await this.processEmailByMode(data, session, mode);
|
|
||||||
|
|
||||||
// Update statistics
|
|
||||||
this.stats.messages.processed++;
|
|
||||||
this.stats.messages.delivered++;
|
|
||||||
|
|
||||||
// Calculate processing time
|
|
||||||
const processingTime = Date.now() - startTime;
|
|
||||||
this.processingTimes.push(processingTime);
|
|
||||||
this.updateProcessingTimeStats();
|
|
||||||
|
|
||||||
// Emit event for delivery queue
|
|
||||||
this.emit('emailProcessed', processedEmail, mode, session.matchedRule);
|
|
||||||
|
|
||||||
logger.log('info', `Email processed successfully in ${processingTime}ms, mode: ${mode}`);
|
|
||||||
callback();
|
|
||||||
} catch (error) {
|
|
||||||
logger.log('error', `Error processing email: ${error.message}`);
|
|
||||||
|
|
||||||
// Update statistics
|
|
||||||
this.stats.messages.processed++;
|
|
||||||
this.stats.messages.failed++;
|
|
||||||
|
|
||||||
// Calculate processing time for failed attempts too
|
|
||||||
const processingTime = Date.now() - startTime;
|
|
||||||
this.processingTimes.push(processingTime);
|
|
||||||
this.updateProcessingTimeStats();
|
|
||||||
|
|
||||||
SecurityLogger.getInstance().logEvent({
|
|
||||||
level: SecurityLogLevel.ERROR,
|
|
||||||
type: SecurityEventType.EMAIL_PROCESSING,
|
|
||||||
message: 'Email processing failed',
|
|
||||||
ipAddress: session.remoteAddress,
|
|
||||||
details: {
|
|
||||||
error: error.message,
|
|
||||||
sessionId: session.id,
|
|
||||||
mode: session.processingMode,
|
|
||||||
processingTime
|
|
||||||
},
|
|
||||||
success: false
|
|
||||||
});
|
|
||||||
|
|
||||||
callback(error);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
stream.on('error', (err) => {
|
|
||||||
logger.log('error', `Stream error: ${err.message}`);
|
|
||||||
|
|
||||||
SecurityLogger.getInstance().logEvent({
|
|
||||||
level: SecurityLogLevel.ERROR,
|
|
||||||
type: SecurityEventType.EMAIL_PROCESSING,
|
|
||||||
message: 'Email stream error',
|
|
||||||
ipAddress: session.remoteAddress,
|
|
||||||
details: {
|
|
||||||
error: err.message,
|
|
||||||
sessionId: session.id
|
|
||||||
},
|
|
||||||
success: false
|
|
||||||
});
|
|
||||||
|
|
||||||
callback(err);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Update processing time statistics
|
* Update processing time statistics
|
||||||
@@ -636,8 +595,7 @@ export class UnifiedEmailServer extends EventEmitter {
|
|||||||
// Process based on mode
|
// Process based on mode
|
||||||
switch (mode) {
|
switch (mode) {
|
||||||
case 'forward':
|
case 'forward':
|
||||||
await this.handleForwardMode(email, session);
|
throw new Error('Forward mode is not implemented');
|
||||||
break;
|
|
||||||
|
|
||||||
case 'mta':
|
case 'mta':
|
||||||
await this.handleMtaMode(email, session);
|
await this.handleMtaMode(email, session);
|
||||||
@@ -655,99 +613,6 @@ export class UnifiedEmailServer extends EventEmitter {
|
|||||||
return email;
|
return email;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Handle email in forward mode (SMTP proxy)
|
|
||||||
*/
|
|
||||||
private async handleForwardMode(email: Email, session: IExtendedSmtpSession): Promise<void> {
|
|
||||||
logger.log('info', `Handling email in forward mode for session ${session.id}`);
|
|
||||||
|
|
||||||
// Get target server information
|
|
||||||
const rule = session.matchedRule;
|
|
||||||
const targetServer = rule?.target?.server || this.options.defaultServer;
|
|
||||||
const targetPort = rule?.target?.port || this.options.defaultPort || 25;
|
|
||||||
const useTls = rule?.target?.useTls ?? this.options.defaultTls ?? false;
|
|
||||||
|
|
||||||
if (!targetServer) {
|
|
||||||
throw new Error('No target server configured for forward mode');
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.log('info', `Forwarding email to ${targetServer}:${targetPort}, TLS: ${useTls}`);
|
|
||||||
|
|
||||||
try {
|
|
||||||
// Create a simple SMTP client connection to the target server
|
|
||||||
const client = new net.Socket();
|
|
||||||
|
|
||||||
await new Promise<void>((resolve, reject) => {
|
|
||||||
// Connect to the target server
|
|
||||||
client.connect({
|
|
||||||
host: targetServer,
|
|
||||||
port: targetPort
|
|
||||||
});
|
|
||||||
|
|
||||||
client.on('data', (data) => {
|
|
||||||
const response = data.toString().trim();
|
|
||||||
logger.log('debug', `SMTP response: ${response}`);
|
|
||||||
|
|
||||||
// Handle SMTP response codes
|
|
||||||
if (response.startsWith('2')) {
|
|
||||||
// Success response
|
|
||||||
resolve();
|
|
||||||
} else if (response.startsWith('5')) {
|
|
||||||
// Permanent error
|
|
||||||
reject(new Error(`SMTP error: ${response}`));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
client.on('error', (err) => {
|
|
||||||
logger.log('error', `SMTP client error: ${err.message}`);
|
|
||||||
reject(err);
|
|
||||||
});
|
|
||||||
|
|
||||||
// SMTP client commands would go here in a full implementation
|
|
||||||
// For now, just finish the connection
|
|
||||||
client.end();
|
|
||||||
resolve();
|
|
||||||
});
|
|
||||||
|
|
||||||
logger.log('info', `Email forwarded successfully to ${targetServer}:${targetPort}`);
|
|
||||||
|
|
||||||
SecurityLogger.getInstance().logEvent({
|
|
||||||
level: SecurityLogLevel.INFO,
|
|
||||||
type: SecurityEventType.EMAIL_FORWARDING,
|
|
||||||
message: 'Email forwarded',
|
|
||||||
ipAddress: session.remoteAddress,
|
|
||||||
details: {
|
|
||||||
sessionId: session.id,
|
|
||||||
targetServer,
|
|
||||||
targetPort,
|
|
||||||
useTls,
|
|
||||||
ruleName: rule?.pattern || 'default',
|
|
||||||
subject: email.subject
|
|
||||||
},
|
|
||||||
success: true
|
|
||||||
});
|
|
||||||
} catch (error) {
|
|
||||||
logger.log('error', `Failed to forward email: ${error.message}`);
|
|
||||||
|
|
||||||
SecurityLogger.getInstance().logEvent({
|
|
||||||
level: SecurityLogLevel.ERROR,
|
|
||||||
type: SecurityEventType.EMAIL_FORWARDING,
|
|
||||||
message: 'Email forwarding failed',
|
|
||||||
ipAddress: session.remoteAddress,
|
|
||||||
details: {
|
|
||||||
sessionId: session.id,
|
|
||||||
targetServer,
|
|
||||||
targetPort,
|
|
||||||
useTls,
|
|
||||||
ruleName: rule?.pattern || 'default',
|
|
||||||
error: error.message
|
|
||||||
},
|
|
||||||
success: false
|
|
||||||
});
|
|
||||||
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handle email in MTA mode (programmatic processing)
|
* Handle email in MTA mode (programmatic processing)
|
||||||
@@ -948,24 +813,7 @@ export class UnifiedEmailServer extends EventEmitter {
|
|||||||
return filename.substring(filename.lastIndexOf('.')).toLowerCase();
|
return filename.substring(filename.lastIndexOf('.')).toLowerCase();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Handle server errors
|
|
||||||
*/
|
|
||||||
private onError(err: Error): void {
|
|
||||||
logger.log('error', `Server error: ${err.message}`);
|
|
||||||
this.emit('error', err);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handle server close
|
|
||||||
*/
|
|
||||||
private onClose(): void {
|
|
||||||
logger.log('info', 'Server closed');
|
|
||||||
this.emit('close');
|
|
||||||
|
|
||||||
// Update statistics
|
|
||||||
this.stats.connections.current = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set up automatic DKIM configuration with DNS server
|
* Set up automatic DKIM configuration with DNS server
|
||||||
@@ -1025,25 +873,6 @@ export class UnifiedEmailServer extends EventEmitter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Get SMTP client for a specific destination
|
|
||||||
*/
|
|
||||||
public getSmtpClient(host: string, port: number): smtpClientMod.SmtpClient {
|
|
||||||
const key = `${host}:${port}`;
|
|
||||||
|
|
||||||
if (!this.smtpClients.has(key)) {
|
|
||||||
// Create a new client for this destination
|
|
||||||
const client = smtpClientMod.createSmtpClient({
|
|
||||||
...this.smtpClientConfig,
|
|
||||||
host,
|
|
||||||
port
|
|
||||||
});
|
|
||||||
|
|
||||||
this.smtpClients.set(key, client);
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.smtpClients.get(key)!;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generate SmartProxy routes for email ports
|
* Generate SmartProxy routes for email ports
|
||||||
|
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user