const { SESClient, SendEmailCommand } = require("@aws-sdk/client-ses"); const { getAWSConfig } = require("../../../config/aws"); const { htmlToPlainText } = require("./emailUtils"); const logger = require("../../../utils/logger"); /** * EmailClient handles AWS SES configuration and core email sending functionality * This class is responsible for: * - Initializing the AWS SES client * - Sending emails with HTML and plain text content * - Managing email sending state (enabled/disabled via environment) */ class EmailClient { constructor() { // Singleton pattern - return existing instance if already created if (EmailClient.instance) { return EmailClient.instance; } this.sesClient = null; this.initialized = false; this.initializationPromise = null; EmailClient.instance = this; } /** * Initialize the AWS SES client * @returns {Promise} */ async initialize() { // If already initialized, return immediately if (this.initialized) return; // If initialization is in progress, wait for it if (this.initializationPromise) { return this.initializationPromise; } // Start initialization and store the promise this.initializationPromise = (async () => { try { // Use centralized AWS configuration with credential profiles const awsConfig = getAWSConfig(); this.sesClient = new SESClient(awsConfig); this.initialized = true; logger.info("AWS SES Email Client initialized successfully"); } catch (error) { logger.error("Failed to initialize AWS SES Email Client", { error }); throw error; } })(); return this.initializationPromise; } /** * Send an email using AWS SES * @param {string|string[]} to - Email address(es) to send to * @param {string} subject - Email subject line * @param {string} htmlContent - HTML content of the email * @param {string|null} textContent - Plain text content (auto-generated from HTML if not provided) * @returns {Promise<{success: boolean, messageId?: string, error?: string}>} */ async sendEmail(to, subject, htmlContent, textContent = null) { if (!this.initialized) { await this.initialize(); } // Check if email sending is enabled in the environment if (!process.env.EMAIL_ENABLED || process.env.EMAIL_ENABLED !== "true") { logger.debug("Email sending disabled in environment"); return { success: true, messageId: "disabled" }; } // Auto-generate plain text from HTML if not provided if (!textContent) { textContent = htmlToPlainText(htmlContent); } // Use friendly sender name format for better recognition const fromName = process.env.SES_FROM_NAME || "Village Share"; const fromEmail = process.env.SES_FROM_EMAIL; const source = `${fromName} <${fromEmail}>`; const params = { Source: source, Destination: { ToAddresses: Array.isArray(to) ? to : [to], }, Message: { Subject: { Data: subject, Charset: "UTF-8", }, Body: { Html: { Data: htmlContent, Charset: "UTF-8", }, Text: { Data: textContent, Charset: "UTF-8", }, }, }, }; // Add reply-to address if configured if (process.env.SES_REPLY_TO_EMAIL) { params.ReplyToAddresses = [process.env.SES_REPLY_TO_EMAIL]; } try { const command = new SendEmailCommand(params); const result = await this.sesClient.send(command); logger.info("Email sent successfully", { to, messageId: result.MessageId }); return { success: true, messageId: result.MessageId }; } catch (error) { logger.error("Failed to send email", { error, to }); return { success: false, error: error.message }; } } } module.exports = EmailClient;