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

npm install @sendgrid/mail

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:

twilio.com. Each item can be a string email or an object { email, name }.

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:

  1. Prepare an HTML template (e.g. with {{placeholders}}).
  2. Load the template in Node (using fs.readFileSync).
  3. Replace placeholders with actual values in the string.
  4. Use SendGrid’s Node library (@sendgrid/mail) to send the email (setting to, from, subject, and the html content). We also showed how to send to multiple recipients by giving to an array.
  5. 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.

Image Writen by Dylan Been