Refactored email service for modernity and clarity

This commit is contained in:
Alex Holliday
2024-07-19 14:35:50 -07:00
parent cb2961b9d6
commit 1c3c7e115a
2 changed files with 76 additions and 165 deletions

View File

@@ -1,26 +1,24 @@
const express = require("express");
const helmet = require("helmet");
const cors = require("cors");
const authRouter = require("./routes/authRoute");
const monitorRouter = require("./routes/monitorRoute");
const checkRouter = require("./routes/checkRoute");
const alertRouter = require("./routes/alertRoute");
const { connectDbAndRunServer } = require("./configs/db");
require("dotenv").config();
const logger = require("./utils/logger");
const { verifyJWT } = require("./middleware/verifyJWT");
const { handleErrors } = require("./middleware/handleErrors");
const authRouter = require("./routes/authRoute");
const monitorRouter = require("./routes/monitorRoute");
const checkRouter = require("./routes/checkRoute");
const alertRouter = require("./routes/alertRoute");
const pageSpeedCheckRouter = require("./routes/pageSpeedCheckRoute");
const { connectDbAndRunServer } = require("./configs/db");
const queueRouter = require("./routes/queueRoute");
const JobQueue = require("./service/jobQueue");
const pageSpeedCheckRouter = require("./routes/pageSpeedCheckRoute");
const nodemailer = require("nodemailer");
const emailService = require("./service/emailService");
const EmailService = require("./service/emailService");
// Need to wrap server setup in a function to handle async nature of JobQueue
const startApp = async () => {
// const { sendEmail } = require('./utils/sendEmail')
// **************************
// Here is where we can swap out DBs easily. Spin up a mongoDB instance and try it out.
// Simply comment out the FakeDB and uncomment the MongoDB or vice versa.
@@ -42,12 +40,6 @@ const startApp = async () => {
? DB_TYPE[process.env.DB_TYPE]()
: require("./db/FakeDb");
/**
* NOTES
* Email Service will be added
* Logger Service will be added (Winston or similar)
*/
const app = express();
// middlewares
@@ -62,11 +54,12 @@ const startApp = async () => {
// Make DB accessible anywhere we have a Request object
// By adding the DB to the request object, we can access it in any route
// Thus we do not need to import it in every route file, and we can easily swap out DBs as there is only one place to change it
// Same applies for JobQueue
// Same applies for JobQueue and emailService
// **************************
app.use((req, res, next) => {
req.db = db;
req.jobQueue = jobQueue;
req.emailSerivce = emailSerivce;
next();
});
@@ -90,39 +83,21 @@ const startApp = async () => {
}
});
// Nodemailer code here
const transporter = nodemailer.createTransport({
host: process.env.EMAIL_SERVICE_HOST,
port: process.env.EMAIL_SERVICE_PORT,
auth: {
user: process.env.EMAIL_SERVICE_USERNAME,
pass: process.env.EMAIL_SERVICE_PASSWORD,
},
});
app.use("/api/v1/mail", (req, res) => {
console.log("Started");
// Replacing varibales
const context = { name: "Alex" };
// Define mail options
const mailOptions = {
from: "BlueWave Uptime <bluewaveuptime@gmail.com>", // sender address
to: "muhammadkhalilzadeh1998@gmailc.com", // list of receivers
subject: "Testing template emails", // Subject line
html: emailService.sendWelcomeEmail(context), // html body
};
// Send mail with defined transport object
transporter.sendMail(mailOptions, (error, info) => {
if (error) {
return res
.status(500)
.send({ message: "Error sending email", error: error });
}
console.log(info);
res.status(200).send({ message: "Email sent successfully", info: info });
});
app.use("/api/v1/mail", async (req, res) => {
try {
const id = await req.emailSerivce.buildAndSendEmail(
"welcomeEmailTemplate",
{
name: "Alex",
},
"ajhollid@gmail.com",
"Welcome"
);
res.status(200).json({ success: true, msg: "Email sent", data: id });
} catch (error) {
logger.error(error.message);
return res.status(500).json({ message: error.message });
}
});
/**
@@ -131,8 +106,10 @@ const startApp = async () => {
*/
app.use(handleErrors);
// Create services
await connectDbAndRunServer(app, db);
const jobQueue = await JobQueue.createJobQueue(db);
const emailSerivce = new EmailService();
const cleanup = async () => {
console.log("Shutting down gracefully");

View File

@@ -1,125 +1,59 @@
const fs = require("fs");
const path = require("path");
const nodemailer = require("nodemailer");
const { compile } = require("handlebars");
const { mjml2html } = require("mjml");
// Fetching Templates
class EmailService {
constructor() {
this.loadTemplate = (templateName) => {
const templatePath = path.join(
__dirname,
`../templates/${templateName}.mjml`
);
const templateContent = fs.readFileSync(templatePath, "utf8");
return compile(templateContent);
};
// Welcome Email Template
const welcomeEmailTemplatePath = path.join(
__dirname,
"../templates/welcomeEmail.mjml"
);
const welcomeEmailTemplateContent = fs.readFileSync(
welcomeEmailTemplatePath,
"utf8"
);
const welcomeEmailTemplate = compile(welcomeEmailTemplateContent);
// 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"),
};
// Employee Activation Email Template
const employeeActivationTemplatePath = path.join(
__dirname,
"../templates/employeeActivation.mjml"
);
const employeeActivationTemplateContent = fs.readFileSync(
employeeActivationTemplatePath,
"utf8"
);
const employeeActivation = compile(employeeActivationTemplateContent);
this.transporter = nodemailer.createTransport({
host: process.env.EMAIL_SERVICE_HOST,
port: process.env.EMAIL_SERVICE_PORT,
secure: true, // Use `true` for port 465, `false` for all other ports
auth: {
user: process.env.EMAIL_SERVICE_USERNAME,
pass: process.env.EMAIL_SERVICE_PASSWORD,
},
});
}
// No Incident This Week Template
const noIncidentsThisWeekTemplatePath = path.join(
__dirname,
"../templates/noIncidentsThisWeek.mjml"
);
const noIncidentsThisWeekTemplateContent = fs.readFileSync(
noIncidentsThisWeekTemplatePath,
"utf8"
);
const noIncidentsThisWeek = compile(noIncidentsThisWeekTemplateContent);
buildAndSendEmail = async (template, context, to, subject) => {
const buildHtml = (template, context) => {
const mjml = this.templateLookup[template](context);
const html = mjml2html(mjml);
return html;
};
// Server is Down Template
const serverIsDownTemplatePath = path.join(
__dirname,
"../templates/serverIsDown.mjml"
);
const serverIsDownTemplateContent = fs.readFileSync(
serverIsDownTemplatePath,
"utf8"
);
const serverIsDown = compile(serverIsDownTemplateContent);
// Server is Up Template
const serverIsUpTemplatePath = path.join(
__dirname,
"../templates/serverIsUp.mjml"
);
const serverIsUpTemplateContent = fs.readFileSync(
serverIsUpTemplatePath,
"utf8"
);
const serverIsUp = compile(serverIsUpTemplateContent);
// Password Reset Template
const passwordResetTemplatePath = path.join(
__dirname,
"../templates/passwordReset.mjml"
);
const passwordResetTemplateContent = fs.readFileSync(
passwordResetTemplatePath,
"utf8"
);
const passwordReset = compile(passwordResetTemplateContent);
// *** Application specific functions ***
function sendWelcomeEmail(context) {
const mjml = welcomeEmailTemplate(context);
const html = mjml2html(mjml);
return html;
const sendEmail = async (to, subject, html) => {
const info = await this.transporter.sendMail({
to: to,
subject: subject,
html: html,
});
return info;
};
const info = await sendEmail(to, subject, buildHtml(template, context));
return info.messageId;
};
}
function sendEmployeeActivationEmail(context) {
const mjml = employeeActivation(context);
const html = mjml2html(mjml);
return html;
}
function sendNoIncidentsThisWeekEmail(context) {
const mjml = noIncidentsThisWeek(context);
const html = mjml2html(mjml);
return html;
}
function sendServerIsDownEmail(context) {
const mjml = serverIsDown(context);
const html = mjml2html(mjml);
return html;
}
function sendServerIsUpEmail(context) {
const mjml = serverIsUp(context);
const html = mjml2html(mjml);
return html;
}
function sendPasswordResetEmail(context) {
const mjml = passwordReset(context);
const html = mjml2html(mjml);
return html;
}
module.exports = {
sendWelcomeEmail,
sendEmployeeActivationEmail,
sendNoIncidentsThisWeekEmail,
sendServerIsDownEmail,
sendServerIsUpEmail,
sendPasswordResetEmail,
};
module.exports = EmailService;