Sending HTML Emails with Node.js, Nodemailer, and SendGrid
Sending emails from a Node.js app is common for notifications, signups, newsletters, and more. Instead of plain text, you often want to send styled HTML emails (e.g. welcome messages, receipts, etc.). Node.js makes this easy with libraries like Nodemailer (for SMTP) or SendGrid’s official mail library. In this guide, we’ll show how to load an HTML template from a file, replace placeholders (like {{name}}
), and send the message to multiple recipients using SendGrid’s Node library (@sendgrid/mail
). We’ll also briefly note how you could use Nodemailer with SendGrid’s SMTP relay.
Prerequisites
- Node.js environment: Make sure you have Node.js installed (v18+).
- SendGrid account & API key: Sign up for SendGrid (free tier available) and create an API key. Also verify a sender (email or domain) in SendGrid so you can send from that address.
- Install packages: In your project, install the SendGrid mail library (and Nodemailer if you choose SMTP later):
npm install @sendgrid/mail
- Environment variables: Store your SendGrid API key in an environment variable (e.g.
process.env.SENDGRID_API_KEY
) so it’s not hardcoded. In Node, you can use dotenv or another method.
Loading an HTML Email Template
First, create an HTML file for your email (e.g. email-template.html
). This file can contain {{placeholder}}
tags where you want to insert dynamic data:
<!-- email-template.html -->
<!DOCTYPE html>
<html>
<body style="font-family: Arial, sans-serif;">
<h1>Hello {{name}},</h1>
<p>Thank you for signing up for {{app_name}}!</p>
<p>We’re excited to have you on board.</p>
</body>
</html>
Then, in your Node code, use the fs
module to read the file contents. For simplicity, we’ll use fs.readFileSync
, which blocks until the file is loaded (suitable for loading a small email template on startup)
const fs = require('fs');
// Load HTML template from file (synchronously for simplicity)
const htmlTemplate = fs.readFileSync('email-template.html', 'utf8');
According to Node.js docs, fs.readFileSync()
ensures the file’s content is available immediately after the call. (In production servers with many requests, you might prefer the async fs.readFile
method; for now, sync is fine.)
Replacing Placeholders in the Template
Our template uses {{name}}
and {{app_name}}
to mark where to insert values. We can define a simple function to replace these tags. For example, given an array of replacement objects like { placeholder: 'name', value: 'Alice' }
, we iterate and replace every instance:
/**
* Replace {{placeholders}} in the template with actual values.
* @param {string} template - The HTML template content.
* @param {Array} replacements - Array of { placeholder, value }.
* @returns {string} - The processed HTML with values inserted.
*/
function applyReplacements(template, replacements) {
let result = template;
for (const { placeholder, value } of replacements) {
// Create a global regex to replace all occurrences of {{placeholder}}
const regex = new RegExp(`{{\\s*${placeholder}\\s*}}`, 'g');
result = result.replace(regex, value);
}
return result;
}
// Example usage:
const replacements = [
{ placeholder: 'name', value: 'Alice' },
{ placeholder: 'app_name', value: 'AwesomeApp' }
];
const personalizedHtml = applyReplacements(htmlTemplate, replacements);
// personalizedHtml now has 'Alice' and 'AwesomeApp' filled in
This code handles all occurrences of each {{placeholder}}
tag by using a regular expression with the g
(global) flag. You could also use replaceAll
in modern Node, but regex works broadly. The applyReplacements
function keeps things simple: it takes the template string and an array of replacement objects, and returns the customized HTML.
Sending the Email with SendGrid
Now that we have the final HTML content, let’s send the email. We’ll use SendGrid’s official library @sendgrid/mail
, which provides a clean API for sending emails via the SendGrid service (instead of raw SMTP). First, initialize the library with your API key:
const sgMail = require('@sendgrid/mail');
sgMail.setApiKey(process.env.SENDGRID_API_KEY); // Set this from your env vars
As noted in examples, this sets the API key from an environment variable. Next, build the email message object. It should include:
to
: the recipient(s). SendGrid allows an array here for multiple recipients
twilio.com. Each item can be a string email or an object { email, name }
.
from
: your verified sender email.subject
: the email subject line.html
: the HTML body (our personalized template).
For example:
const msg = {
to: ['[email protected]', '[email protected]'], // multiple recipients
from: '[email protected]', // your verified sender
subject: 'Welcome to AwesomeApp!',
html: personalizedHtml,
};
According to the SendGrid API reference, the to
field can be an array of recipient objects (each with email
and optional name
)
twilio.com. Here we just use a simple string array for emails, which SendGrid also accepts. Finally, send the email:
async function sendEmail() {
try {
await sgMail.send(msg);
console.log('Email sent successfully');
} catch (error) {
console.error('Error sending email:', error);
if (error.response) {
console.error(error.response.body);
}
}
}
sendEmail();
This uses async/await
to send the email. If successful, you’ll see “Email sent successfully”; otherwise, errors (including detailed response) are logged. Putting it all together, here’s a complete example wrapped in a reusable function:
const fs = require('fs');
const sgMail = require('@sendgrid/mail');
// Initialize SendGrid API
sgMail.setApiKey(process.env.SENDGRID_API_KEY);
/**
* Replace placeholders in template and send email via SendGrid.
* @param {string} templatePath - File path to the HTML template.
* @param {Array} replacements - Array of { placeholder, value }.
* @param {Array<string>} toEmails - Array of recipient email addresses.
*/
module.exports = async function sendHtmlEmail(templatePath, replacements, toEmails) {
// Load and process the HTML template
const htmlTemplate = fs.readFileSync(templatePath, 'utf8');
let htmlContent = htmlTemplate;
for (const { placeholder, value } of replacements) {
const regex = new RegExp(`{{\\s*${placeholder}\\s*}}`, 'g');
htmlContent = htmlContent.replace(regex, value);
}
// Construct the email
const msg = {
to: toEmails, // can be an array of emails
from: '[email protected]', // your verified sender address
subject: 'Your AwesomeApp Welcome Email',
html: htmlContent,
};
// Send the email
try {
await sgMail.send(msg);
console.log(`Email sent to ${toEmails.join(', ')}`);
} catch (error) {
console.error('Error sending email:', error);
if (error.response) {
console.error(error.response.body);
}
}
};
Usage example: Call the function with your template and data:
const sendHtmlEmail = require('./sendHtmlEmail');
const templatePath = 'email-template.html';
const recipients = ['[email protected]', '[email protected]'];
const replacements = [
{ placeholder: 'name', value: 'Alice' },
{ placeholder: 'app_name', value: 'AwesomeApp' }
];
sendHtmlEmail(templatePath, replacements, recipients);
This will send the customized HTML email to both recipients, greeting them by name.
(Alternative) Using Nodemailer with SendGrid SMTP
While the above uses SendGrid’s API library, you can also use Nodemailer with SendGrid’s SMTP relay. For example, Nodemailer’s createTransport
can be configured as follows:
const nodemailer = require('nodemailer');
let transporter = nodemailer.createTransport({
host: 'smtp.sendgrid.net',
port: 587,
auth: {
user: 'apikey', // this literal string "apikey"
pass: process.env.SENDGRID_API_KEY, // your SendGrid API key
}
});
transporter.sendMail({
from: '[email protected]',
to: recipients, // array or comma-separated list
subject: 'Your Subject',
html: htmlContent,
}, (err, info) => {
if (err) {
console.error('Nodemailer error:', err);
} else {
console.log('Email sent with Nodemailer:', info.response);
}
});
This tells Nodemailer to use SendGrid’s SMTP (smtp.sendgrid.net
) on port 587. Note that the SMTP username is literally "apikey"
and the password is your actual API key. The HTML content (htmlContent
) can be the same string we generated earlier. Both approaches (API vs SMTP) achieve the same result; using the SendGrid library is often simpler and more direct.
Quick Tip: Designing HTML Emails
💡 Tip: Crafting HTML emails is notoriously tricky. Unlike web pages, most email clients do not support modern CSS. You usually must use inline styles and table-based layouts for reliable results.
For example, Gmail and Outlook often ignore <style>
blocks or CSS like flexbox. As one developer notes, “most email clients don't support a lot of CSS” and recommend using inline styles and tables.
If you find this hard, consider using a tool that converts your HTML/CSS into an email-friendly format. RebbidMail offers a generator that takes your styled HTML and outputs email-ready code. It inlines CSS, converts layouts to tables where needed, and optimizes for email clients. 👉 Try the RebbidMail Email Generator: Rebbidmail It can save a lot of hassle when building beautiful, compatible HTML emails.
Summary
In this post, we covered how to send HTML emails from Node.js using SendGrid. The key steps were:
- Prepare an HTML template (e.g. with
{{placeholders}}
). - Load the template in Node (using
fs.readFileSync
). - Replace placeholders with actual values in the string.
- Use SendGrid’s Node library (
@sendgrid/mail
) to send the email (settingto
,from
,subject
, and thehtml
content). We also showed how to send to multiple recipients by givingto
an array. - We mentioned Nodemailer as an alternative SMTP approach and noted the typical configuration.
With this setup, beginner and intermediate Node developers can send customized HTML emails (newsletters, notifications, etc.) with ease. Remember to keep your CSS simple and inline for best compatibility, and test your emails across major clients. And if email HTML coding gets complex, tools like RebbidMail can automate the conversion for you.
