Use Handlebars To Send Great Emails From Node Applications

Alexander Paterson
|
Posted about 6 years ago
|
5 minutes
Use Handlebars To Send Great Emails From Node Applications
Face it: your applications have to send emails. Handlebars is a simple templating engine

Your node application has to send an email: here's how to write a template and achieve that goal.

Sending Email

I'll start with a simple function that sends an email using a MailOptions object (which I'll explain in a minute) using nodemailer(@2.4.2)

// mailers/sendMail.js

var nodemailer = require('nodemailer'),
    logger = require('../services/logger');

// Assumes we use gmail
var transporter = nodemailer.createTransport({
  host: process.env.EMAIL_SERVER,
  port: 465,
  auth: {
    user: process.env.EMAIL_USERNAME,
    pass: process.env.EMAIL_PASSWORD
  }
});

module.exports = (mailOptions) => {
  transporter.sendMail(mailOptions, function(err, info) {
    if (err) return logger.log('error', JSON.stringify(err), {tags: 'email'});
    if (info) return logger.log('info', JSON.stringify(info), {tags: 'email'});
    return null;
  });
};

A Password Reset Email

Now, say we want to send a password-reset email to a user. We need a template for the email, and a function which will process the template with some local variables, and call sendMail with some valid options. Here's that function:

// mailers/sendPasswordReset/index.js

var fs = require('fs'),
    path = require('path'),
    Handlebars = require('handlebars'),
    PASSWORD_RESET_URL = require('../../constants').PASSWORD_RESET_URL,
    logger = require('../../services/logger'),
    sendMail = require('../sendMail');

// Open template file
var source = fs.readFileSync(path.join(__dirname, 'password-reset.hbs'), 'utf8');
// Create email generator
var template = Handlebars.compile(source);

var options = (email, locals) => {
  logger.log('info', `Sending password reset email to ${email}.`, {tags: 'email'});
  return {
    from: `"MyApplication" <${process.env.EMAIL_FROM_ADDR}>`,
    to: email,
    subject: 'MyApplication | Password Reset',
    html: template(locals) // Process template with locals - {passwordResetAddress}
  };
};

module.exports = (user) => {
  var passwordResetAddress = PASSWORD_RESET_URL(user.passwordResetToken, user._id);
  return sendMail(options(user.email, {passwordResetAddress}));
}

Here I'm using fs to open the email template and handlebars(@4.0.6) to turn it into a generator called template. template simply takes an object containing variables we will inject into our template.

The Template

Handlebars template syntax is somewhat idiosyncratic, so read about it here. Fortunately we're just injecting a single variable, passwordResetAddress, so we won't have much trouble. We use double-brackets to inject local variables passed to our email generator (template).

// mailers/sendPasswordReset/password-reset.hbs

<html>
<head><title>Reset Your Password</title></head>
<body>
  <h1>Click this!</h1>
  <p>
    <a href="{{passwordResetAddress}}">{{passwordResetAddress}}</a>
  </p>
</body>
</html>

And it's as easy as that.

Usage

Here's our mailer being used in a controller:

var mongoose = require('mongoose'),
    User = mongoose.model('user'),
    sendPasswordReset = require('../mailers').sendPasswordReset;

exports.triggerPasswordResetEmailSend = function(req, res, next) {
  var email = req.body.email;
  if (!email) return res.status(422).json({error: "Please provide an email."});
  User.findOne({email}, function(err, user) {
    if (err) return next(err);
    if (!user) return res.status(404).json({error: "User not found."});
    sendPasswordReset(user);
    return res.json({});
  });
}

Improvements

You might have noticed our application stores our email template in memory. For a large application, your email templates could add up to megabytes of memory usage, at which point I would consider writing a seperate API for this service. It could live on a private subnet of your VPC, and accept requests from your application over HTTP. This way, if you scale up your public API, you're not wasting RAM storing excessive duplicate email templates in memory. 



-->
ALEXANDER
PATERSON