No description
Find a file
2025-08-19 19:00:37 +07:00
.claude initial commit 2025-08-18 22:22:04 +07:00
examples feat: Update Nodemailer transport to support optional host and apiKey parameters, enhance error handling, and improve example usage 2025-08-19 02:30:52 +07:00
src feat: Bump version to 1.2.2 and improve event emission handling in SMTPOverWSClient to avoid race conditions 2025-08-19 19:00:37 +07:00
.eslintrc.js initial commit 2025-08-18 22:22:04 +07:00
.gitignore feat: Refactor SMTP client to support session-based email processing, enhancing queue management and error handling 2025-08-19 13:53:20 +07:00
.prettierrc initial commit 2025-08-18 22:22:04 +07:00
bun.lock chore: bump version to 1.2.0 2025-08-19 18:24:17 +07:00
CLAUDE.md feat: Update Nodemailer transport to support optional host and apiKey parameters, enhance error handling, and improve example usage 2025-08-19 02:30:52 +07:00
LICENSE initial commit 2025-08-18 22:22:04 +07:00
package-lock.json initial commit 2025-08-18 22:22:04 +07:00
package.json feat: Bump version to 1.2.2 and improve event emission handling in SMTPOverWSClient to avoid race conditions 2025-08-19 19:00:37 +07:00
README.md chore: bump version to 1.2.0 2025-08-19 18:24:17 +07:00
tsconfig.cjs.json initial commit 2025-08-18 22:22:04 +07:00
tsconfig.esm.json initial commit 2025-08-18 22:22:04 +07:00
tsconfig.json initial commit 2025-08-18 22:22:04 +07:00
tsconfig.types.json initial commit 2025-08-18 22:22:04 +07:00

@siwats/mxrelay-consumer

TypeScript License: MIT

A production-ready TypeScript client library for SMTP over WebSocket protocol with dedicated SMTP sessions, automatic reconnection, and comprehensive error handling. Each email gets its own complete SMTP session for maximum protocol compliance and isolation.

🚀 Key Features

Dedicated SMTP Sessions

  • Isolated SMTP Sessions: One email = One complete SMTP session (EHLO → AUTH → Email → QUIT)
  • Session Mutex: Only one SMTP session active at a time (protocol compliant)
  • Maximum Isolation: Each email in its own session for better error handling
  • Resource Optimization: Connect only when needed, disconnect when idle

Intelligent Queue Management

  • Session Queuing: Each email queued as its own dedicated session
  • Priority-Based Processing: CRITICAL > HIGH > NORMAL > LOW priority levels
  • One-to-One Mapping: One email per SMTP session for maximum isolation
  • 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
    
    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
    
    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,
    sessionTimeout: 300000   // 5 minute session timeout
});

// Queue individual emails - each gets its own dedicated SMTP session
try {
    // Each email will be sent in its own complete 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();
}
import nodemailer from 'nodemailer';
import { createTransport } from '@siwats/mxrelay-consumer';

// Create transport with dedicated sessions
const transport = createTransport('your-api-key', {
    host: 'api.siwatsystem.com',
    port: 443,
    secure: true,
    debug: true,
    sessionBatchTimeout: 2000   // Processing delay between sessions
});

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 - each gets its own dedicated SMTP session
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)
    sessionBatchTimeout?: number;       // Processing delay between sessions (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  // Send with highest priority
    }
);

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 process
  • PROCESSING - Session currently being processed (mutex locked)
  • FAILED - Session failed, will retry or reject
  • COMPLETED - 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 - Email 4]
        S2[Session 2 - HIGH - Email 1]
        S3[Session 3 - HIGH - Email 3]
        S4[Session 4 - NORMAL - Email 2]
    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
    SQ --> S4
    
    S1 --> SP
    S2 --> SP
    S3 --> SP
    S4 --> 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,
    sessionBatchTimeout: 5000,      // Longer delay between sessions
    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 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 dedicated SMTP sessions.

  • apiKey - Your API key for authentication
  • options - 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 (one per email)
// - 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


Built by SiwatSystem | Session-based SMTP over WebSocket