Node.js: Sending Email Notifications Using Nodemailer and Gmail

Nodemailer

We all want to be in the loop and notified when certain events occur within our Node.js programs. For example, email notifications can be very important for creating situational awareness with IoT systems we develop that interact with our physical world. Email communication can be used to deliver messages to our inboxes as well as to deliver text messages in order to enable us to take more immediate action.

In today’s tutorial, we’ll walk through the steps of using the amazing Nodemailer package which has become the de facto standard for sending email messages in the Node.js world. Let’s get started so we can start seeing our own custom messages flow to our inbox!

Is the International Space Station Near Me?

Let’s continue our theme from the previous tutorial to utilize the ISS (International Space Station) as a “sensor” in the sky. As we read this “sensor”, we want to send a notification by email if the ISS is approaching our current location so we can walk outside and see it pass over us.

We’ll leverage a different API call this time around that enables us to supply our current coordinates and retrieve information on the next five times the ISS will pass over us. The following URL, for example, returns the next passes of the ISS based on San Diego coordinates: http://api.open-notify.org/iss-pass.json?lat=32.715738&lon=-117.161084

This URL returns the following JSON object:

{
  "message": "success", 
  "request": {
    "altitude": 100, 
    "datetime": 1476107333, 
    "latitude": 32.715738, 
    "longitude": -117.161084, 
    "passes": 5
  }, 
  "response": [
    {
      "duration": 506, 
      "risetime": 1476131595
    }, 
    {
      "duration": 631, 
      "risetime": 1476137299
    }, 
    {
      "duration": 469, 
      "risetime": 1476143206
    }, 
    {
      "duration": 209, 
      "risetime": 1476149222
    }, 
    {
      "duration": 362, 
      "risetime": 1476155044
    }
  ]
}

Using this API, we can write the following code to invoke a URL every two minutes and retrieve the next times the ISS will pass over us. We could probably back down the loop frequency, but we’ll keep it at two minutes to ensure we catch any changes to the ISS trajectory that may occur.

const got = require('got');
const moment = require('moment');

const loopSeconds = 120;
const myLoc = { latitude: 32.715738, longitude: -117.161084 };
const url = `http://api.open-notify.org/iss-pass.json?lat=${myLoc.latitude}&lon=${myLoc.longitude}`;

function loop() {
    got(url, { json: true })
        .then(iss => {
            const nextPasses = iss.body.response;
            const now = moment();
            console.log('Next ISS passes near me');
            for (const pass of nextPasses) {
                const passTime = moment.unix(pass.risetime);
                const timeFromNow = moment.duration(passTime.diff(now));
                const minutesFromNow = Number(timeFromNow.asMinutes()).toFixed(1);
                console.log(`${passTime} (in ${minutesFromNow} minutes) for ${pass.duration} seconds`);
            }
        })
        .catch(error => {
            console.log(error.response.body);
        });
    setTimeout(loop, loopSeconds * 1000);
}

loop();

To get this working, we will need to npm install both got to help us retrieve data from the web and moment to help us manipulate dates and times. We can install both of these at the same time using the following command:

$ npm install got moment

In our code, we utilize the incredible moment package to help us manipulate dates and times. This package should be part of the core Node.js built-in libraries, in my opinion, as it fills in the gap for the dearth of functionality that ships as part of the standard JavaScript date functions.

The risetime returned from the API returns a Unix timestamp which represents the number of seconds that have elapsed since the Unix Epoch which is 00:00:00 Coordinated Universal Time (UTC), Thursday, 1 January 1970. We will run out of seconds in the year 2038, but I digress. 🙂 We use the moment.unix function to convert the Unix timestamp to a standard Moment timestamp that we are able manipulate in order to determine when the ISS will pass over us next.

Tell Me When the ISS Is within 30 Minutes of Passing over Me

Let’s modify our code so we can be notified when it is within 30 minutes of passing over us:

const got = require('got');
const moment = require('moment');

const loopSeconds = 120;
const myLoc = { latitude: 32.715738, longitude: -117.161084 };
const url = `http://api.open-notify.org/iss-pass.json?lat=${myLoc.latitude}&lon=${myLoc.longitude}`;

const warningMinutes = 30;
let providedNotification = false;

function sendNotification(message) {
    console.log(`Could send email here: ${message}`);
}

function loop() {
    got(url, { json: true })
        .then(iss => {
            const nextPasses = iss.body.response;
            const now = moment();
            const pass = nextPasses[0];
            const passTime = moment.unix(pass.risetime);
            const timeFromNow = moment.duration(passTime.diff(now));
            const minutesFromNow = Number(timeFromNow.asMinutes()).toFixed(1);
            console.log(`${passTime} (in ${minutesFromNow} minutes) for ${pass.duration} seconds`);

            if (minutesFromNow <= warningMinutes) { if (!providedNotification) { sendNotification(`The ISS will pass over in ${minutesFromNow} minutes`); providedNotification = true; } } else { providedNotification = false; } }) .catch(error => {
            console.log(error.response.body);
        });
    setTimeout(loop, loopSeconds * 1000);
}

loop();

In this new iteration, we only analyze the next time the ISS will pass over us. We also send a notification if it is within 30 minutes of passing over us. You will also see that we have now created a sendNotification function as a placeholder that we will ultimately use to send email messages. If we have already invoked the sendNotication function, we don’t invoke it again. We only want to be notified once rather than bombarding our inbox with email messages every two minutes!

I Want to Send Email Using Node.js – Nodemailer is Your Ticket!

Now that we have the foundational code in place, we are ready to start sending email with the help of Nodemailer!

For this exercise, we will be using Gmail. Sending email by Gmail with Nodemailer is not the most secure option in the world since your password is sent in plaintext so it can be intercepted using network sniffers. For this reason, I recommend that you create a test Gmail account for this exercise. There are other email options that we can use in production applications such as Mailgun; however, they require a lot more ceremony to set up, and Gmail works great as a testing platform to understand the mechanics of sending email messages with Nodemailer.

After creating a test Gmail account, you will need to configure it to allow less secure apps to access your account. Nodemailer also provides some additional notes on using Gmail.

Next, we will install the Nodemailer npm package as follows:

npm install nodemailer

Create Simple Email Test Program

Let’s go ahead and create some code in a file called send-email-test.js that we can use to verify that we are able to send email messages with Nodemailer:

const nodemailer = require('nodemailer');

const transport = nodemailer.createTransport({
    service: 'Gmail',
    auth: {
        user: 'thisdavejdemo@gmail.com',
        pass: 'myGmailPassword',
    },
});

const mailOptions = {
    from: 'thisdavejdemo@gmail.com',
    to: 'thisdavejdemo@gmail.com',
    subject: 'hello world!',
    html: 'hello world!',
};

transport.sendMail(mailOptions, (error, info) => {
    if (error) {
        console.log(error);
    }
    console.log(`Message sent: ${info.response}`);
});

Substitute my email address and password with your email and password and then invoke it from the command line:

$ node send-email-test.js

Hopefully you should receive an email message!

Create General Purpose Nodemailer Module

Let’s go ahead and create a general purpose module that we can use internally to send email messages. First, create a file called send-email.js with the following content:

const nodemailer = require('nodemailer');

const transport = nodemailer.createTransport({
    service: 'Gmail',
    auth: {
        user: 'thisdavejdemo@gmail.com',
        pass: 'myGmailPassword',
    },
});

module.exports = function sendEmail(to, subject, message) {
    const mailOptions = {
        from: 'thisdavejdemo@gmail.com',
        to,
        subject,
        html: message,
    };

    transport.sendMail(mailOptions, (error) => {
        if (error) {
            console.log(error);
        }
    });
};

This time around, we create a general purpose sendEmail function that enables us to specify a recipient (or recipients if separated by commas), a subject, and a message body. We export this function so it can be used with a require statement in our Nodej.js programs.

Let’s go ahead and create a program to ensure that our module works:

const sendEmail = require(‘./send-email2’);

sendEmail('thisdavej@gmail.com', 'Test subject', 'Test message');

There’s not a lot of code required here to invoke our module. Node modules are a great way to encapsulate functionality! Go ahead and invoke this code to confirm it works.

Encrypt your Gmail Password in Node.js Code

While we can’t encrypt our Gmail password as it travels over the wire on the Internet, we can apply some due diligence to encrypt it within our Node.js code. This will enable us to publish sample code to Github and other places without revealing our Gmail credentials to the world. To accomplish this objective, we utilize the crypto module that ships as part of the standard Node.js core API.

Create Encryption Module

Let’s create a file called crypt.js that uses reversible encryption so that we can encrypt our GMail password and decrypt it later using the same password (key):

const crypto = require('crypto');

const algorithm = 'aes-256-ctr';
const password = process.env.cryptokey || 'secretPassword';

console.log(password);

function encrypt(text) {
    const cipher = crypto.createCipher(algorithm, password);
    let crypted = cipher.update(text, 'utf8', 'hex');
    crypted += cipher.final('hex');
    return crypted;
}

function decrypt(text) {
    const decipher = crypto.createDecipher(algorithm, password);
    let dec = decipher.update(text, 'hex', 'utf8');
    dec += decipher.final('utf8');
    return dec;
}

exports.encrypt = encrypt;
exports.decrypt = decrypt;

Notice that we provide the capability for the password to be supplied as an environment variable using process.env.cryptokey. If the user supplies an environment variable called cryptokey, it will be used instead of ‘secretpassword’. This enables us to avoid embedding our real password in the source code. We will explain how the environment variable is used in the next section.

Test Encryption Module

Perfect! Let’s now create a file called crypt-test to verify that our reversible encryption is functioning properly:

const crypt = require('./crypt.js');

const encPassword = crypt.encrypt('myGmailPassword');
console.log(encPassword);

console.log(crypt.decrypt(encPassword));

Let’ invoke our test program as follows:

$ node crypt-test.js

You should see the encrypted version of your Gmail password and the decrypted version of your Gmail password. This little test program is also handy since you can copy and paste the encrypted version of your Gmail password into the code we will write next in the next section.

If we want to supply a different encryption password as an environment variable (for example, mySecretKey, we can use the following command syntax from Linux/OS X:

$ cryptokey=newSecretKey node crypt-test.js

For Windows, we accomplish the goal like this:

C:/> set cryptokey=newSecretKey
C>/> node crypt-test.js

For real applications, you would probably create a shell script or a batch file to accomplish the goal in a more automated fashion.

Utilize Encryption Module with Nodemailer

Let’s modify our Nodemailer module (send-email.js) to utlize our crypt module so we can conceal our Gmail password:

const nodemailer = require('nodemailer');
const crypt = require('./crypt');

const encryptedPassword = '126891c03fef8f5fc1e1d8714d82bb';

const transport = nodemailer.createTransport({
    service: 'Gmail',
    auth: {
        user: 'thisdavejdemo@gmail.com',
        pass: crypt.decrypt(encryptedPassword),
    },
});

module.exports = function sendEmail(to, subject, message) {
    const mailOptions = {
        from: 'thisdavejdemo@gmail.com',
        to,
        subject,
        html: message,
    };

    transport.sendMail(mailOptions, (error) => {
        if (error) {
            console.log(error);
        }
        // console.log(`Message sent: ${info.response}`);
    });
};

Our Gmail password is now concealed in the code and it is decrypted just in time to be used by Nodemailer!

The Final Product

We are ready to put together the final product so we can be notified by email when the ISS is within 30 minutes of our GPS coordinates! Here we go:

const got = require('got');
const moment = require('moment');
const sendEmail = require('./send-email');

const loopSeconds = 120;
const myLoc = { latitude: 32.715738, longitude: -117.161084 };
const url = `http://api.open-notify.org/iss-pass.json?lat=${myLoc.latitude}&lon=${myLoc.longitude}`;

const warningMinutes = 30;
let providedNotification = false;

function sendNotification(message) {
    sendEmail('thisdavej@gmail.com', 'ISS notification', message);
    console.log('Email sent');
}

function loop() {
    got(url, { json: true })
        .then(iss => {
            const nextPasses = iss.body.response;
            const now = moment();
            const pass = nextPasses[0];
            const passTime = moment.unix(pass.risetime);
            const timeFromNow = moment.duration(passTime.diff(now));
            const minutesFromNow = Number(timeFromNow.asMinutes()).toFixed(1);
            console.log(`${passTime} (in ${minutesFromNow} minutes) for ${pass.duration} seconds`);

            if (minutesFromNow <= warningMinutes) { if (!providedNotification) { sendNotification(`The ISS will pass over in ${minutesFromNow} minutes.`); providedNotification = true; } } else { providedNotification = false; } }) .catch(error => {
            console.log(error.response.body);
        });
    setTimeout(loop, loopSeconds * 1000);
}

loop();

As you can see, after we “require” our send-email module, we are able to send an email using a very simple function:

sendEmail('thisdavej@gmail.com', 'ISS notification', message);

We could also send an email to more than one recipient by invoking the sendEmail function as follows:

sendEmail('thisdavej@gmail.com, john@doe.com', 'ISS notification', message);

In both cases, we invoke a simple function and our Node module and Nodemailer do all of the heavy lifting behind the scenes. We have successfully encapsulated all of this functionality so we can send email messages with ease!

Sending SMS (Text) Messages Using Nodemailer

Nodemailer is not limited to delivering email messages to your inbox. Keep in mind that we can also utilize email to send SMS (text) messages. For example, to send a text message to 610-555-1234, you can simply send an email message to 6105551234@tmomail.net, assuming the cell phone number is a T-Mobile subscriber. See this page for a list of SMS email addresses used by many carriers.

Conclusion

We have opened up our world by learning how to send email (and text) messages in our Node.js programs. I hope you have enjoyed this tutorial and learned how to use Nodemailer and Node.js reversible encryption in the process.

Follow @thisDaveJ (Dave Johnson) on Twitter to stay up to date on the latest tutorials and tech articles.

Additional Articles

Node.js IoT – Tracking the ISS through the Sky
Node.js IoT: Logging Data That Is Out of This World
Beginner’s Guide to Installing Node.js on a Raspberry Pi
Upgrading to more recent versions of Node.js on the Raspberry Pi

2 thoughts on “Node.js: Sending Email Notifications Using Nodemailer and Gmail

Leave a Reply

Your email address will not be published. Required fields are marked *