Files
rentall-app/backend/services/email/core/EmailClient.js
2026-01-10 20:47:29 -05:00

129 lines
3.8 KiB
JavaScript

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<void>}
*/
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;