This commit is contained in:
2025-05-27 15:06:44 +00:00
parent 073c8378c7
commit cfea44742a
5 changed files with 232 additions and 1942 deletions

View File

@@ -452,4 +452,126 @@ External Port → SmartProxy → Internal Port → UnifiedEmailServer → Proces
- 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
- 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();
```