Sending emails in Node.js

Sending emails in Node.js

A common feature in todays web application is to send emails, some examples are password recovery, promotions, order confirmation. Sending emails in node.js is as easy as pie, thanks to a library called Nodemail. In this tutorial, you will learn how to send emails and how to use email templates.

Getting started

Run the below commands in your terminal

npm init -y
npm i nodemailer express dotenv nodemailer-express-handlebars

Here we initialized our project and installed our dependencies nodemailer, express, dotenv and nodemailer-express-handlebars.

Dontenv will be used to store values to an smtp server, the benefits of using environment variables are we can swap out values without having to touch the code, and we can set up different values for different environments, for example, develop or production.

express Minimal web framework to speed up development.

nodemailer Send emails in a easy way.

nodemailer-express-handlebars Makes it possible to use templates with nodemailer.

Setup express

Next we will setup the starter code for express, creat a file called app.js with the following content.

require("dotenv").config()
const express = require("express")
const nodemailer = require("nodemailer")
const app = express()
const port = 3000

app.get('/', (req, res) => res.send("Send emails with node.js"))

app.listen(port, () => console.log(`app listening on port ${port}!`))

Open up your terminal and run node app.js. Here we set up a simple web application, you can now visit http://localhost:3000 and you should see the message "Send emails with node.js".

Create MailTraip account

Mailtrap is a service that lets you test sending emails. You will get credentials to an smtp server and when sending mail you can log in to a gui with an inbox and inspect all emails. Setup your account at Mailtrap.

Create .env file

In the root create a file called .env, replaces the values with the one you got when creating your Mailtrap account.

SMTP_HOST=smtp.mailtrap.io
SMTP_PORT=2525
SMTP_USER=""
SMTP_PASSWORD=""
FROM_EMAIL="shop@example.com"

Sending email

require("dotenv").config()
const express = require("express")
const nodemailer = require("nodemailer")
const app = express()
const port = 3000

const transporter = nodemailer.createTransport({
  host:  process.env.SMTP_HOST
  port:  process.env.SMTP_PORT,
  auth: {
    user:  process.env.SMTP_USER,
    pass:  process.env.SMTP_PASSWORD
  }
});

app.get('/', (req, res) => res.send("Send emails with node.js"))

app.listen(port, () => console.log(`app listening on port ${port}!`))

To send emails we need to create a transporter object and pass our environment variables for the smtp server. To find out about more possible configurations checkout Nodemailer documentation.

Now we just need to call the sendEmail method from the transporter object and pass it some information about the mail, for example, a title, some content, receiver and specify who is sending the email.

require("dotenv").config()
const express = require("express")
const nodemailer = require("nodemailer")
const app = express()
const port = 3000

const transporter = nodemailer.createTransport({
  host:  process.env.SMTP_HOST,
  port:  process.env.SMTP_PORT,
  auth: {
    user:  process.env.SMTP_USER,
    pass:  process.env.SMTP_PASSWORD
  }
})

app.get("/", async (req, res) => {
  const mailInfo = {
    from: '"John Doe 😎" <johndoe132@example.com>',
    to: "testemail333@example.com",
    subject: "Test email", 
    text: "Sending test email 123456",
  }
    try {
        await transporter.sendMail(mailInfo)
        res.send("email sent")
    } catch(e) {
        res.status(500).send("Something broke!")
    }
})

app.listen(port, () => console.log(`app listening on port ${port}!`))

Every time a user visits the / route an email will be sent. Restart the node server and visit http://localhost:3000 . If you now login into your Mailtrap account you should see an email in your inbox.

Mailtrap test email

Sending email with handlebars

Usually, an email will be a lot more complex. To make it easier we can use a template engine, in this example we will be using handlebars.

In the root create views folder with a layouts folder inside.

in /views/layouts create a file called main.hbs with the following content

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Shop</title>
</head>
<body>
    {{{body}}}
</body>
</html>

All template views will extend this layout. Next step in /views create a file called orderConfirmation.hbs with the following content.

<h1>Order Confirmation</h1>
<p>Thank you {{name}}, we received your order.</p>

<h2>Your order: {{orderId}} </h2> 
<h2>Total price: ${{price}}</h2>

You should now have this structure.

 ┣ 📂node_modules
 ┣ 📂views
 ┃ ┣ 📂layouts
 ┃ ┃ ┗ 📜main.hbs
 ┃ ┗ 📜orderConfirmation.hbs
 ┣ 📜.env
 ┣ 📜app.js
 ┣ 📜package-lock.json
 ┗ 📜package.json

The Last step is to configure nodemailer-express-handlebars

require("dotenv").config();
const express = require("express");
const nodemailer = require("nodemailer");
const hbs = require("nodemailer-express-handlebars");
const app = express();
const port = 3000;

const transporter = nodemailer.createTransport({
  host: process.env.SMTP_HOST,
  port: process.env.SMTP_PORT,
  auth: {
    user: process.env.SMTP_USER,
    pass: process.env.SMTP_PASSWORD
  }
});

const options = {
  viewEngine: {
    partialsDir: __dirname + "/views/partials",
    layoutsDir: __dirname + "/views/layouts",
    extname: ".hbs"
  },
  extName: ".hbs",
  viewPath: "views"
};

transporter.use("compile", hbs(options));
app.get("/", async (req, res) => {
  try {
    const order = {
      orderId: 948584,
      name: "Patrik",
      price: 50
    };

    const mailInfo = {
      from: "shop@example.com",
      to: "test943933@test.com",
      subject: "Order Confirmation",
      template: "orderConfirmation",
      context: order
    };

    await transporter.sendMail(mailInfo);

    res.send("email sent");
    
  } catch (e) {
    console.log(e);
    res.status(500).send("Something broke!");
  }
});

app.listen(port, () => console.log(`app listening on port ${port}!`));

We tell nodemailer-express-handlebars where to find our templates. To specify we want to use a template in the sendEmail method we can pass template as a key with the name of the template we wanna use. To pass variables to the template we supply the context object.

Restart the node server and go to http://localhost:3000, this email should now pop up in your mailbox.

Order Confirmation Email

Include attachments

Adding attachments is very straightforward all we have to do is update our mailInfo object with a key called attachments, it's an array of objects containing a path and filename key.

At the root of the project place an image called img.jpg, if you don't have any images on your computer feel free to download this image of a lovely cat.

Download cat image

Now update mailInfo with the following content.

const mailInfo = {
    to: "test943933@hotmail.com",
    subject: "Order Confirmation",
    template: "orderConfirmation",
    context: order,
    attachments: [
        {
            filename: "img.jpg",
            path: "./img.jpg"
        }
    ]
};

Restart the application and visit the home route you should now get a new email with the image included.

Create an email service

Everything is working great but there is a lot of boilerplate code every time we want to send an email. Let's create an email service. Create a file called MailService.js with the following content.

const nodemailer =  require("nodemailer");
const hbs = require("nodemailer-express-handlebars");

class MailService {
  constructor(host, port, user, password) {
    const options = {
      viewEngine: {
        partialsDir: __dirname + "/views/partials",
        layoutsDir: __dirname + "/views/layouts",
        extname: ".hbs"
      },
      extName: ".hbs",
      viewPath: "views"
    };

    this._transporter = nodemailer.createTransport({
      host: process.env.SMTP_HOST,
      port: process.env.SMTP_PORT,
      auth: {
        user: process.env.SMTP_USER,
        pass:  process.env.SMTP_PASSWORD
      }
    });

    this._transporter.use("compile", hbs(options));
  }
  sendMail({to, subject, template, context, attachments}) {
    return this._transporter.sendMail({
        to,
        from: process.env.FROM_EMAIL,
        subject,
        template,
        context,
        attachments
    });
  }
}

module.exports = MailService;

Import MailService in app.js and update the old code.

require("dotenv").config();
const MailService = require("./MailService");
const express = require("express");
const mailService = new MailService();
const app = express();
const port = 3000;

app.get("/", async (req, res) => {
  try {
    const order = {
      orderId: 948584,
      name: "Patrik",
      price: 50
    };

    const mailInfo = {
      to: "test943933@hotmail.com",
      subject: "Order Confirmation",
      template: "orderConfirmation",
      context: order,
       attachments: [
        {
          filename: "img.jpg",
          path: "./img.jpg"
        }
      ]
    };

    await mailService.sendMail(mailInfo);
    
    res.send("email sent");

  } catch (e) {
    res.status(500).send("Something broke!");
  }
});

app.listen(port, () => console.log(`app listening on port ${port}!`));

Conclusion

In this article you learned how to send emails with node.js, You also learned how to use handlebars as email templates. By using an EmailService class we can hide away some of the boilerplate code. To send emails in production just update the environment variables with your smtp server.