learn hapi — Sending Emails in Node.js

Sending emails from your application is a beneficial feature. In situations where a user signs up for your platform or wants to reset the password, use their attention. Welcome emails should have a single, targeted call to action.

The foundations of all the tactics to use the user’s attention is the actual email sending process. This is what you’ll implement in Futureflix. With the existing signup functionality, you’ll use the opportunity to send a welcoming email to your platform.

hapi Series Overview

Overview

Sending emails for your application doesn’t require a complete in- and outbound email handling. You don’t want to implement a Gmail clone. Make use of an email delivery service like Postmark, Mailgun, Sparkpost, SendGrid or any other.

Depending on the number of emails you’re going to send, a specific platform might have cost benefits over another.

Compare the platforms and ultimately decide on your favorite one. The next step is to sign up for an email delivery service.

Sign Up for an Email Delivery Service

There’s one thing you should know: you can use your personal email account to send emails from your Futureflix app. To go this way, use the SMTP credentials of your email account.

Using your private email account isn’t the best choice because you’re restricted in the number of emails to send within a given timeframe. The mail providers don’t want you to send transactional emails to another platform. In case you do, they may disable your account.

At the time of writing this tutorial, Postmark and Mailgun have either a free plan or credits included. We’re using Mailgun at Future Studio to send welcome emails to new Future Students.

Sign up for the service you prefer. The number of emails included in both services, Postmark and Mailgun, will hopefully last for some time. At least to get a grasp on email sending.

Creating a Server at Postmark

For Futureflix, I’ve decided to check out Postmark and give it a spin 😁

With Postmark, you’re going through an onboarding process that requires a coworker on their side to approve you. If you don’t want to wait for this approval process, choose Mailgun.

At Postmark, you receive a credit volume of 25,000 emails. This will last for a while. Create a server at Postmark after the successful approval. This server is a representation of credentials that you’ll use to send emails.

Create a new Postmark server to send emails

I’m calling my Postmark server “Futureflix” and assign the orange color.

Name your Postmark server and assign a color

Click the „Create Server“ button to finish the process. You’ll see the created server and a code block on how to send your first email with Postmark. If you want to try the mail sending process, go ahead and use the cURL statement. It’s working!

Postmark Futureflix server to send emails

For Futureflix, you’re interested in the Postmark API token. You can see it already within the cURL statement. Navigate to the “Credentials” link in the sub-navigation and check the “Server API” section.

Postmark API token overview

There’s the API token that you’ll need in the next section. Alright, go ahead and create your mailer in Node.js and connect it with your Postmark account.

The Mailer Utility

The Futureflix starter kit comes with a set of utilities. One of them is the mailer which helps you sending emails to users directly from the project. This said, you don’t need to implement the mailer yourself, but you should know what it does.

You’ll use the Nodemailer package to send emails in Node.js. This library gives you a simple interface to send emails from your application. The single Nodemailer requirement is Node.js v6 or later.

Nodemailer builds on the concept of transports to send emails. The main transporter is SMTP. Besides that, there are dozens of transporters for services like Postmark, Mailgun, Sendgrid, Amazon’s Simple Email Service (Amazon SES). At this point, NPM hosts 60 nodemailer transport packages.

If you decided to go with another email service than Postmark, replace the nodemailer-postmark-transporter with the email service of your choice.

npm i -S nodemailer nodemailer-postmark-transport  

Nodemailer is written ES2015 (ES6), dependency-free, ships with Unicode support for emojis 👌, and does a great job of giving you a helping hand by hiding all technical depth.

Connect Nodemailer With Postmark

In the previous section, you’ve installed the nodemailer and nodemailer-postmark-transport packages. Here, connect both packages within the mailer utility. Speaking of connecting, the mailer uses the POSTMARK_API_KEY environment variable. Create it in your secrets.env file with your Postmark API key (from the screenshot above).

Again, if you’re with another email service than Postmark, adapt this solution. For SMTP, you need to define other environment variables, like MAIL_HOST, MAIL_PORT, MAIL_USER and MAIL_PASS.

secrets.env

# MongoDB
# the data store for everything :)
DATABASE=mongodb://localhost/futureflix

# Postmark API Key
# SMTP service to send emails from this project
POSTMARK_API_KEY=your-postmark-key  

With this Postmark API key, you’ll build a transporter to ultimately send emails.

The nodemailer-postmark-transport package connects to Postmark with the given API key. For the sending process, you need to provide the email’s recipient, subject, body, and attachments. This is the job of the mailer itself. Check it out in the following paragraph.

The Mailer

It’s time to jump right into the mailer utility. Navigate to the utils directory and open the mailer.js. The mailer isn’t a dedicated hapi plugin, although it could be. Keeping it outside a hapi plugin, you can adapt and even copy it to any other Node.js project 😃

The mailer executes the following steps:

  1. Read and render the email template
  2. Prepare the mail configuration: from and to recipients, subject line, mail body
  3. Send the email

At the file’s top (in the following code block), you’ll see the package imports and Transporter creation. The Postmark transporter requires the API key, taken from the environment variable. You’ve already added the variable to your secrets.env environment file.

The email templates can have placeholders for dynamic content. To render them, you’ll use the Handlebars template engine. This allows you to dynamically add user-specific data and give each mail a personal touch.

Got it, you want to see the mailer’s code 😉

utils/mailer.js

'use strict'

const Fs = require('fs')  
const Path = require('path')  
const Boom = require('boom')  
const Util = require('util')  
const Nodemailer = require('nodemailer')  
const Handlebars = require('handlebars')  
const htmlToText = require('html-to-text')  
const ReadFile = Util.promisify(Fs.readFile)  
const PostmarkTransport = require('nodemailer-postmark-transport')  
const Transporter = Nodemailer.createTransport(PostmarkTransport({  
  auth: {
    apiKey: process.env.POSTMARK_API_KEY
  }
}))
const Templates = Path.resolve(__dirname, '..', 'email-templates')

/**
 * filename: email template name, without ".html" file ending. Email templates are located within "server/email-templates"
 * options: data which will be used to replace the placeholders within the template
 **/
async function prepareTemplate (filename, options = {}) {  
  try {
    const templatePath = Path.resolve(Templates, `${filename}.html`)
    const content = await ReadFile(templatePath, 'utf8')

    // use handlebars to render the email template
    // handlebars allows more complex templates with conditionals and nested objects, etc.
    // this way we have much more options to customize the templates based on given data
    const template = Handlebars.compile(content)
    const html = template(options)

    // generate a plain-text version of the same email
    const text = HtmlToText.fromString(html)

    return {
      html,
      text
    }
  } catch (error) {
    throw new Boom('Cannot read the email template content.')
  }
}

exports.send = async (template, user, subject, data) => {  
  const { html, text } = await prepareTemplate(template, data)
  const mailOptions = {
    from: `Marcus Poehls <marcus@futurestud.io>`,
    to: user.email,
    subject: subject,
    html,
    text
  }

  try {
    await Transporter.sendMail(mailOptions)
  } catch (err) {
    console.log(err)
  })
}

The entry point to the Mailer is a named export called send. Calling Mailer.send(…) from a route handler in hapi will turn on the engines to render specific data into the template. The rendered HTML and text are part of the email configuration which Nodemailer requires to send the mail.

Make sure to update the from field in the mailOptions. In case you forget to update this field and use the current configuration, you might run into this error:

{ status: 422,
  message: 'The \'From\' address you supplied ("Marcus Poehls" <marcus@futurestud.io>) is not a Sender Signature on your account. Please add and confirm this address in order to be able to use it in the \'From\' field of your messages.',
  code: 400 }

Seeing this error? Update the from address to an allowed sender signature from your email service

Within the send method, the created transporter sends an email with the composed mailOptions. If you don’t specify a callback as the second argument for Nodemailer’s sendMail function, it returns a promise. You can see that there’s only a catch block to handle the error situation. For now, it’s ok to leave the control flow as is. You’ll get back to that in a later tutorial.

Before sending any email, you need a template. This is the next step, preparing the welcome email’s template.

Prepare the “Welcome” Email Template

The template is an important piece of your email puzzle. You want it to look nicely designed with good copy text and at the same time not being to marketing like. Also, there’s a wide variety of email clients you want to support so that your email displays on every app and display size.

The great news: you don’t have to create a template from scratch. Postmark supports you with nine templates for different occasions. Mailgun has three action templates, tested in popular email clients. All templates are responsive, well tested, and work fine on mobile and desktop.

We suggest taking the templates with inlined styles because they have better support in email clients.

Create the Email Template

For the welcome email, we take Postmark’s welcome HTML template and customize the content. This welcome.html template is already part of the Futureflix starter kit. Find it in server/email-templates. Because it’s an HTML file, you can open it in the browser to get a grasp on the styling.

Refine the Email Template’s Content

If you want to adjust the template’s content, wording, and styling: please go ahead 😉 Customize the template to your wishes.

Keep in mind that Google might put your email into Gmail’s promotions tab if it contains images. The provided template doesn’t have any image and should go straight to Gmail’s main tab. The promotions tab in Gmail has a negative impact on the open rates. Try your best to not land there.

Render Email Template and Fill with Data

You probably had a look at the welcome.html email template. If you didn’t, please open it now. You can also check it out on GitHub.

You’ll recognize the use of mustache tags, e.g. {{discoverURL}}. The reason for that: the mailer uses the Handlebars templating engine to dynamically render data into the template.

Put placeholders into your email templates and dynamically render data into them. The mailer utility passes your provided data object straight to the view. Handlebars takes care of replacing the related placeholders with the object’s data.

In the welcome.html template, there’s a button with a call to action. The link to this button is dynamic and you need to provide it while rendering. The placeholder in the email looks like this:

<a href="{{discoverURL}}" class="button button--" target="_blank" style="…">  
  Discover movies and TV shows
</a>  

Provide the {{discoverURL}} in the data object for this email template. You know what, this is the right time to integrate the welcome email dispatch into the signup flow.

Send the “Welcome” Email on Each Sign-Up

Your project is ready to send the welcome email to users to sign up. Navigate to the user-signup-login plugin within the server directory and open the handler.js file.

Import the mailer utility at the top of this handler.js file.

Find the signup object and extend the related handler. Remember the discoverURL for the action button within the welcome email? Depending on your development or production deployment, you might want to send a different URL. Use the host property within the request headers to define the host part of the URL.

Send the welcome email using the Mailer and define the parameters: template name, the user object, subject line and template data.

web/user-signup-login/handler.js

…
const Mailer = require('../utils/mailer')

…

signup: {  
  plugins: { 'hapi-auth-cookie': { redirectTo: false } },
  handler: (request, reply) => {
      // skipping to the interesting part
      // …

    try {
      const user = await user.save()
      request.cookieAuth.set({ id: user.id })

      const discoverURL = `http://${request.headers.host}/discover`
      Mailer.send('welcome', user, '📺 Futureflix — Great to see you!', { discoverURL })

      // \o/ wohoo, sign up successful
      return reply.view('signup-success')
    } catch (err) {
      …
    }
  },
  validate: { … }
}

At this point, the mailer integration is in style of “fire and forget”. You don’t integrate the promise of Mailer.send() in the signup promise chain. In case an error occurs while sending the email, the mailer logs the error and the request proceeds.

This design decision proceeds with the actual sign up and doesn’t prioritize the welcome email over the account creation.

A Word of “Email Sending Wisdom” 😌

The implementation in this tutorial doesn’t wait for Postmark to finish its operation. You’re not making sure the email is actually sent to the user. The reason is to favor the successful signup and guide the user on your platform instead of showing an error related to a welcome email that wasn’t delivered.

If you integrate the mail sending process into the signup promise chain and the Postmark servers are unresponsive or unavailable, the signup process would fail and return an error. You don’t want a signup error if the user account is created in MongoDB. The thing that should happen in this situation is a delayed welcome mail, but a user should see their successful signup.

You’ll change this behavior of email sending in a later tutorial. You’ll use a queue and integrate the email delivery as a queued job. Another benefit of handling your emails via queues is the free retries for failed sending attempts. The queuing system handles errors the way you want it to be, like a delayed retry.

Register a User and Receive the Welcome Email

Yeah, it’s time to test the implementation. Start your Futureflix server and navigate to the signup page. Register a user with an email address that you’ve access to. In case you’ve already registered a user with your email address, delete the one from MongoDB with Robo 3T and sign up again.

Preview of the Futureflix Welcome Email

This is what the welcome email looks like in Postbox. A call to action in the welcome email is a good chance to navigate users to specific pages in your app. Use the user’s attention for good.

Your Tasks

Before moving on to the next tutorial, please make sure you can check off the following tasks:

  • [ ✓ ] Sign up for a transactional email service
  • [ ✓ ] Install the nodemailer and nodemailer-postmark-transport dependencies. If you’re using another service, use the related transporter
  • [ ✓ ] Configure the environment variable(s) to store secrets for the email service
  • [ ✓ ] Get to know and adjust the welcome email template
  • [ ✓ ] Implement the mail sending process for a welcome email in your sign up handler
  • [ ✓ ] Test the mailer by signing up for your Futureflix with your personal email

Proceed to the next lesson once you’ve completed all tasks.

Next Lesson

Sending emails from your Node.js application is a great way to react to specific events. Like, send a personalized welcome email on each sign up gives you the opportunity to ask questions or talk to the user. It’s a gateway to the inbox that you can use for good.

Don’t send emails to spam the user’s inbox. Make it comfortable to receive your emails, not work.


Mentioned Resources

Explore the Library

Find interesting tutorials and solutions for your problems.