feat: Refactor SMTP client to support session-based email processing, enhancing queue management and error handling
This commit is contained in:
parent
c2aa9b6cf6
commit
069540e310
7 changed files with 951 additions and 480 deletions
719
README.md
719
README.md
|
@ -1,122 +1,232 @@
|
|||
# @siwatsystem/mxrelay-consumer
|
||||
# @siwats/mxrelay-consumer
|
||||
|
||||
[](http://www.typescriptlang.org/)
|
||||
[](https://opensource.org/licenses/MIT)
|
||||
|
||||
A production-ready TypeScript client library for SMTP over WebSocket protocol with intelligent queue management, automatic reconnection, and comprehensive error handling. Includes full Nodemailer transport compatibility.
|
||||
A production-ready TypeScript client library for SMTP over WebSocket protocol with **session-based queue management**, automatic reconnection, and comprehensive error handling. Features proper SMTP session protocol implementation with mutex-based session locking.
|
||||
|
||||
## Features
|
||||
## 🚀 Key Features
|
||||
|
||||
**Intelligent Queue Management**
|
||||
- Automatic WebSocket connection when messages are queued
|
||||
- Priority-based message processing (CRITICAL > HIGH > NORMAL > LOW)
|
||||
- Configurable queue limits and overflow protection
|
||||
- Auto-disconnect when queue is empty
|
||||
### **Session-Based SMTP Protocol**
|
||||
- **Proper SMTP Sessions**: One session = EHLO → AUTH → Multiple Emails → QUIT
|
||||
- **Session Mutex**: Only one SMTP session active at a time (protocol compliant)
|
||||
- **Email Batching**: Send multiple emails efficiently within a single session
|
||||
- **Resource Optimization**: Connect only when needed, disconnect when idle
|
||||
|
||||
**Robust Connection Handling**
|
||||
- Automatic reconnection with exponential backoff
|
||||
- Connection state management and lifecycle
|
||||
- Heartbeat monitoring and timeout handling
|
||||
- Graceful connection recovery
|
||||
### **Intelligent Queue Management**
|
||||
- **Session Queuing**: Queues complete email sessions, not individual commands
|
||||
- **Priority-Based Processing**: CRITICAL > HIGH > NORMAL > LOW priority levels
|
||||
- **Configurable Batching**: Control emails per session with `maxEmailsPerSession`
|
||||
- **Smart Resource Usage**: Auto-connect/disconnect based on queue state
|
||||
|
||||
**High Performance**
|
||||
- Efficient SMTP channel cycling per message
|
||||
- Minimal resource usage with smart connection management
|
||||
- Concurrent message processing support
|
||||
- Optimized WebSocket communication
|
||||
### **Production-Ready Reliability**
|
||||
- **Comprehensive Error Handling**: Structured error classification and meaningful messages
|
||||
- **Automatic Reconnection**: Exponential backoff with configurable retry limits
|
||||
- **Timeout Management**: Session, email, and connection-level timeouts
|
||||
- **Connection State Management**: Full lifecycle state tracking
|
||||
|
||||
**Enterprise-Grade Reliability**
|
||||
- Comprehensive SMTP error handling with meaningful messages
|
||||
- Timeout management for all operations
|
||||
- Retry logic with configurable attempts
|
||||
- Structured error classification
|
||||
### **Nodemailer Integration**
|
||||
- **Full Nodemailer Compatibility**: Drop-in replacement for existing transports
|
||||
- **Standard API**: Use familiar Nodemailer methods and options
|
||||
- **Advanced Features**: Attachments, HTML, multipart messages, custom headers
|
||||
|
||||
**Nodemailer Integration**
|
||||
- Full Nodemailer transport compatibility
|
||||
- Transparent bridge for all email features
|
||||
- Support for attachments, HTML, multipart messages
|
||||
- Standard Nodemailer API compatibility
|
||||
## 📊 Architecture Overview
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
npm install @siwatsystem/mxrelay-consumer
|
||||
# or
|
||||
bun add @siwatsystem/mxrelay-consumer
|
||||
```mermaid
|
||||
graph TB
|
||||
subgraph "Client Application"
|
||||
App[Your Application]
|
||||
NM[Nodemailer]
|
||||
Transport[SMTPWSTransport]
|
||||
end
|
||||
|
||||
subgraph "Session Management"
|
||||
Client[SMTPOverWSClient]
|
||||
Queue[Session Queue]
|
||||
SM[Session Manager]
|
||||
end
|
||||
|
||||
subgraph "Protocol Layer"
|
||||
WS[WebSocket Connection]
|
||||
Auth[Authentication]
|
||||
Channel[SMTP Channel]
|
||||
end
|
||||
|
||||
subgraph "Remote Server"
|
||||
Relay[SMTP WebSocket Relay]
|
||||
SMTP[SMTP Server]
|
||||
end
|
||||
|
||||
App --> NM
|
||||
NM --> Transport
|
||||
Transport --> Client
|
||||
Client --> Queue
|
||||
Queue --> SM
|
||||
SM --> WS
|
||||
WS --> Auth
|
||||
Auth --> Channel
|
||||
Channel --> Relay
|
||||
Relay --> SMTP
|
||||
|
||||
style Client fill:#e1f5fe
|
||||
style Queue fill:#f3e5f5
|
||||
style SM fill:#fff3e0
|
||||
style Channel fill:#e8f5e8
|
||||
```
|
||||
|
||||
## Quick Start
|
||||
## 🔄 Session Processing Flow
|
||||
|
||||
### Direct Client Usage
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant App as Application
|
||||
participant Client as SMTPOverWSClient
|
||||
participant Queue as Session Queue
|
||||
participant WS as WebSocket
|
||||
participant Server as SMTP Server
|
||||
|
||||
Note over App,Server: Email Submission
|
||||
App->>Client: queueEmail(from, to[], data)
|
||||
Client->>Queue: Add to session or create new
|
||||
|
||||
Note over App,Server: Session Processing (Mutex)
|
||||
Queue->>Client: Process next session
|
||||
Client->>WS: Connect (if needed)
|
||||
WS->>Server: WebSocket connection
|
||||
Server-->>WS: Connection established
|
||||
|
||||
Client->>Server: AUTHENTICATE
|
||||
Server-->>Client: AUTH_SUCCESS
|
||||
|
||||
Client->>Server: SMTP_CHANNEL_OPEN
|
||||
Server-->>Client: SMTP_CHANNEL_READY
|
||||
Server-->>Client: 220 SMTP ready
|
||||
|
||||
Client->>Server: EHLO client
|
||||
Server-->>Client: 250 EHLO OK
|
||||
|
||||
Client->>Server: AUTH PLAIN <credentials>
|
||||
Server-->>Client: 235 AUTH OK
|
||||
|
||||
loop For each email in session
|
||||
Client->>Server: MAIL FROM:<sender>
|
||||
Server-->>Client: 250 Sender OK
|
||||
|
||||
Client->>Server: RCPT TO:<recipient>
|
||||
Server-->>Client: 250 Recipient OK
|
||||
|
||||
Client->>Server: DATA
|
||||
Server-->>Client: 354 Start data
|
||||
|
||||
Client->>Server: <email content>\r\n.
|
||||
Server-->>Client: 250 Message accepted
|
||||
|
||||
Client->>App: Email resolved
|
||||
end
|
||||
|
||||
Client->>Server: QUIT
|
||||
Server-->>Client: 221 Goodbye
|
||||
|
||||
Client->>Server: SMTP_CHANNEL_CLOSE
|
||||
WS->>WS: Disconnect (if queue empty)
|
||||
|
||||
Client->>App: Session completed
|
||||
```
|
||||
|
||||
## 📦 Installation
|
||||
|
||||
```bash
|
||||
npm install @siwats/mxrelay-consumer
|
||||
# or
|
||||
bun add @siwats/mxrelay-consumer
|
||||
```
|
||||
|
||||
## 🚀 Quick Start
|
||||
|
||||
### Direct Client Usage (New Session-Based API)
|
||||
|
||||
```typescript
|
||||
import { SMTPOverWSClient } from '@siwatsystem/mxrelay-consumer';
|
||||
import { SMTPOverWSClient } from '@siwats/mxrelay-consumer';
|
||||
|
||||
const client = new SMTPOverWSClient({
|
||||
url: 'wss://api.siwatsystem.com/smtp',
|
||||
apiKey: 'your-api-key',
|
||||
debug: true
|
||||
debug: true,
|
||||
maxEmailsPerSession: 5, // Batch up to 5 emails per session
|
||||
sessionTimeout: 300000 // 5 minute session timeout
|
||||
});
|
||||
|
||||
// Send SMTP commands directly
|
||||
// Queue individual emails - they'll be batched into sessions automatically
|
||||
try {
|
||||
const response = await client.sendSMTPCommand(`
|
||||
MAIL FROM: <sender@example.com>
|
||||
RCPT TO: <recipient@example.com>
|
||||
DATA
|
||||
Subject: Test Email
|
||||
|
||||
Hello from SMTP over WebSocket!
|
||||
.
|
||||
QUIT
|
||||
`);
|
||||
console.log('Email sent:', response);
|
||||
// These emails will be sent in the same SMTP session
|
||||
const emailId1 = await client.queueEmail(
|
||||
'sender@example.com',
|
||||
['recipient1@example.com'],
|
||||
'Subject: Email 1\r\n\r\nFirst email content'
|
||||
);
|
||||
|
||||
const emailId2 = await client.queueEmail(
|
||||
'sender@example.com',
|
||||
['recipient2@example.com'],
|
||||
'Subject: Email 2\r\n\r\nSecond email content'
|
||||
);
|
||||
|
||||
console.log('Emails queued:', emailId1, emailId2);
|
||||
} catch (error) {
|
||||
console.error('SMTP error:', error.message);
|
||||
console.error('Email error:', error.message);
|
||||
} finally {
|
||||
await client.shutdown();
|
||||
}
|
||||
```
|
||||
|
||||
### Nodemailer Transport
|
||||
### Nodemailer Transport (Recommended)
|
||||
|
||||
```typescript
|
||||
import nodemailer from 'nodemailer';
|
||||
import { createTransport } from '@siwatsystem/mxrelay-consumer';
|
||||
import { createTransport } from '@siwats/mxrelay-consumer';
|
||||
|
||||
// Create transport (uses defaults: api.siwatsystem.com:443 secure)
|
||||
const transport = createTransport('your-api-key');
|
||||
|
||||
// Or with custom options
|
||||
// Create transport with session batching
|
||||
const transport = createTransport('your-api-key', {
|
||||
host: 'custom.server.com',
|
||||
port: 80,
|
||||
secure: false,
|
||||
debug: true
|
||||
host: 'api.siwatsystem.com',
|
||||
port: 443,
|
||||
secure: true,
|
||||
debug: true,
|
||||
maxEmailsPerSession: 10, // Batch up to 10 emails per session
|
||||
sessionBatchTimeout: 2000 // Wait 2 seconds to batch emails
|
||||
});
|
||||
|
||||
const transporter = nodemailer.createTransporter(transport);
|
||||
|
||||
// Send email using standard Nodemailer API
|
||||
const info = await transporter.sendMail({
|
||||
from: 'sender@example.com',
|
||||
to: 'recipient@example.com',
|
||||
subject: 'Test Email via SMTP WebSocket',
|
||||
text: 'Plain text version',
|
||||
html: '<h1>HTML version</h1>',
|
||||
attachments: [
|
||||
{
|
||||
filename: 'document.pdf',
|
||||
path: './document.pdf'
|
||||
}
|
||||
]
|
||||
});
|
||||
// Send multiple emails - automatically batched into sessions
|
||||
const emails = [
|
||||
{
|
||||
from: 'sender@example.com',
|
||||
to: 'user1@example.com',
|
||||
subject: 'Welcome Email',
|
||||
html: '<h1>Welcome to our service!</h1>'
|
||||
},
|
||||
{
|
||||
from: 'sender@example.com',
|
||||
to: 'user2@example.com',
|
||||
subject: 'Newsletter',
|
||||
html: '<h1>Monthly Newsletter</h1>',
|
||||
attachments: [{ filename: 'report.pdf', path: './report.pdf' }]
|
||||
}
|
||||
];
|
||||
|
||||
// Send all emails - they'll be efficiently batched into SMTP sessions
|
||||
for (const email of emails) {
|
||||
try {
|
||||
const info = await transporter.sendMail(email);
|
||||
console.log('Email sent:', info.messageId);
|
||||
} catch (error) {
|
||||
console.error('Failed to send email:', error.message);
|
||||
}
|
||||
}
|
||||
|
||||
console.log('Message sent:', info.messageId);
|
||||
await transport.close();
|
||||
```
|
||||
|
||||
## Configuration
|
||||
## ⚙️ Configuration
|
||||
|
||||
### Client Configuration
|
||||
|
||||
|
@ -124,172 +234,348 @@ await transport.close();
|
|||
interface SMTPClientConfig {
|
||||
url: string; // WebSocket server URL
|
||||
apiKey: string; // Authentication API key
|
||||
|
||||
// Session Management
|
||||
sessionTimeout?: number; // Session timeout (default: 300000ms)
|
||||
maxEmailsPerSession?: number; // Emails per session (default: 10)
|
||||
sessionBatchTimeout?: number; // Batch wait time (default: 1000ms)
|
||||
|
||||
// Connection Management
|
||||
debug?: boolean; // Enable debug logging (default: false)
|
||||
maxQueueSize?: number; // Queue capacity limit (default: 1000)
|
||||
maxQueueSize?: number; // Session queue limit (default: 100)
|
||||
reconnectInterval?: number; // Reconnect delay (default: 5000ms)
|
||||
maxReconnectAttempts?: number; // Max retry attempts (default: 10)
|
||||
authTimeout?: number; // Auth timeout (default: 30000ms)
|
||||
channelTimeout?: number; // Channel timeout (default: 10000ms)
|
||||
messageTimeout?: number; // Message timeout (default: 60000ms)
|
||||
messageTimeout?: number; // SMTP command timeout (default: 60000ms)
|
||||
heartbeatInterval?: number; // Heartbeat interval (default: 30000ms)
|
||||
maxConcurrentMessages?: number; // Concurrent limit (default: 1)
|
||||
}
|
||||
```
|
||||
|
||||
### Transport Configuration
|
||||
|
||||
```typescript
|
||||
interface TransportOptions {
|
||||
interface TransportOptions extends Omit<SMTPClientConfig, 'url' | 'apiKey'> {
|
||||
host?: string; // Server host (default: 'api.siwatsystem.com')
|
||||
port?: number; // Server port (default: 443)
|
||||
secure?: boolean; // Use wss:// (default: true)
|
||||
debug?: boolean; // Enable debug mode (default: false)
|
||||
// ... other SMTPClientConfig options
|
||||
apiKey?: string; // API key for authentication
|
||||
}
|
||||
```
|
||||
|
||||
## Advanced Usage
|
||||
## 🎯 Advanced Usage
|
||||
|
||||
### Priority-Based Messaging
|
||||
### Session Priority and Options
|
||||
|
||||
```typescript
|
||||
import { MessagePriority } from '@siwatsystem/mxrelay-consumer';
|
||||
import { MessagePriority, SessionSendOptions } from '@siwats/mxrelay-consumer';
|
||||
|
||||
// High priority (processed first)
|
||||
await client.sendSMTPCommand('URGENT EMAIL DATA', {
|
||||
priority: MessagePriority.HIGH,
|
||||
timeout: 30000
|
||||
});
|
||||
// High priority email (processed first)
|
||||
await client.queueEmail(
|
||||
'urgent@company.com',
|
||||
['admin@company.com'],
|
||||
'Subject: URGENT ALERT\r\n\r\nSystem down!',
|
||||
{
|
||||
priority: MessagePriority.HIGH,
|
||||
timeout: 30000,
|
||||
retries: 5
|
||||
}
|
||||
);
|
||||
|
||||
// Critical priority (highest)
|
||||
await client.sendSMTPCommand('CRITICAL ALERT EMAIL', {
|
||||
priority: MessagePriority.CRITICAL
|
||||
});
|
||||
// Critical priority email (highest priority)
|
||||
await client.queueEmail(
|
||||
'security@company.com',
|
||||
['security-team@company.com'],
|
||||
'Subject: SECURITY BREACH\r\n\r\nImmediate action required!',
|
||||
{
|
||||
priority: MessagePriority.CRITICAL,
|
||||
immediate: true // Skip batching, send immediately
|
||||
}
|
||||
);
|
||||
```
|
||||
|
||||
### Event Monitoring
|
||||
### Session Event Monitoring
|
||||
|
||||
```typescript
|
||||
// Session-level events
|
||||
client.on('sessionQueued', (sessionId, queueSize) => {
|
||||
console.log(`Session ${sessionId} queued. Queue size: ${queueSize}`);
|
||||
});
|
||||
|
||||
client.on('sessionStarted', (sessionId, emailCount) => {
|
||||
console.log(`Processing session ${sessionId} with ${emailCount} emails`);
|
||||
});
|
||||
|
||||
client.on('sessionCompleted', (sessionId, results) => {
|
||||
console.log(`Session ${sessionId} completed with ${results.length} emails`);
|
||||
});
|
||||
|
||||
// Email-level events within sessions
|
||||
client.on('emailProcessed', (sessionId, emailId, responseTime) => {
|
||||
console.log(`Email ${emailId} in session ${sessionId} processed in ${responseTime}ms`);
|
||||
});
|
||||
|
||||
client.on('emailFailed', (sessionId, emailId, error) => {
|
||||
console.error(`Email ${emailId} in session ${sessionId} failed:`, error.message);
|
||||
});
|
||||
|
||||
// Connection events
|
||||
client.on('connected', () => console.log('WebSocket connected'));
|
||||
client.on('authenticated', () => console.log('Authentication successful'));
|
||||
client.on('disconnected', () => console.log('Connection lost'));
|
||||
|
||||
// Queue events
|
||||
client.on('messageQueued', (messageId, queueSize) => {
|
||||
console.log(`Message ${messageId} queued. Queue size: ${queueSize}`);
|
||||
});
|
||||
|
||||
client.on('messageProcessed', (messageId, responseTime) => {
|
||||
console.log(`Message ${messageId} processed in ${responseTime}ms`);
|
||||
});
|
||||
|
||||
// Error events
|
||||
client.on('error', (error) => console.error('Client error:', error));
|
||||
```
|
||||
|
||||
### Statistics and Monitoring
|
||||
### Session Statistics
|
||||
|
||||
```typescript
|
||||
const stats = client.getStats();
|
||||
console.log('Client Statistics:', {
|
||||
messagesQueued: stats.messagesQueued,
|
||||
messagesProcessed: stats.messagesProcessed,
|
||||
messagesFailed: stats.messagesFailed,
|
||||
averageResponseTime: stats.averageResponseTime,
|
||||
console.log('Session Statistics:', {
|
||||
sessionsQueued: stats.sessionsQueued,
|
||||
sessionsProcessed: stats.sessionsProcessed,
|
||||
sessionsFailed: stats.sessionsFailed,
|
||||
emailsProcessed: stats.emailsProcessed,
|
||||
emailsFailed: stats.emailsFailed,
|
||||
averageSessionTime: stats.averageSessionTime,
|
||||
averageEmailTime: stats.averageEmailTime,
|
||||
queueSize: stats.queueSize
|
||||
});
|
||||
|
||||
console.log('Session State:', client.getSessionState()); // IDLE, PROCESSING, FAILED
|
||||
console.log('Connection State:', client.getConnectionState()); // DISCONNECTED, CONNECTED, etc.
|
||||
```
|
||||
|
||||
## Error Handling
|
||||
## 🛡️ Error Handling
|
||||
|
||||
### SMTP Error Detection
|
||||
|
||||
The transport properly detects and categorizes SMTP errors:
|
||||
### SMTP Session Errors
|
||||
|
||||
```typescript
|
||||
try {
|
||||
await transporter.sendMail({
|
||||
from: 'unauthorized@domain.com', // Invalid sender
|
||||
to: 'recipient@example.com',
|
||||
subject: 'Test'
|
||||
});
|
||||
await client.queueEmail(
|
||||
'invalid-sender@domain.com',
|
||||
['recipient@example.com'],
|
||||
'Subject: Test\r\n\r\nTest message'
|
||||
);
|
||||
} catch (error) {
|
||||
console.error('SMTP Error:', error.message);
|
||||
// Output: "Sender not authorized: Sender domain not authorized for your IP or subnet"
|
||||
|
||||
console.log('Error details:', {
|
||||
smtpCode: error.context.smtpCode, // "550"
|
||||
rejectedRecipients: error.context.rejectedRecipients
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
### Error Classification
|
||||
|
||||
```typescript
|
||||
import {
|
||||
ConnectionError,
|
||||
AuthenticationError,
|
||||
MessageError,
|
||||
TimeoutError
|
||||
} from '@siwatsystem/mxrelay-consumer';
|
||||
|
||||
try {
|
||||
await client.sendSMTPCommand('MAIL FROM: <test@example.com>');
|
||||
} catch (error) {
|
||||
if (error instanceof ConnectionError) {
|
||||
console.error('Connection failed:', error.message);
|
||||
} else if (error instanceof AuthenticationError) {
|
||||
console.error('Authentication failed:', error.message);
|
||||
} else if (error instanceof MessageError) {
|
||||
console.error('SMTP error:', error.message, 'Code:', error.context.smtpCode);
|
||||
} else if (error instanceof TimeoutError) {
|
||||
console.error('Operation timed out:', error.message);
|
||||
if (error instanceof MessageError) {
|
||||
console.error('SMTP Error:', error.message);
|
||||
console.log('Email ID:', error.messageId);
|
||||
console.log('Retry Count:', error.retryCount);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Connection States
|
||||
### Session State Management
|
||||
|
||||
The client manages connection states automatically:
|
||||
The client manages session states automatically:
|
||||
|
||||
- `DISCONNECTED` - No connection
|
||||
- `CONNECTING` - Establishing WebSocket connection
|
||||
- `CONNECTED` - WebSocket connected, authentication pending
|
||||
- `AUTHENTICATING` - Sending credentials
|
||||
- `AUTHENTICATED` - Ready for SMTP operations
|
||||
- `CHANNEL_OPENING` - Opening SMTP channel
|
||||
- `CHANNEL_READY` - SMTP channel active
|
||||
- `CHANNEL_CLOSED` - SMTP channel closed
|
||||
- `RECONNECTING` - Attempting reconnection
|
||||
- `FAILED` - Connection failed, max retries reached
|
||||
```mermaid
|
||||
stateDiagram-v2
|
||||
[*] --> IDLE
|
||||
IDLE --> PROCESSING: Session queued
|
||||
PROCESSING --> IDLE: Session completed
|
||||
PROCESSING --> FAILED: Session error
|
||||
FAILED --> PROCESSING: Retry session
|
||||
FAILED --> IDLE: Max retries reached
|
||||
IDLE --> [*]: Client shutdown
|
||||
```
|
||||
|
||||
## Development
|
||||
- `IDLE` - No active session, ready to process
|
||||
- `PROCESSING` - Session currently being processed (mutex locked)
|
||||
- `FAILED` - Session failed, will retry or reject
|
||||
- `COMPLETED` - Session successfully completed
|
||||
|
||||
This project uses Bun as the primary runtime. TypeScript files can be run directly without building.
|
||||
### Connection States
|
||||
|
||||
```mermaid
|
||||
stateDiagram-v2
|
||||
[*] --> DISCONNECTED
|
||||
DISCONNECTED --> CONNECTING: Connect requested
|
||||
CONNECTING --> CONNECTED: WebSocket established
|
||||
CONNECTED --> AUTHENTICATING: Send credentials
|
||||
AUTHENTICATING --> AUTHENTICATED: Auth successful
|
||||
AUTHENTICATED --> CHANNEL_OPENING: Open SMTP channel
|
||||
CHANNEL_OPENING --> CHANNEL_READY: Channel established
|
||||
CHANNEL_READY --> CHANNEL_CLOSED: Session complete
|
||||
CHANNEL_CLOSED --> AUTHENTICATED: Ready for next session
|
||||
AUTHENTICATED --> DISCONNECTED: Queue empty
|
||||
|
||||
CONNECTING --> RECONNECTING: Connection failed
|
||||
AUTHENTICATING --> RECONNECTING: Auth failed
|
||||
CHANNEL_OPENING --> RECONNECTING: Channel failed
|
||||
RECONNECTING --> CONNECTING: Retry attempt
|
||||
RECONNECTING --> FAILED: Max retries reached
|
||||
|
||||
FAILED --> [*]: Shutdown
|
||||
DISCONNECTED --> [*]: Shutdown
|
||||
```
|
||||
|
||||
## 🔧 Session Queue Architecture
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
subgraph Emails ["📧 Email Submission"]
|
||||
E1[Email 1 - HIGH]
|
||||
E2[Email 2 - NORMAL]
|
||||
E3[Email 3 - HIGH]
|
||||
E4[Email 4 - CRITICAL]
|
||||
end
|
||||
|
||||
subgraph Queue ["📋 Session Creation"]
|
||||
SQ[Session Queue]
|
||||
S1[Session 1 - CRITICAL]
|
||||
S2[Session 2 - HIGH]
|
||||
S3[Session 3 - NORMAL]
|
||||
end
|
||||
|
||||
subgraph Processing ["⚡ Session Processing"]
|
||||
SP[Session Processor]
|
||||
SC[SMTP Connection]
|
||||
SM[Session Manager]
|
||||
end
|
||||
|
||||
E1 --> SQ
|
||||
E2 --> SQ
|
||||
E3 --> SQ
|
||||
E4 --> SQ
|
||||
|
||||
SQ --> S1
|
||||
SQ --> S2
|
||||
SQ --> S3
|
||||
|
||||
S1 --> SP
|
||||
S2 --> SP
|
||||
S3 --> SP
|
||||
|
||||
SP --> SC
|
||||
SC --> SM
|
||||
|
||||
style S1 fill:#ffcdd2
|
||||
style S2 fill:#fff3e0
|
||||
style S3 fill:#e8f5e8
|
||||
style SP fill:#e1f5fe
|
||||
style SC fill:#f3e5f5
|
||||
```
|
||||
|
||||
## 🏭 Production Configuration
|
||||
|
||||
### Optimal Settings
|
||||
|
||||
```typescript
|
||||
const client = new SMTPOverWSClient({
|
||||
url: 'wss://api.siwatsystem.com/smtp',
|
||||
apiKey: process.env.MXRELAY_API_KEY,
|
||||
|
||||
// Production optimizations
|
||||
debug: false,
|
||||
maxEmailsPerSession: 20, // Batch more emails per session
|
||||
sessionBatchTimeout: 5000, // Wait longer to accumulate emails
|
||||
sessionTimeout: 600000, // 10 minute session timeout
|
||||
maxQueueSize: 1000, // Higher queue capacity
|
||||
reconnectInterval: 15000, // Longer reconnect delay
|
||||
maxReconnectAttempts: 3, // Fewer retries in production
|
||||
messageTimeout: 120000 // 2 minute SMTP timeout
|
||||
});
|
||||
```
|
||||
|
||||
### Graceful Shutdown
|
||||
|
||||
```typescript
|
||||
process.on('SIGTERM', async () => {
|
||||
console.log('Shutting down SMTP client...');
|
||||
try {
|
||||
// Wait for current sessions to complete
|
||||
await client.shutdown(60000); // 60 second timeout
|
||||
console.log('SMTP client shutdown complete');
|
||||
} catch (error) {
|
||||
console.error('Forced SMTP client shutdown');
|
||||
}
|
||||
process.exit(0);
|
||||
});
|
||||
```
|
||||
|
||||
## 📚 API Reference
|
||||
|
||||
### SMTPOverWSClient
|
||||
|
||||
#### Methods
|
||||
- `queueEmail(from, to[], data, options?)` - Queue email for session processing
|
||||
- `getStats()` - Get session and email statistics
|
||||
- `getConnectionState()` - Get current connection state
|
||||
- `getSessionState()` - Get current session processing state
|
||||
- `getQueueSize()` - Get session queue size
|
||||
- `shutdown(timeout?)` - Graceful shutdown with session completion
|
||||
|
||||
#### Events
|
||||
- **Session Events**: `sessionQueued`, `sessionStarted`, `sessionCompleted`, `sessionFailed`
|
||||
- **Email Events**: `emailProcessed`, `emailFailed`
|
||||
- **Connection Events**: `connecting`, `connected`, `authenticated`, `disconnected`, `reconnecting`
|
||||
- **Queue Events**: `queueProcessingStarted`, `queueProcessingCompleted`
|
||||
- **State Events**: `stateChanged`, `error`
|
||||
|
||||
### createTransport(apiKey, options?)
|
||||
|
||||
Creates a Nodemailer-compatible transport with session batching.
|
||||
|
||||
- `apiKey` - Your API key for authentication
|
||||
- `options` - Optional transport and session configuration
|
||||
|
||||
## 🔍 Debugging
|
||||
|
||||
### Enable Debug Logging
|
||||
|
||||
```typescript
|
||||
const client = new SMTPOverWSClient({
|
||||
url: 'wss://api.siwatsystem.com/smtp',
|
||||
apiKey: 'your-api-key',
|
||||
debug: true // Enable comprehensive logging
|
||||
});
|
||||
|
||||
// Debug output includes:
|
||||
// - Session creation and batching
|
||||
// - SMTP protocol commands and responses
|
||||
// - Connection state transitions
|
||||
// - Queue processing details
|
||||
// - Error stack traces
|
||||
```
|
||||
|
||||
### Monitor Session Processing
|
||||
|
||||
```typescript
|
||||
client.on('sessionStarted', (sessionId, emailCount) => {
|
||||
console.log(`🚀 Starting session ${sessionId} with ${emailCount} emails`);
|
||||
});
|
||||
|
||||
client.on('emailProcessed', (sessionId, emailId, responseTime) => {
|
||||
console.log(`✅ Email ${emailId} processed in ${responseTime}ms`);
|
||||
});
|
||||
|
||||
client.on('sessionCompleted', (sessionId, results) => {
|
||||
const successful = results.filter(r => r.success).length;
|
||||
const failed = results.filter(r => !r.success).length;
|
||||
console.log(`🎉 Session ${sessionId} complete: ${successful} sent, ${failed} failed`);
|
||||
});
|
||||
```
|
||||
|
||||
## 🧪 Development
|
||||
|
||||
### Setup
|
||||
|
||||
```bash
|
||||
# Clone repository
|
||||
git clone https://git.siwatsystem.com/siwat/mxrelay-consumer.git
|
||||
git clone https://git.siwatsystem.com/siwatsystem-public/mxrelay-consumer.git
|
||||
cd mxrelay-consumer
|
||||
|
||||
# Install dependencies
|
||||
bun install
|
||||
|
||||
# Run examples directly
|
||||
# Run examples directly (TypeScript)
|
||||
bun run examples/nodemailer-transport.ts
|
||||
|
||||
# Run with environment variable
|
||||
MXRELAY_API_KEY=your-key bun run examples/nodemailer-transport.ts
|
||||
```
|
||||
|
||||
### Build & Test
|
||||
|
||||
```bash
|
||||
# Build (optional - bun runs TypeScript directly)
|
||||
# Build all formats
|
||||
bun run build
|
||||
|
||||
# Run tests
|
||||
|
@ -302,90 +588,15 @@ bun run lint
|
|||
bun run format
|
||||
```
|
||||
|
||||
## Protocol Implementation
|
||||
|
||||
### WebSocket Message Types
|
||||
|
||||
- `AUTHENTICATE` / `AUTHENTICATE_RESPONSE` - Authentication flow
|
||||
- `SMTP_CHANNEL_OPEN` / `SMTP_CHANNEL_READY` - Channel management
|
||||
- `SMTP_CHANNEL_CLOSED` / `SMTP_CHANNEL_ERROR` - Channel lifecycle
|
||||
- `SMTP_TO_SERVER` / `SMTP_FROM_SERVER` - SMTP data exchange
|
||||
|
||||
### Connection Flow
|
||||
|
||||
1. **WebSocket Connection** - Connect to relay server
|
||||
2. **Authentication** - Authenticate using API key
|
||||
3. **Channel Management** - Open SMTP channel per message
|
||||
4. **Data Transfer** - Exchange SMTP commands and responses
|
||||
5. **Cleanup** - Close channel and disconnect when queue empty
|
||||
|
||||
## Best Practices
|
||||
|
||||
### Production Configuration
|
||||
|
||||
```typescript
|
||||
const client = new SMTPOverWSClient({
|
||||
url: 'wss://api.siwatsystem.com/smtp',
|
||||
apiKey: process.env.MXRELAY_API_KEY,
|
||||
|
||||
// Production settings
|
||||
debug: false,
|
||||
maxQueueSize: 5000,
|
||||
reconnectInterval: 10000,
|
||||
maxReconnectAttempts: 5,
|
||||
messageTimeout: 120000
|
||||
});
|
||||
```
|
||||
|
||||
### Graceful Shutdown
|
||||
|
||||
```typescript
|
||||
process.on('SIGTERM', async () => {
|
||||
console.log('Shutting down...');
|
||||
try {
|
||||
await client.shutdown(30000); // 30 second timeout
|
||||
console.log('Shutdown complete');
|
||||
} catch (error) {
|
||||
console.error('Forced shutdown');
|
||||
}
|
||||
process.exit(0);
|
||||
});
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
### createTransport(apiKey, options?)
|
||||
|
||||
Creates a Nodemailer-compatible transport.
|
||||
|
||||
- `apiKey` - Your API key for authentication
|
||||
- `options` - Optional transport configuration
|
||||
|
||||
### SMTPOverWSClient
|
||||
|
||||
Main client class for direct SMTP operations.
|
||||
|
||||
#### Methods
|
||||
- `sendSMTPCommand(data, options?)` - Send SMTP command
|
||||
- `getStats()` - Get client statistics
|
||||
- `getConnectionState()` - Get current state
|
||||
- `getQueueSize()` - Get queue size
|
||||
- `shutdown(timeout?)` - Graceful shutdown
|
||||
|
||||
#### Events
|
||||
- Connection: `connecting`, `connected`, `authenticated`, `disconnected`
|
||||
- Queue: `messageQueued`, `messageProcessed`, `messageFailed`
|
||||
- State: `stateChanged`, `error`
|
||||
|
||||
## License
|
||||
## 📄 License
|
||||
|
||||
MIT License - see [LICENSE](LICENSE) file for details.
|
||||
|
||||
## Support
|
||||
## 🆘 Support
|
||||
|
||||
- Issues: [Git Repository Issues](https://git.siwatsystem.com/siwat/mxrelay-consumer/issues)
|
||||
- Documentation: [Project Repository](https://git.siwatsystem.com/siwat/mxrelay-consumer)
|
||||
- **Issues**: [Git Repository Issues](https://git.siwatsystem.com/siwatsystem-public/mxrelay-consumer/issues)
|
||||
- **Documentation**: [Project Repository](https://git.siwatsystem.com/siwatsystem-public/mxrelay-consumer)
|
||||
|
||||
---
|
||||
|
||||
Built by SiwatSystem
|
||||
**Built by SiwatSystem** | *Session-based SMTP over WebSocket*
|
Loading…
Add table
Add a link
Reference in a new issue