Sending Emails From Your Firebase App with Nodemailer using Gmail as SMTP

I was recently working on a Firebase side project where I needed to implement a feature that sends emails to users upon signing up, and I was astonished by how difficult it was to find the resources needed to aid the implementation. I then decided to document the steps I took for posterity's sake. If you are looking for a guide on how to integrate Nodemailer with your Firebase application, then this is the right one for you.

What is Firebase?

If you've been following the world of software development for a while now, you will no doubt, have heard of Firebase. Firebase is a Google-backed Backend-as-a-Service(BAAS) app development platform that provides hosted backend services such as authentication, hosting, storage and database solutions. You can describe it as an all-in-one backend solution for your apps.

What is Nodemailer?

Nodemailer is arguably the most popular emailing package for NodeJS.

Nodemailer is a module for Node.js applications to allow easy-as-cake email sending. It is very easy to settle and integrate into an existing project.

In this article, I'll show you how to integrate Nodemailer into your Firebase project with Cloud Functions. We will be setting up a 3-Legged authentication with OAuth and Gmail to obtain the access tokens needed by Nodemailer and then set those tokens as environment variables for use in our Firebase app and emulator.

Requirements

The Setup

Setting up Firebase is a very easy process. All you need to do is sign in to Firebase with an existing Google account and then follow the steps listed below;

I am working on an already created React app, so I already have a package.json file at the root of my project directory. This file contains all the dependencies and config needed for my application. If you don't have one already, run the following command on your terminal while in the project's root directory:

npm init

Next, we need to install the firebase NPM package:

npm install --save firebase

Lastly, we must to import the required firebase modules into your app and then initialize the project:

// Firebase App (the core Firebase SDK) is always required and must be listed first
import firebase from "firebase/app";


// Add the Firebase products that you want to use
import "firebase/auth";
import "firebase/firestore";


const firebaseConfig = {
  // ... paste the config values given when you created the Firebase project on the console.
};

// Initialize Firebase
firebase.initializeApp(firebaseConfig);

For this article, we will be requiring the Firebase CLI for our Cloud Functions and Firebase Emulators setup. The Firebase CLI provides us with a set of tools to administer and manage our Firebase projects directly from the terminal. You can read more about that on the Firebase documentation.

Before we carry on with the rest of this guide, we must first initialize Cloud Functions for Firebase. This provides us with a Node.js runtime environment required to run Nodemailer. To initialize functions in your Firebase project, run the command below in the root of your project's directory and follow the prompts in line with your project's specifications:

firebase init functions

This command creates a functions directory in the root of our project that holds files and modules necessary to write and deploy Cloud Functions successfully.

Setting up a Google Cloud Project

The first step in our tasks is to set up a Google Cloud Project.

  • Head over to your Google Developer Console page.
  • On your dashboard, click on the dropdown icon on the menu. This opens up a pop-up window.

Alt Text

You can either use an existing project or create a new one. Since we already created a project on the Firebase console, we can access it by typing the name we gave to the project on the search bar.

Alt Text

Getting OAuth Credentials

Next, we need to retrieve our project's OAuth credentials from the Google Cloud Platform page. Alt Text

  • On the sidebar of the developer console, click the "APIs and services" menu.
  • Then, click the "Credentials" option to go to the "Credentials" page.

On the "Credentials" page, you will notice that we already have an OAuth 2.0 Client ID autogenerated for us by Google client. This was created when we created our Firebase project on the console.

  • Click on the "Web Client (auto-created by Google Service)" link to show the credentials.
  • Then, copy the Client ID and the Client secret from the list of credentials. These are required to set up the OAuth configuration.

Alt Text Alt Text

Getting OAuth Tokens

The easiest way to get the required OAuth tokens is to use the OAuth 2.0 playground.

  • Head over to the OAuth 2.0 playground page.
  • Click on the cog icon (⚙️) at the top right corner of the screen to display the interaction window. Then check the "Use your OAuth credentials" option.
  • Next, paste the Client secret and Client ID gotten from the Google Cloud Platform's "Credentials" page.

Alt Text

Now, we need to set the scope of the OAuth credentials by authorizing the Gmail API for our project:

  • Head over to the "Select and authorize APIs" section on the left side of the screen.
  • Next, paste the Gmail link — https://mail.google.com— into the text field provided to authorize the Gmail API.

Alt Text

Then, click on the "Authorize APIs" button. This opens up a Google authentication prompt. Alt Text

Select the Google account in use and then authorize your app to access your Google account. Alt Text Click on the "Advanced" button at the bottom. Alt Text Click on the "Continue to project" button at the bottom and then grant the app access to your Google account. Alt Text

After completing the steps above, you will be redirected back to the OAuth playground.

  • Click on the "Exchange authorization code for tokens" button on the left-hand side of the screen. Alt Text

When the request completes, it will return an object on the "Response/Request" section of the screen that contains your access token and refresh token. Alt Text

These values, along with the Client Secret and Client ID from the credentials page, make up our OAuth credentials needed for Nodemailer.

Firebase Environment Variables

You will often need to set up additional environment configurations for your Firebase Functions. These can be third-party API keys, sensitive data, or, in our case, our OAuth credentials. The Firebase SDK for Cloud Functions offers a built-in environment configuration to make it easy to store and retrieve this type of data for your project.

Setting of environment variables in Firebase is done with the command

firebase functions:config:set x.key="THE API KEY" x.id="THE CLIENT ID"

For this project, we need to set environment variables to store our access and refresh tokens; and our client secret and client ID.

We can do this by running the command in out terminal:

firebase functions:config:set gmail.useremail="yourgoogleemail@gmail.com" gmail.clientid="yourclientid.apps.googleusercontent.com" gmail.refreshtoken="1//04zKnDTh1mXdLCgYI-yourrefreshtoken" gmail.clientsecret="mbFQnYOurCLienTSecREt"

Keep in mind that only lowercase characters are accepted as keys.

If your project is running with a Firebase emulator, you must retrieve your custom configuration variables to make them locally accessible. Depending on your operating system, run either of the following commands in the functions directory of your project:

For MacOS:

firebase functions:config:get > .runtimeconfig.json

And for Windows

firebase functions:config:get | ac .runtimeconfig.json

Accessing environment variables on Firebase

In Firebase, defined environment variables are made available to functions via functions.config(). We can access them within our application by following the following syntax:

functions.config().envKey.envValue

We can destructure these values in our index.js file to make it more readable:

let { useremail, refreshtoken, clientid, clientsecret } = functions.config().gmail;

Installing and Configuring Nodemailer

For this part of the tutorial, you will need to install Nodemailer if you haven't already. To install Nodemailer, run the code below on the terminal within your project's functions directory :

npm install nodemailer

Then, copy the code below and paste in your index.js file within your functions folder:

const functions = require("firebase-functions");
const admin = require("firebase-admin");
const nodemailer = require("nodemailer");

admin.initializeApp();

/** defining and destructuring environments config for firebase functions */
let { useremail, refreshtoken, clientid, clientsecret } =
    functions.config().gmail;

/**create reusable transporter object using the gmail SMTP transport */
let transporter = nodemailer.createTransport({
    host: "smtp.gmail.com",
    port: 465,
    secure: true,
    auth: {
        type: "OAuth2",
        user: useremail,
        clientId: clientid,
        clientSecret: clientsecret,
        refreshToken: refreshtoken,
    },
});


//our firebase cloud function
exports.userCreate = functions.auth.user().onDelete((user) => {

    // TODO: Replace the `from`, `html` and `subject` values
    const mailOptions = {
        from: "okekechidera97@gmail.com",
        to: user.email,
        subject: "Thanks for Signing up",
        html: `<div
        Hey, I am an HTML template
    </div>`,

    };

// send mail with defined transport object
return transporter.sendMail(mailOptions).catch((err)=>{
        console.log(err);
    });
});

The code above illustrates a Firebase Auth triggered function that uses Nodemailer to send emails to new users upon signup. Edit the from, subject, and html values in the mailOptions object to suit your needs. However, all Cloud Functions must be deployed before usage; therefore, we must deploy our newly created function. To deploy a Cloud Function, run the command below in the terminal:

firebase deploy --only functions

This command bundles all the Cloud Functions code contained in the index.js file and deploys them to the Cloud Functions runtime.

Conclusion

We just discussed how to integrate and send emails with Nodemailer in a Firebase project; I hope you found it useful. Thanks for taking the time to read this. This article is my first attempt at technical writing; I appreciate any feedback you might have.

The project that inspired this article is available here.