UNPKG

@voilajsx/appkit

Version:

Minimal and framework agnostic Node.js toolkit designed for AI agentic backend development

286 lines 11.2 kB
/** * SMTP email strategy with nodemailer and connection pooling * @module @voilajsx/appkit/email * @file src/email/strategies/smtp.ts * * @llm-rule WHEN: App has SMTP_HOST environment variable for universal email sending * @llm-rule AVOID: Manual SMTP setup - this handles connection pooling, authentication, and reliability * @llm-rule NOTE: Universal email strategy that works with any SMTP server (Gmail, Outlook, etc.) */ /** * SMTP email strategy with connection pooling and reliability features */ export class SmtpStrategy { config; transporter = null; connected = false; /** * Creates SMTP strategy with direct environment access (like auth pattern) * @llm-rule WHEN: Email initialization with SMTP_HOST detected * @llm-rule AVOID: Manual SMTP configuration - environment detection handles this */ constructor(config) { this.config = config; } /** * Sends email via SMTP with automatic connection management * @llm-rule WHEN: Sending emails through SMTP servers * @llm-rule AVOID: Manual SMTP calls - this handles all connection complexity * @llm-rule NOTE: Includes connection pooling, authentication, and error handling */ async send(data) { try { // Ensure transporter is connected await this.ensureConnected(); // Convert to nodemailer format const mailOptions = this.convertToSmtpFormat(data); // Send via SMTP const result = await this.transporter.sendMail(mailOptions); return { success: true, messageId: result.messageId, }; } catch (error) { const errorMessage = this.parseSmtpError(error); if (this.config.environment.isDevelopment) { console.error(`[AppKit] SMTP error:`, errorMessage); } return { success: false, error: errorMessage, }; } } /** * Disconnects SMTP strategy gracefully * @llm-rule WHEN: App shutdown or email cleanup * @llm-rule AVOID: Abrupt disconnection - graceful shutdown prevents connection issues */ async disconnect() { if (!this.connected || !this.transporter) return; try { await this.transporter.close(); this.connected = false; this.transporter = null; if (this.config.environment.isDevelopment) { console.log(`👋 [AppKit] SMTP disconnected`); } } catch (error) { console.error(`⚠️ [AppKit] SMTP disconnect error:`, error.message); } } // Private helper methods /** * Ensures SMTP transporter is connected */ async ensureConnected() { if (this.connected && this.transporter) return; try { // Dynamic import for nodemailer const nodemailer = await import('nodemailer'); const smtpConfig = this.config.smtp; // Create transporter with connection pooling this.transporter = nodemailer.createTransport({ host: smtpConfig.host, port: smtpConfig.port, secure: smtpConfig.secure, auth: smtpConfig.auth.user && smtpConfig.auth.pass ? { user: smtpConfig.auth.user, pass: smtpConfig.auth.pass, } : undefined, pool: smtpConfig.pool, maxConnections: 5, maxMessages: 100, rateDelta: 1000, rateLimit: 10, connectionTimeout: smtpConfig.timeout, socketTimeout: smtpConfig.timeout, // Security options tls: { rejectUnauthorized: this.config.environment.isProduction, }, }); // Verify connection await this.transporter.verify(); this.connected = true; if (this.config.environment.isDevelopment) { console.log(`✅ [AppKit] SMTP connected to ${smtpConfig.host}:${smtpConfig.port}`); } } catch (error) { this.connected = false; this.transporter = null; throw new Error(`SMTP connection failed: ${error.message}`); } } /** * Converts EmailData to nodemailer format */ convertToSmtpFormat(data) { const mailOptions = { from: this.formatEmailAddress(data.from), to: this.formatEmailAddresses(data.to), subject: data.subject, }; // Add content if (data.html) { mailOptions.html = data.html; } if (data.text) { mailOptions.text = data.text; } // Add optional fields if (data.replyTo) { mailOptions.replyTo = this.formatEmailAddress(data.replyTo); } if (data.cc) { mailOptions.cc = this.formatEmailAddresses(data.cc); } if (data.bcc) { mailOptions.bcc = this.formatEmailAddresses(data.bcc); } // Add attachments if (data.attachments && data.attachments.length > 0) { mailOptions.attachments = this.formatAttachments(data.attachments); } return mailOptions; } /** * Formats single email address for nodemailer */ formatEmailAddress(address) { if (typeof address === 'string') { return address; } if (address.name) { return `"${address.name}" <${address.email}>`; } return address.email; } /** * Formats multiple email addresses for nodemailer */ formatEmailAddresses(addresses) { const addressArray = Array.isArray(addresses) ? addresses : [addresses]; return addressArray.map(addr => this.formatEmailAddress(addr)).join(', '); } /** * Formats attachments for nodemailer */ formatAttachments(attachments) { return attachments.map(attachment => ({ filename: attachment.filename, content: attachment.content, contentType: attachment.contentType || this.guessContentType(attachment.filename), })); } /** * Guesses content type from filename */ guessContentType(filename) { const ext = filename.split('.').pop()?.toLowerCase(); const mimeTypes = { pdf: 'application/pdf', doc: 'application/msword', docx: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', xls: 'application/vnd.ms-excel', xlsx: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', png: 'image/png', jpg: 'image/jpeg', jpeg: 'image/jpeg', gif: 'image/gif', txt: 'text/plain', html: 'text/html', css: 'text/css', js: 'application/javascript', json: 'application/json', zip: 'application/zip', }; return mimeTypes[ext || ''] || 'application/octet-stream'; } /** * Parses SMTP errors into user-friendly messages */ parseSmtpError(error) { if (error.message) { const message = error.message.toLowerCase(); // Authentication errors if (message.includes('authentication') || message.includes('invalid credentials') || message.includes('username') || message.includes('password')) { return 'SMTP authentication failed. Check SMTP_USER and SMTP_PASS environment variables.'; } // Connection errors if (message.includes('connection') || message.includes('connect') || message.includes('econnrefused') || message.includes('timeout')) { return 'SMTP connection failed. Check SMTP_HOST and SMTP_PORT environment variables.'; } // TLS/SSL errors if (message.includes('tls') || message.includes('ssl') || message.includes('secure')) { return 'SMTP TLS/SSL error. Check SMTP_SECURE environment variable or server settings.'; } // Rate limiting if (message.includes('rate') || message.includes('limit') || message.includes('quota')) { return 'SMTP rate limit exceeded. Please try again later.'; } // Recipient errors if (message.includes('recipient') || message.includes('address') || message.includes('mailbox') || message.includes('does not exist')) { return 'Invalid recipient email address or mailbox does not exist.'; } // Sender errors if (message.includes('sender') || message.includes('from') || message.includes('not authorized') || message.includes('relay')) { return 'SMTP sender not authorized. Check FROM address and server relay settings.'; } // Message size errors if (message.includes('size') || message.includes('too large') || message.includes('exceeded')) { return 'Email message too large. Reduce attachment size or content length.'; } // DNS errors if (message.includes('dns') || message.includes('hostname') || message.includes('resolve')) { return 'DNS resolution failed. Check SMTP_HOST value.'; } // Port errors if (message.includes('port') || message.includes('refused') || message.includes('unreachable')) { return 'SMTP port connection failed. Check SMTP_PORT value and firewall settings.'; } return error.message; } // Check for specific error codes if (error.code) { switch (error.code) { case 'ECONNREFUSED': return 'SMTP connection refused. Check SMTP_HOST and SMTP_PORT.'; case 'ENOTFOUND': return 'SMTP host not found. Check SMTP_HOST value.'; case 'ETIMEDOUT': return 'SMTP connection timeout. Check network connectivity.'; case 'ECONNRESET': return 'SMTP connection reset. Server may be overloaded.'; case 'ESOCKET': return 'SMTP socket error. Check network settings.'; default: return `SMTP error: ${error.code}`; } } return 'Unknown SMTP error occurred'; } /** * Gets SMTP connection info for debugging */ getConnectionInfo() { const smtpConfig = this.config.smtp; return { host: smtpConfig.host, port: smtpConfig.port, secure: smtpConfig.secure, authenticated: !!(smtpConfig.auth.user && smtpConfig.auth.pass), connected: this.connected, }; } } //# sourceMappingURL=smtp.js.map