17 KiB
17 KiB
@siwats/mxrelay-consumer
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.
🚀 Key Features
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
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
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
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
📊 Architecture Overview
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
🔄 Session Processing Flow
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
npm install @siwats/mxrelay-consumer
# or
bun add @siwats/mxrelay-consumer
🚀 Quick Start
Direct Client Usage (New Session-Based API)
import { SMTPOverWSClient } from '@siwats/mxrelay-consumer';
const client = new SMTPOverWSClient({
url: 'wss://api.siwatsystem.com/smtp',
apiKey: 'your-api-key',
debug: true,
maxEmailsPerSession: 5, // Batch up to 5 emails per session
sessionTimeout: 300000 // 5 minute session timeout
});
// Queue individual emails - they'll be batched into sessions automatically
try {
// 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('Email error:', error.message);
} finally {
await client.shutdown();
}
Nodemailer Transport (Recommended)
import nodemailer from 'nodemailer';
import { createTransport } from '@siwats/mxrelay-consumer';
// Create transport with session batching
const transport = createTransport('your-api-key', {
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 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);
}
}
await transport.close();
⚙️ Configuration
Client Configuration
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; // 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; // SMTP command timeout (default: 60000ms)
heartbeatInterval?: number; // Heartbeat interval (default: 30000ms)
}
Transport Configuration
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)
apiKey?: string; // API key for authentication
}
🎯 Advanced Usage
Session Priority and Options
import { MessagePriority, SessionSendOptions } from '@siwats/mxrelay-consumer';
// 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 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
}
);
Session Event Monitoring
// 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'));
Session Statistics
const stats = client.getStats();
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
SMTP Session Errors
try {
await client.queueEmail(
'invalid-sender@domain.com',
['recipient@example.com'],
'Subject: Test\r\n\r\nTest message'
);
} catch (error) {
if (error instanceof MessageError) {
console.error('SMTP Error:', error.message);
console.log('Email ID:', error.messageId);
console.log('Retry Count:', error.retryCount);
}
}
Session State Management
The client manages session states automatically:
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
IDLE
- No active session, ready to processPROCESSING
- Session currently being processed (mutex locked)FAILED
- Session failed, will retry or rejectCOMPLETED
- Session successfully completed
Connection States
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
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
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
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 processinggetStats()
- Get session and email statisticsgetConnectionState()
- Get current connection stategetSessionState()
- Get current session processing stategetQueueSize()
- Get session queue sizeshutdown(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 authenticationoptions
- Optional transport and session configuration
🔍 Debugging
Enable Debug Logging
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
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
# Clone repository
git clone https://git.siwatsystem.com/siwatsystem-public/mxrelay-consumer.git
cd mxrelay-consumer
# Install dependencies
bun install
# Run examples directly (TypeScript)
bun run examples/nodemailer-transport.ts
Build & Test
# Build all formats
bun run build
# Run tests
bun test
# Run linting
bun run lint
# Format code
bun run format
📄 License
MIT License - see LICENSE file for details.
🆘 Support
- Issues: Git Repository Issues
- Documentation: Project Repository
Built by SiwatSystem | Session-based SMTP over WebSocket