import { fileURLToPath } from "url"; import path from "path"; const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); const SERVICE_NAME = "EmailService"; /** * Represents an email service that can load templates, build, and send emails. */ class EmailService { /** * Constructs an instance of the EmailService, initializing template loaders and the email transporter. * @param {Object} settingsService - The settings service to get email configuration. * @param {Object} fs - The file system module. * @param {Object} path - The path module. * @param {Function} compile - The Handlebars compile function. * @param {Function} mjml2html - The MJML to HTML conversion function. * @param {Object} nodemailer - The nodemailer module. * @param {Object} logger - The logger module. */ constructor(settingsService, fs, path, compile, mjml2html, nodemailer, logger) { this.settingsService = settingsService; this.fs = fs; this.path = path; this.compile = compile; this.mjml2html = mjml2html; this.nodemailer = nodemailer; this.logger = logger; /** * Loads an email template from the filesystem. * * @param {string} templateName - The name of the template to load. * @returns {Function} A compiled template function that can be used to generate HTML email content. */ this.loadTemplate = (templateName) => { try { const templatePath = this.path.join( __dirname, `../templates/${templateName}.mjml` ); const templateContent = this.fs.readFileSync(templatePath, "utf8"); return this.compile(templateContent); } catch (error) { this.logger.error({ message: error.message, service: SERVICE_NAME, method: "loadTemplate", stack: error.stack, }); } }; /** * A lookup object to access preloaded email templates. * @type {Object.} * TODO Load less used templates in their respective functions */ this.templateLookup = { welcomeEmailTemplate: this.loadTemplate("welcomeEmail"), employeeActivationTemplate: this.loadTemplate("employeeActivation"), noIncidentsThisWeekTemplate: this.loadTemplate("noIncidentsThisWeek"), serverIsDownTemplate: this.loadTemplate("serverIsDown"), serverIsUpTemplate: this.loadTemplate("serverIsUp"), passwordResetTemplate: this.loadTemplate("passwordReset"), hardwareIncidentTemplate: this.loadTemplate("hardwareIncident"), }; /** * The email transporter used to send emails. * @type {Object} */ const { systemEmailHost, systemEmailPort, systemEmailAddress, systemEmailPassword } = this.settingsService.getSettings(); const emailConfig = { host: systemEmailHost, port: systemEmailPort, secure: true, auth: { user: systemEmailAddress, pass: systemEmailPassword, }, }; this.transporter = this.nodemailer.createTransport(emailConfig); } /** * Asynchronously builds and sends an email using a specified template and context. * * @param {string} template - The name of the template to use for the email body. * @param {Object} context - The data context to render the template with. * @param {string} to - The recipient's email address. * @param {string} subject - The subject of the email. * @returns {Promise} A promise that resolves to the messageId of the sent email. */ buildAndSendEmail = async (template, context, to, subject) => { const buildHtml = async (template, context) => { try { const mjml = this.templateLookup[template](context); const html = await this.mjml2html(mjml); return html.html; } catch (error) { this.logger.error({ message: error.message, service: SERVICE_NAME, method: "buildAndSendEmail", stack: error.stack, }); } }; const sendEmail = async (to, subject, html) => { try { const info = await this.transporter.sendMail({ to: to, subject: subject, html: html, }); return info; } catch (error) { this.logger.error({ message: error.message, service: SERVICE_NAME, method: "sendEmail", stack: error.stack, }); } }; const html = await buildHtml(template, context); const info = await sendEmail(to, subject, html); return info?.messageId; }; } export default EmailService;