Sending Emails Securely Using Node.js, Nodemailer, SMTP, Gmail, and OAuth2
Tags: node, tutorial, javascript
Published: 2020-12-16 23:05:54

Many solutions online regarding configuring Nodemailer to use your Gmail requires you to **enable less secure app access**. If that sounds too scary for you, then you have come to the right place! In this article, you will learn how to securely configure Nodemailer and Gmail.

Let's start by understanding what Nodemailer is.

**Nodemailer** is a module that makes **sending emails from Node.js applications** ridiculously easy. 

The following are the main steps required to send emails:
1. Creating a transporter (object used to send emails) using 
   either SMTP or some other transport mechanism
2. Setting up message options (who sends what to whom)
3. Sending the email by calling sendMail method on the 
   transporter

### Less Secure Configuration

Before we look at the secure solution for configuring Nodemailer and Gmail, let's look at the less secure solution. 

Using the steps above as a reference, here is the corresponding code:
```
//Step 1: Creating the transporter
const transporter = nodemailer.createTransport({
    service: "Gmail",
    auth: {
          user: "******@gmail.com",
          pass: "gmail_password"
        }
});

//Step 2: Setting up message options
const messageOptions = {
  subject: "Test",
  text: "I am sending an email from nodemailer!",
  to: "put_email_of_the_recipient",
  from: "put_email_of_sender"
};

//Step 3: Sending email
transporter.sendMail(messageOptions);
```
Note: the solution above won't work until you **enable less secure app access** in Google account settings. 

Now, let's look at the more secure solution.

### Step 1: Creating a Google Project

Visit [Google Developer Console] (https://console.cloud.google.com/) to create a project. A project is needed so that we can create the necessary API credentials.

Once in the console, click the dropdown in the top left corner.

![Project Dropdown](https://dev-to-uploads.s3.amazonaws.com/i/3shpt049gyks0ackctl6.jpg)

After the create project window loads, click **New Project**.

![Project Window](https://dev-to-uploads.s3.amazonaws.com/i/9rimkadjgpm7yzry83zg.jpg)

Enter in the project name and click **create**.

![3](https://dev-to-uploads.s3.amazonaws.com/i/vyg31g1tb02qe1nnttd2.jpg)

### Step 2: Creating OAuth 2.0 API Credentials

To get the client secret and client id, we need to create OAuth credentials. A client id identifies our app to Google's OAuth servers so that we can securely send emails from Nodemailer.

Start by selecting **credentials** in the sidebar on the left. Once selected, the following screen should appear: 

![4](https://dev-to-uploads.s3.amazonaws.com/i/4yahpghg15w6t3sertqa.jpg)

After clicking **create credentials**, a dropdown will appear. In the dropdown, select **OAuth client ID**.

![5](https://dev-to-uploads.s3.amazonaws.com/i/r6xph65rhetlgeq4v8p7.jpg)

Before proceeding, we need to configure the consent screen. The consent screen configuration is important when an application offers Google Sign In. Nevertheless, it must be completed so we can create a client id and secret.

Click **configure consent screen**.

![6](https://dev-to-uploads.s3.amazonaws.com/i/5qvv6h483suw6lmdqlww.jpg)

Select **external** for the User Type and then click **create**.

![7](https://dev-to-uploads.s3.amazonaws.com/i/140rlcbclfw5lr4ywxqu.jpg)

After the multi-step form appears, fill out the required fields for each step.

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/lpti10zypz3mnzo2tddz.png)

Once on the last step, click **back to dashboard**. 

![8](https://dev-to-uploads.s3.amazonaws.com/i/hr0oek8sxlzkf9kpxg62.jpg)

Go back to the Create OAuth client ID screen (page with the configure consent screen button). If the consent screen has been configured successfully, an application type dropdown should appear. Select **Web application** and fill in the required field(s).

![9](https://dev-to-uploads.s3.amazonaws.com/i/6updx36lrwlsscdmer7f.jpg)

In the Authorized redirect URIs section, make sure to **add https://developers.google.com/oauthplayground**.

Now click **create**!

![9.1](https://dev-to-uploads.s3.amazonaws.com/i/8ibsi4viip6xmto9yio6.jpg)

Copy the client ID and client secret shown on the screen and save it for later.

![9.2](https://dev-to-uploads.s3.amazonaws.com/i/i5bbno93r4vdul05sjo6.jpg)
        
### Step 3: OAuth 2.0 Playground

We also need a refresh token and access token which can be generated from the client id and secret. 

Start by visiting https://developers.google.com/oauthplayground.
Once on the page, click the gear icon and check the **Use your own OAuth credentials** box. Then paste in the client id and secret from before.

![9.3](https://dev-to-uploads.s3.amazonaws.com/i/9ort9i5akmrcaqggfppf.jpg)

On the left, under the Select & authorize APIs section, find Gmail API v1 and select **https://mail.google.com/**. Alternately, you can also **type https://mail.google.com/** into the **Input your own scopes** field.

Now click **Authorize APIs**.

![9.4](https://dev-to-uploads.s3.amazonaws.com/i/petrnbxfghisnaoya0uv.jpg)

If the following pages appear, click allow so that Google OAuth 2.0 Playground has access to your Google account. 

![9.41](https://dev-to-uploads.s3.amazonaws.com/i/issqbjh6b6cdn6n66ccl.jpg)

After being redirected back to the OAuth 2.0 Playground,
click the **Exchange authorization code for tokens** button under the **Exchange authorization code for tokens** section.

Once the refresh and access token is generated, copy the refresh token and save it for later. 

![9.5](https://dev-to-uploads.s3.amazonaws.com/i/4mmdp7040vchu6ozm32b.jpg)
    
### Step 4: Writing Code

Now that we have the client id, client secret, and refresh token, we can now use them to send emails!

Start by making a new folder for the application and cd into the folder. 
```
mkdir sendEmails
cd sendEmails
```
To initialize the app as a node project, run ``npm init``.

Next, let's install the npm packages.
```
//Note: dotenv is a dev dependency
npm i nodemailer googleapis && npm i dotenv --save-dev
```
**googleapis**
- library for using Google APIs
- Will be used to dynamically generate access token
 
**dotenv**
- library for using environment variables
- Will be used to avoid having API keys in our code

Like with any NPM packages, we start by requiring the packages. So, create an ``index.js`` file and add the following:
```
const nodemailer = require("nodemailer");
const { google } = require("googleapis");
const OAuth2 = google.auth.OAuth2;
```
#### Environment Variables Setup

Typically when using sensitive info in code (e.g. API keys), the best practice is to use environment variables.

Create a ``.env`` file in the root directory of the project and add the following:
```
EMAIL=YOUR_GOOGLE_EMAIL_HERE
REFRESH_TOKEN=PASTE_REFRESH_TOKEN_HERE
CLIENT_SECRET=PASTE_CLIENT_SECRET_HERE
CLIENT_ID=PASTE_CLIENT_ID_HERE
```
Now, we need to require and call the config method before requiring all the packages:

```
require('dotenv').config();
const nodemailer = require("nodemailer");
const { google } = require("googleapis");
const OAuth2 = google.auth.OAuth2;
```

``process.env`` now has the keys and values defined in the ``.env`` file. For example, we can access client id via ``process.env.CLIENT_ID``

#### Creating a transporter

We first need to create an OAuth client with all of our info from before (client ID, client secret, and the OAuth Playground URL). The OAuth client will allow us to dynamically create an access token from a refresh token.

“But wait, why can't we just use the access token from the OAuth Playground? Or why are we creating the access token dynamically?”

Well, if you noticed earlier, there was a message indicating the access token would expire after 3582 seconds.

The following code creates the OAuth client and provides it with the refresh token:
```
const oauth2Client = new OAuth2(
    process.env.CLIENT_ID,
    process.env.CLIENT_SECRET,
    "https://developers.google.com/oauthplayground"
);

oauth2Client.setCredentials({
    refresh_token: process.env.REFRESH_TOKEN
});
```

Since getting the access token through the OAuth client is an asynchronous process, we need to wrap the above in an async function.
```
const createTransporter = async () => {
  const oauth2Client = new OAuth2(
    process.env.CLIENT_ID,
    process.env.CLIENT_SECRET,
    "https://developers.google.com/oauthplayground"
  );

  oauth2Client.setCredentials({
    refresh_token: process.env.REFRESH_TOKEN
  });
};
```

Now, we can get the access token by calling the getAccessToken method.
```
const accessToken = await new Promise((resolve, reject) => {
  oauth2Client.getAccessToken((err, token) => {
    if (err) {
      reject("Failed to create access token :(");
    }
    resolve(token);
  });
});
```
You might be wondering, why are we wrapping the getAccessToken method call in a promise? This is because getAccessToken requires a callback and does not support using async await. Thus, we can either wrap it in a promise or create the transporter inside the callback. I prefer the former as it is more readable.

Now for the main part, creating the transporter object itself.  To create it, we pass some configurations to the createTransport method. 
```
const transporter = nodemailer.createTransport({
  service: "gmail",
  auth: {
    type: "OAuth2",
    user: process.env.EMAIL,
    accessToken,
    clientId: process.env.CLIENT_ID,
    clientSecret: process.env.CLIENT_SECRET,
    refreshToken: process.env.REFRESH_TOKEN
  }
});
```
Note: If you receive an "unauthorized client", try adding the following to the JS object above.

```
tls: {
  rejectUnauthorized: false
}
```

After the transporter is created, the completed createTransporter function should look like this:
```
const createTransporter = async () => {
  const oauth2Client = new OAuth2(
    process.env.CLIENT_ID,
    process.env.CLIENT_SECRET,
    "https://developers.google.com/oauthplayground"
  );

  oauth2Client.setCredentials({
    refresh_token: process.env.REFRESH_TOKEN
  });

  const accessToken = await new Promise((resolve, reject) => {
    oauth2Client.getAccessToken((err, token) => {
      if (err) {
        reject();
      }
      resolve(token);
    });
  });

  const transporter = nodemailer.createTransport({
    service: "gmail",
    auth: {
      type: "OAuth2",
      user: process.env.EMAIL,
      accessToken,
      clientId: process.env.CLIENT_ID,
      clientSecret: process.env.CLIENT_SECRET,
      refreshToken: process.env.REFRESH_TOKEN
    }
  });

  return transporter;
};
```
Notice we are returning the transporter instead of writing the code to send an email. We will create another function for sending the email for the sake of code readability and separations of concerns. 

Let's now create the sendEmail function. This function calls the createTransporter function and then the sendMail method that exists on the transporter.

```
//emailOptions - who sends what to whom
const sendEmail = async (emailOptions) => {
  let emailTransporter = await createTransporter();
  await emailTransporter.sendMail(emailOptions);
};
```

All that is left now is to send the email by calling the sendEmail function:
```
sendEmail({
  subject: "Test",
  text: "I am sending an email from nodemailer!",
  to: "put_email_of_the_recipient",
  from: process.env.EMAIL
});
```
The complete list of the email options can be found at https://nodemailer.com/message/.

Run ``node index.js`` from the terminal/command line and Voila! Here is the email we sent from the application!

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/d2gx8j0s6l0v4a3lonp3.png)

For reference, here is the completed ``index.js`` file:

```
require("dotenv").config();
const nodemailer = require("nodemailer");
const { google } = require("googleapis");
const OAuth2 = google.auth.OAuth2;

const createTransporter = async () => {
  const oauth2Client = new OAuth2(
    process.env.CLIENT_ID,
    process.env.CLIENT_SECRET,
    "https://developers.google.com/oauthplayground"
  );

  oauth2Client.setCredentials({
    refresh_token: process.env.REFRESH_TOKEN
  });

  const accessToken = await new Promise((resolve, reject) => {
    oauth2Client.getAccessToken((err, token) => {
      if (err) {
        reject("Failed to create access token :(");
      }
      resolve(token);
    });
  });

  const transporter = nodemailer.createTransport({
    service: "gmail",
    auth: {
      type: "OAuth2",
      user: process.env.EMAIL,
      accessToken,
      clientId: process.env.CLIENT_ID,
      clientSecret: process.env.CLIENT_SECRET,
      refreshToken: process.env.REFRESH_TOKEN
    }
  });

  return transporter;
};

const sendEmail = async (emailOptions) => {
  let emailTransporter = await createTransporter();
  await emailTransporter.sendMail(emailOptions);
};

sendEmail({
  subject: "Test",
  text: "I am sending an email from nodemailer!",
  to: "put_email_of_the_recipient",
  from: process.env.EMAIL
});
```