Introduction
I love contributing to OSS, and I recently received a task in one of the projects of Team Shiksha, where I had to send emails to the product's clients using our custom templates with a personalized look and feel. This post is about how to customize emails using different templates.
Creating Email Templates
Sending emails using Express is not a big deal, and almost every other project uses Nodemailer for that. We too, were using the same, but we were just sending text through it.
The first step here was to create different templates for different flows in the application and then send the email through the HTML property of the transport object.
So, I did the same. Created a few templates in simple HTML and CSS and added them as a string in my transport object like below:
(The process core module of Node.js provides the
env
property which hosts all the environment variables that were set when the process was started. Read more here: How to read environment variables from Node.js)
sendEmail function
async function sendEmail(email, subject, text) {
try {
const transporter = nodemailer.createTransport({
host: process.env.EMAIL_HOST,
service: process.env.EMAIL_SERVICE,
port: Number(process.env.EMAIL_PORT),
secure: true,
auth: {
user: process.env.EMAIL_USER,
pass: process.env.EMAIL_PASS,
},
});
await transporter.sendMail({
from: process.env.EMAIL_USER,
to: email,
subject: subject,
text: text,
html: text,
});
return { success: true };
} catch (error) {
console.error(error);
return { success: false, error: error };
}
}
A function to replace the variable in the HTML with the actual values
const getHTMLReplyForCustomer = (message, reply) => {
return `<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800;900&family=Lato:wght@700&family=Nunito&family=Poppins&display=swap" rel="stylesheet" />
<style>
*,
*::before,
*::after {
box-sizing: border-box;
list-style: none;
}
* {
padding: 0;
margin: 0;
font-family: Inter;
}
</style>
</head>
<body style="--white: rgb(255, 255, 255); --smooth-purple: rgb(79, 70, 229); --black: rgb(17, 24, 39); --smooth-gray: rgb(107 114 128); --description: rgb(75, 85, 99); --light-purple: rgba(79, 70, 229, 0.1); --faded-purple: rgba(79, 70, 229, 0.5); --border: 2px solid rgb(229, 231, 235); --bradius: 6px; --shadow: rgba(140, 152, 164, 0.125) 0px 6px 24px 0px; --small-text: 12px; --medium-text: 16px; --large-text: 20px; --gray-drag-drop-background: rgb(249, 250, 251); --error: rgb(255, 55, 55); --success: rgb(52, 168, 83); --table-heading-bg: rgb(226, 224, 224);">
<header style="padding: 20px; background-color: #f1f1f1;">
<a class="logo" href="https://logoexecutive.vercel.app/home" style="color: var(--black); cursor: pointer; text-decoration: none; display: flex; align-items: center; justify-content: center;">
<img src="https://logoexecutive.vercel.app/static/media/business-man-logo.3a122f718b0294570293.webp" style="height: 35px; width: 35px; margin-right: 10px;" />
<h2 style="font-size: 24px; font-weight: 800;">LogoExecutive</h2>
</a>
</header>
<main style="padding: 30px;">
<h4 style="font-weight: 400;">
Hi there,
<br />
<br />
Thanks for connecting with us through our contact-us form. We have prepared a response based on your query:
<br />
<br />
<b>Query:</b>
<br />
${message}
<br />
<br />
<b>Reply:</b>
<br />
${reply}
<br />
<br />
<br />
Thanks,
<br />
OpenLogo Team
</h4>
</main>
<footer class="footer" style="width: 100%; background-color: #f1f1f1; display: flex; padding: 12px 0; position: fixed; left: 0; bottom: 0; width: 100%;">
<section class="footer-child" style="padding: 12px; display: flex; justify-content: center; flex-direction: column; align-items: center; gap: 20px; flex-grow: 1; max-width: 500px; text-align: center; margin: 0 auto;">
<div class="footer-child-heading-container" style="display: flex; gap: 16px;">
<h4 style="font-size: var(--large-text); color: var(--black);">Copyright © 2024 | OpenLogo</h4>
</div>
<section class="poweredBy" style="display: flex; align-items: center; justify-content: center; border: 2px solid var(--smooth-purple); border-radius: 6px; padding: 2px 6px; cursor: pointer; color: var(--smooth-purple); font-weight: 700; font-size: var(--medium-text);">Powered By<img src="https://logoexecutive.vercel.app/static/media/teamshishalogo.7bfb117958cfe1b031c9.webp" alt="TeamShiksha Logo" style="margin-left: 5px; width: auto; height: 18px;"></section>
</section>
</footer>
</body>
</html>`;
};
Using sendEmail
const htmlReply = getHTMLReplyForCustomer(revertForm.message, reply);
const emailRes = await sendEmail(
revertForm.email,
"Response for your query at LogoExecutive",
htmlReply
);
But with this implementation, I faced two issues:
Email template was not consistent across different email clients.
Look at this email I got on the outlook:
And now this is what I got on the gmail:
Separation of concerns was not achieved fully, as the same file had the code for HTML and JavaScript.
So, now I had to first figure out how to create templates that behave the same for all the email clients. And then find a way to separate HTML templates from JavaScript code.
Creating consistent templates
After playing around and digging the internet, I found out that the issue is with how email clients render the HTML. And the solution is to use a table structure to have seamless and consistent behavior across different clients (Btw, this post by Fanny helped me a lot in creating the templates).
So, I created the templates using table structures and voila 🎉 It worked like a charm! See the below samples:
Separating HTML templates from the JS code
As suggested by a teammate, I found out that I'll need to create the template in a different file and then replace the variables in it using some utility. So, again after searching for some packages, I figured that Handlebars would be the best solution for our problem.
I created the template in a different file and used the replacements to replace the variables in the HTML, and it worked like a perfectly:
HTML Template
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>OpenLogo Email</title>
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link
href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800;900&family=Lato:wght@700&family=Nunito&family=Poppins&display=swap"
rel="stylesheet" />
</head>
<body style="margin: 0; padding: 0; font-family: Inter, Arial, sans-serif; box-sizing: border-box;">
<table width="100%" cellpadding="0" cellspacing="0"
style="background-color: #ffffff; max-width: 675px; margin: 0 auto; border: 1px solid #ddd; box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);">
<tr>
<td style="padding: 20px; background-color: #f1f1f1; margin: 0 auto; text-align: center">
<a href="https://logoexecutive.vercel.app/home" style="color: rgb(17, 24, 39); text-decoration: none;">
<img src="https://logoexecutive.vercel.app/static/media/business-man-logo.3a122f718b0294570293.webp"
alt="Logo" style="height: 35px; width: 35px; margin-right: 10px; vertical-align: bottom;">
<span style="font-size: 24px; font-weight: 800;">OpenLogo</span>
</a>
</td>
</tr>
<tr>
<td style="padding: 50px 20px;">
<h4 style="font-weight: 400; margin: 0;">
Hi there,
<br><br>
Thanks for connecting with us through our contact-us form. We have prepared a response based on your question
(<b><i>{{message}}</i></b>) below:
<br><br>
<i>{{reply}}</i>
<br><br>
Thanks,<br>
OpenLogo Team
</h4>
</td>
</tr>
<tr>
<td style="background-color: #f1f1f1; padding: 12px; text-align: center;">
<table width="100%" cellpadding="0" cellspacing="0">
<tr>
<td style="padding-bottom: 10px;">
<p style="font-size: 14px; color: #777; margin: 0;">
Copyright © 2024 | OpenLogo
</p>
</td>
</tr>
<tr>
<td>
<div
style="display: inline-block; border: 2px solid rgb(79, 70, 229); border-radius: 6px; padding: 2px 6px;">
<a href="https://team.shiksha/" target="_blank" rel="noopener"
style="text-decoration: none; color: rgb(79, 70, 229); font-weight: 700; font-size: 16px; display: flex; align-items: center;">
Powered By
<img src="https://logoexecutive.vercel.app/static/media/teamshishalogo.7bfb117958cfe1b031c9.webp"
alt="TeamShiksha Logo" style="margin-left: 5px; width: auto; height: 18px;">
</a>
</div>
</td>
</tr>
<tr>
<td style="padding-top: 10px;">
<p style="font-size: 10px; color: #777; margin: 0;">
This is an automated message from OpenLogo. Please do not reply to this email.
</p>
</td>
</tr>
</table>
</td>
</tr>
</table>
</body>
</html>
Using sendEmail
const htmlFile = fs.readFileSync(
__dirname + "/../../templates/RevertEmail.html",
"utf-8"
).toString();
const template = handlebars.compile(htmlFile);
const replacements = {
message: revertForm.message,
reply
};
const htmlBody = template(replacements);
const emailRes = await sendEmail(
revertForm.email,
"Response for your query at LogoExecutive",
htmlBody
);
And this is all we need. We've not only created consistent templates, but we've separated the HTML and Javascript code.
So, this is how we create our own customized e-mail templates and send them in the e-mail!
P.S. Let me know if you liked this post and if you have any suggestions in the comments. I'll keep posting about my learnings here. Also, do join Team Shiksha to contribute, learn, and enhance your skills as a software engineer.
Top comments (0)