Unclebigbay's 🚀 Blog

Unclebigbay's 🚀 Blog

Build an Email Application using Node JS Express JS with Gmail and Nodemailer - (All in one Article)

Series 1 - 5 all in one article

Build an Email Application using Node JS Express JS with Gmail and Nodemailer - (All in one Article)

Subscribe to my newsletter and never miss my upcoming articles

Listen to this article

Hello 😊, my gorgeous friend on the internet 👋, how are you doing today?

Sometimes we want to send out a welcome email to our new registered users or send them a link to reset their forgotten password.

This is an extracted feature from my HarperDB Hackerthon open source project, I found it difficult to implement, because of resources, so I am saving you from the stress I passed through.

Below you will learn how to send out emails directly from your Node JS Application using your registered Gmail account and Nodemailer, with an interface built with HTML and CSS that is powered by Express JS.

Don't worry about the complexity of what is listed above, I am going to explain all of them in this article (in a bit).

You can check out the final project sample of what you will be building along with me in this article Here.

If you would prefer a series of this article (broken into multiple parts), you can check it out HERE

PREREQUISITES

Before following along in this tutorial, I assume you already have a basic understanding of JavaScript and that you already have the following setup:

  1. Gmail account

  2. Node installed on your PC

If you do not have a Gmail account setup, click Here to register your account and you can also download the Node application by clicking Here.

Before we proceed, kindly note that this is a very long article, because I ensure to explain every concept that is been used in building the application, and this will help you grasp useful concepts in Node JS.

At the end of this article, you will learn the following concepts and technologies:

  1. How to kick-start a Node JS application
  2. Writing your first route (beginners)
  3. Rendering HTML with Node JS package.json and explanation of what is inside
  4. Implementation of Nodemailer
  5. How to add Multer to your Node JS project
  6. How to retrieve form data from the HTML form to our Node js Server

What is Node JS?

Node JS is a JavaScript framework that allows us to use JavaScript outside of the browser and on the server-side.

The Server-side is the system that runs on the server (Node JS runs here), and the client-side is the software that runs on a user's web browser (Vanilla JavaScript or React JS runs here).

To learn more about Node JS check out the official documentation Here

Let us verify that Node JS is successfully installed on your system, run the command below on your terminal.

node --version

If there is no error, the command will return the current version of the installed Node on your system.

frame_generic_light (2).png


What is Express JS?

Express JS is a popular framework of Node JS that makes it easier to manage web applications, Express JS is the E in both MERN and MEAN stack.

To learn more about Express JS check out the official documentation Here


What is Gmail?

Gmail is a free mail service provided by Google, they enable us to send and receive information (electronic-mail) on their platform.

To learn more about Gmail and its services you can check it out Here


What is Nodemailer?

Nodemailer is a Node JS module (function) that allows us to send emails from our Node JS (Server) Application.

To learn more about Nodemailer check out the official documentation Here


Setting up our Node JS Application

1. To begin our project create a new folder and name it projectMailSender
2. Right-click on the projectMailSender and select the Git bash here option

Screenshot of the Gitbash option

Your current directory should be displayed on the bash terminal like this

frame_generic_light (1).png

If you are not using Gitbash, ensure to navigate inside the projectMailSender folder on your cmd.

3. Let's proceed to generate our project package.json file by running the code below 👇

yarn init -y

The yarn init command is used to create or update a package.json file interactively. This command will walk you through a question and answer process to create a package.

frame_generic_light.png

We skipped some questions about the new package.json that we are creating with the -y flag, you can check out the questions in your next Node project by running yarn init without the -y.

Note that -y is a shorthand for -yes

To learn more about the yarn init command check out the official documentation Here


What's inside package.json?

If the yarn init command is successful a new file named package.json will be created in ourprojectMailSender` folder.

image.png

Let's take a look at what is inside our package.json file below 👇

image.png

1. name: the name of our project folder.
2. version: you can set and update your project version here

1 stands for a major update (version 1), second 0 stands for a minor update for the major update (version 1, with zero updates (the number of updates on version 1)), and finally the last 0 means patch, a very minor update that is not a big deal.

3. main: every project must have an index.js which serves as an entry point for your project (you can change this).
4. License: your project license goes here.

More information about your project will be stored in the package.json file if you do not skip the yarn init command with the -y flag.

We will be using a file name index.js as our project entry point, so let's proceed to create it in our project root 👇.

You can either run the command below to create the file from your terminal or create it manually.

touch index.js

Your project folder should look like this 👇

image.png


Installing Express JS

Now that we have our project setup, let us proceed to install the Express JS into our project. Use the command below 👇 to install Express JS


yarn add express

Note: if you initialized your package.json file with npm, kindly stick to it to avoid conflict, let me know in the comment section if you made this mistake.

The Express JS package should be successfully installed by now let's take a look at our project folder structure again below 👇

image.png

You should also notice from your end that a folder named node_modules and a file named yarn.lock has been created automatically in our project folder.

Explanation

  • node_modules

This is where yarn or npm will keep the folders of our installed packages for us to make use of them later.

To learn more about node_modules you can check out the official documentation Here

  • yarn.lock

Because we are using yarn as our package manager, yarn auto-generate this file to keep track of our project dependencies(packages).

To learn more about yarn.lock you can check out the official documentation Here

Warning: Do not alter the node_modules folder and the yarn.lock file manually.


Creating our Project Server with Express JS

Copy and paste the code below into your project entry file (index.js in this tutorial)

// Import express into our project
const express = require("express");

// Creating an instance of express function
const app = express();

// The port we want our project to run on
const PORT = 3000;

// Express allows us to listen to the PORT and trigger a console.log() when you visit the port
app.listen(PORT, () => {
  console.log(`Server is 🏃‍♂️ on port ${PORT}`);
});

This is the minimum way of setting up a server with the Express Node JS framework, I have included comments that are straightforward in the code snippet 👆, so it will be present in your own project for reference.

Let's proceed to test our server by running the code below in our terminal.

node index.js

Output 👇

image.png


Setting up Nodemon

Now that our server is running on our terminal, let's try to change something in our console.log(), let's say we update the console message below 👇


app.listen(PORT, () => {
  console.log(`Server is currently 🏃‍♂️ on port ${PORT}`);
});

Save your file and check your terminal, you should notice that the previous message is still displayed, this implies that the server will not restart whenever there is a change in our project directory, we need to do this manually each time we make a change in the project by stopping the server with ctrl + c and starting it again with node index.js.

If you restart your server, you should get a new message like below 👇

frame_generic_light.png

To do this automatically, we can install a package called nodemon, nodemon will help us listen to changes in our project files and automatically restart the server within seconds.

Run the following command to install nodemon to your project

yarn add --dev nodemon

We are installing the nodemon package as a development dependency because it is not needed when we finally host our Node JS project to production.

To learn more about dependencies and devDependencies in package.json you can check it out Here

Now that we've installed nodemon package in our project, let us set it up to do its job.

Open your package.json file 👇

image.png

Your project express package should be inside the dependencies object while the nodemon should be inside the devDependencies, this is because we need the express package in our production but we only need the nodemon for our project development.

if your package.json is not structured like the above, kindly check back how we installed the two packages previously.

Add the following code to your package.json file 👇

// Before the dependencies

  "scripts": {
    "start": "nodemon index.js"
  },

Your package.json file should look something like below 👇

image.png

we are basically adding a command to our project which can be accessed through yarn or npm, in this case, we are saying when we run the yarn start command, yarn should start our project with nodemon index.js, so that nodemon will start listening to our file changes.

Start the server again with the code below 👇


yarn start

You should get a response, that nodemon is ready for any file change and will restart the server immediately.

frame_generic_light (1).png

You can proceed to change your console.log() message and see if the server will restart automatically.

Kindly note that nodemon will only listen to changes to files with .js, .mjs, and .json extensions, we will have to handle the refresh of the HTML and CSS files on our browser.

If you wish to handle this automatically you can click HERE to install a live server on your VScode to listen for the HTML and CSS file changes.

Click the go-live to turn on the server for your HTML file (ensure you are currently on your HTML file)

image.png


Now that we have our server up and running with nodemon listening to our file changes, let design our application interface.

  1. Create a new folder and name it public

  2. Inside the public folder create the following files

  • index.html

  • index.css

  • success.html

  • success.css


Copy the code below 👇 into the stated files respectively


  • index.html


<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <!-- Css styles -->
    <link rel="stylesheet" href="./index.css" />
    <!-- Fontawesome icon library -->
    <link
      rel="stylesheet"
      href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css"
    />
    <title>e-Mail Sender Application</title>
  </head>
  <body>
    <form style="max-width: 500px; margin: auto">
      <h2>e-Mail Sender Application</h2>
      <div class="input-wrapper">
        <i class="fa fa-user icon"></i>

        <input
          class="input-field"
          type="text"
          placeholder="To: i.e unclebigbay@gmail.com"
          name="email"
          autocomplete="off"
          required
        />
      </div>
      <div class="input-wrapper">
        <i class="fa fa-envelope icon"></i>

        <input
          class="input-field"
          type="text"
          placeholder="Subject"
          name="subject"
          required
        />
      </div>

      <div class="input-wrapper">
        <textarea
          id="message"
          name="message"
          placeholder="Message body.."
          style="height: 200px; width: 100%"
          required
        ></textarea>
      </div>

      <div class="attachment-wrapper">
        <label for="attachment">Attachment</label>
        <input id="attachment" name="attachment" type="file" required/>
      </div>

      <button type="submit" class="send-btn">Send Mail</button>
    </form>
  </body>
</html>

  • index.css


@import url("https://fonts.googleapis.com/css2?family=Poppins:ital,wght@0,300;0,400;0,500;0,600;0,700;0,800;0,900;1,300;1,400;1,500;1,600;1,700;1,800;1,900&display=swap");

* {
  box-sizing: border-box;
  font-family: "Poppins";
}

h2 {
  font-size: 2rem;
}

.input-wrapper {
  display: -ms-flexbox; /* IE10 */
  display: flex;
  width: 100%;
  margin-bottom: 15px;
  font-family: "Poppins";
}

.icon {
  padding: 10px;
  background: #333333;
  color: white;
  min-width: 50px;
  text-align: center;
  font-family: "Poppins";
}

.input-field {
  width: 100%;
  padding: 10px;
  outline: none;
  font-family: "Poppins";
  border: 2px solid #333333;
}

.input-field:focus {
  box-shadow: 0 0 10px #333333;
  border: 2px solid #333333;
}

.input-wrapper textarea {
  padding: 1rem;
  border: 2px solid #333333;
}

.input-wrapper textarea:focus {
  outline: none;
  box-shadow: 0 0 10px #333333;
  border: 2px solid #333333;
}

.attachment-wrapper {
  margin: 15px 0;
}

.attachment-wrapper label {
  font-weight: 600;
}

.send-btn {
  background-color: #333333;
  color: white;
  padding: 15px 20px;
  border: none;
  cursor: pointer;
  width: 100%;
  opacity: 0.9;
  font-size: 16px;
}

.send-btn:hover {
  opacity: 1;
}

  • success.html


<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <!-- Css file -->
    <link rel="stylesheet" href="./success.css" />
    <!-- Fontawesome icon library -->
    <link
      rel="stylesheet"
      href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css"
    />
    <title>Mail Sent</title>
  </head>
  <body>
    <i class="fa fa-paper-plane icon"></i>

    <h1>Message Delivered!!!</h1>
    <a href="./index.html">New message</a>
  </body>
</html>

  • success.css

@import url("https://fonts.googleapis.com/css2?family=Poppins:ital,wght@0,300;0,400;0,500;0,600;0,700;0,800;0,900;1,300;1,400;1,500;1,600;1,700;1,800;1,900&display=swap");

body {
  font-family: "Poppins";
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
  height: 70vh;
  color: #333333;
}

a {
  color: blue;
}

a:hover {
  opacity: 0.8;
}

body .icon {
  font-size: 3rem;
}

Rendering Static Files to the Browser with Express JS

Yes, we cannot just access our HTML file directly from our browser, express must handle that for us and to this point, we can only access our application port 3000 on our terminal, let's see how we can display our HTML files on our browser using Express JS.

Update the index.js with the following code

....
// After PORT = 3000;

// Express should add our path
app.use(express.static("public"));


// Render the index.html when the user visit our project port
app.get("/", (req, res) => {
  res.sendFile("/index.html");
});

...

Explanation

1. app.use(express.static("public"));
  • We are using the express js method called use to chain a middleware called static, which allows us to serve static files like html, css, js, image, and more.

The public folder is defined inside the static middleware as our project root directory.

2. app.get
  • app.get() is a function that tells the server what to do when a get request at the given route is called.
3. ("/", (req, res)
  • The forward slash signifies the root directory of our route, just like www.hashnode.com

  • The req argument stands for request from that particular route, while the res represents response, which is a means to send back a response to the route caller.

4. res.sendFile("/index.html");
  • Remember the res above 👆 us used to send back a response? the res has a method to send back a file to the route been called, and that is what we are doing here, we are returning the index.html file when the root route of the project is been hit.

Project UI Routing on a Browser with Express JS

Routing refers to how our application's endpoints (URLs) respond to the client (browser) request through the req parameter, this is unique for a particular endpoint (URL).

www.hashnode.com will respond with the landing page while a request to www.hashnode.com/onboard will respond with a login page 😉.

Since we have our route and static files setup, it's time to visit our project port in our browser 💃

You can either copy-paste this route 👉 localhost:3000 to your browser address bar and hit enter or click this link localhost:3000.

If all goes well, you should have this 👇 displayed on your browser.

frame_generic_light (3).png

You can as well visit the success page using this link localhost:3000/success.html

Our success page should look something like below 👇.

frame_generic_light (4).png


If you made it this far let's take a break, and celebrate 😍

fb7596eafc1adf186d0e6ed139b458d8.gif

Let's drink up 👇

giphy (4).gif


Welcome Back 🤩


Handling Data from our HTML forms

Now that we have our project UI setup, let's look into handling the data submitted by the sender.

Note that, our HTML form has the following form input types

  1. email - text
  2. subject - text
  3. message - text
  4. attachment - file

Generally, to have access to a request body through the req parameter in the get() function, we need a middleware package called bodyparser, but this is also provided by the express JS function, so we do not need to install any package for this.

Update the index.js with the code below

// After the express static middleware
...

// Body parser
app.use(express.json());
app.use(express.urlencoded({ extended: false }));

...

Now that we have our body-parser setup, let's see it in action by creating a POST route in our application, which we will use to post the entries of the sender to our server.

Add the following code below the get route function

// Post route to handle retrieving data from HTML form to server
app.post("/send_email", (req, res) => {
  console.log(req.body);
});

We also need to update our HTML form to have a POST method and also set the path to post the data to like below.

    <form
      action="/send_email"
      method="POST"
      style="max-width: 500px; margin: auto"
    >

Explanation

1. action="/send_email"
  • The action attribute is used to specify where we want to send the form data when the form is submitted, we are sending the data to the /send_email route in our application.
2. method="POST"
  • The post method is good when sending sensitive information from the client (browser) to the server (backend), it hides the form values from the browser address bar unlike the GET method which displays it, the post method also appends form data inside the body of the HTTP request, this body is what express js is parsing and is also been retrieved using through the req parameter in our route functions.
3. req.body
  • Here we are accessing the form body object from the req parameter which contains all the data from our HTML inputs (example ahead).

Now that we have all this setup, let us proceed to send a sample mail that will be displayed in our terminal.

Fill in the form inputs and also select an attachment then click the send mail button.

frame_generic_light (6).png

If you have all your project set up correctly, you should have the form values displayed below in your terminal.

image.png

That 👆 is the example of the req.body, this picks up the attribute name of the HTML input tags


The POST Method

frame_generic_light (9).png

Did you noticed that your browser address bar didn't change 💁‍♂️, now set your form method to GET and try sending a mail again, what did you notice?


The GET Method

frame_generic_light (8).png

The submitted data are been displayed on the address bar, imagine you are submitting your password 😱.

Which method will you use for credit cards?


Handling Files over a Node JS server

In this section, we will look into handling files coming from the client-side to our project Node JS backend application.

In other to handle files coming from the frontend, we need to install a package called multer

Multer is a node.js middleware for handling multipart/form-data, which is primarily used for uploading files.

Multer also has a rule, which is that multer will not process any form which is not multipart (multipart/form-data).

In other to make multer work in our project we need to add an encryption attribute to our form tag like below.

    <form
      action="/send_email"
      method="POST"
      enctype="multipart/form-data"
      style="max-width: 500px; margin: auto"
    >

Ensure to update your form tag with the new attribute, after that, we need to create a new folder called attachments in our project root directory

Your project structure and index.js should look like this

image.png


Configuring Multer

To configure our project to use multer, we need to first install it using the command below

yarn add multer

Once it is installed, we need to configure it in our index.js file.

Setting up multer diskStorage

Multer allows us to specify where we want to store the attachments coming from the frontend and also define their unique names through its method called diskStorage.

require multer below where we required the express js

// Import express into our project
const express = require("express");

// Import multer
const multer = require("multer");

...

Copy-paste the following code, below the body-parser.

// Multer file storage
const Storage = multer.diskStorage({
  destination: function (req, file, callback) {
    callback(null, "./attachments");
  },
  filename: function (req, file, callback) {
    callback(null, `${file.fieldname}_${Date.now()}_${file.originalname}`);
  },
});

Explanation

multer.diskStorage

1. destination

This multer method allows us to set the destination of our attachment through a callback function, and as you can see we passed our attachment folder as the second argument of the callback function, this is where the attachment of a mail will be store (example coming up).

2. filename

The second property we can set using the diskStorage is the file name of the incoming attachment, we do this so that every attachment will have a unique file name, we concatenate the field name (from, input name=" attachment") with underscore _ and current date that the file is been attached in milliseconds with another underscore _ and finally the attachment original name.

That is super unique 😍


Let's proceed to write middleware to actually get the attachment from the request body.

// Middleware to get a single attachment
const attachmentUpload = multer({
  storage: Storage,
}).single("attachment");

What we are doing here is that we are passing the attachment storage logic we created into the multer object and also telling multer that we are only targeting a single attachment with the .single("attachment").

Applying Multer Middleware to our Post Route

Now that we have set up a middleware with multer, which will help us retrieve attachments from the frontend form, let's proceed to apply it within our Post route.

Update the application post route with the following code

// Post route to handle retrieving data from HTML form to server
app.post("/send_email", (req, res) => {
  if (error) {
      console.log(err);
      return res.send("Error uploading file");
    } else {
      const recipient = req.body.email;
      const subject = req.body.subject;
      const message = req.body.message;
      const attachmentPath = req.file.path;
      console.log("recipient:", recipient);
      console.log("subject:", subject);
      console.log("message:", message);
      console.log("attachmentPath:", attachmentPath);
    }

});

Explanation

1. We invoked (call/execute) the attachmentUpload middleware function and extend (pass) the req and res parameter from the post route function to it. This will allow the middleware to have access to any file in the request body.
2. The attachmentUpload also takes in a callback function which will check for any error while attachmentUpload processes the files from the request body.
3. In the callback function, we are checking whether there is an error in the process, if any error occurs, we want to display the error in the terminal and send back a message which says Error uploading file as a response (res).

A call-back function is any function that is been passed as an argument to another function, which will be executed (call-back) later in time (any point in time).

4. Finally, we included an alternative statement with the else block which is executed if the previous statement is not satisfied. This implies that the else statement block will only run if there is no error when the middleware is trying to upload any attachments from the HTML form to the "attachment" folder.

Storing Files from HTML Form to our Storage Folder

Let's proceed to test our implementation so far.

Save your script and refresh your browser (if you're not using a live server) to get updated with our node script.

Fill the form inputs and attach any file of your choice as shown below 👇 and hit the send button.

frame_generic_light (13).png

Output 1 👇

If all goes well, your terminal should have the following output 👇

frame_generic_light (12).png

You should also notice that the attachment name has changed to the naming convention logic we created earlier 👇.

frame_generic_light (14).png

Output 2 👇

The fun part is, if you expand the attachment folder, you will see your attached file there 💃, thanks to multer, you can also check the folder directory in the file explorer.

image.png

Check your File Explorer 👇

image.png

You can check the complete code to this point from my GitHub gist Here ✋


Setting up Nodemailer Package

Now that we can get data submitted from our HTML form to our Node JS server, let us proceed to set up the logic that will actually send out the mail 💃 to our recipient.

In other to achieve this, we need to install and setup the awaited Nodemailer package, just as the name implies, this package allows us to send mail from our Node js application without stress, to begin with, let us install the Nodemailer package using the code below.


yarn add nodemailer

Nodemailer Requirements

Now that the nodemailer package has been successfully installed, let us break down the requirements for using the nodemailer package.

1. Nodemailer requires a transporter object

This is where we configure the means of sending out the mail, we are going to be using the Gmail service in this article.

Below 👇 is the format of a nodemailer transport.

let transporter = nodemailer.createTransport({
   service: 'gmail',
   auth: {
      type: 'OAuth2',
      user: "your gmail address",
      pass: "your gmail password",
      clientId: "your gmail client id",
      clientSecret: "your gmail client secret token",
      refreshToken: "gmail refresh token",
   }
});

2. Nodemailer requires a mailOption

This is an object where we specify the following

a. The sender's email address (required)

b. The recipient's email address (required)

c. The mail subject (optional)

d. The email text body (optional) and

e. The attachments (optional)

Below 👇 is the format for a mailOption

let mailOptions = {
  from: "your gmail address",
  to: "your recipient email address",
  subject: "e-mail subject",
  text: "e-mail body",
};

3. Node mailer requires a sendMail method

Nodemailer sendMail method is actually the one responsible for the sending of email to the recipient, this method takes in the mailOption we've discussed above and also a callback function to handle the status of the mail transportation.

Below 👇 is the format for sendMail method

transporter.sendMail(mailOptions, function(err, data) {
   if (err) {
      console.log("Error: " + err);
   } else {
      console.log("Email sent successfully");
   }
});

The complete code of the nodemailer should be like this 👇


// After the last console.log(attachmentPath) in the else statement

// Connecting to gmail service
let transporter = nodemailer.createTransport({
    service: "gmail",
    auth: {
        type: "OAuth2",
        user: "your gmail address",
        pass: "your gmail password",
        clientId: "your gmail client id",
        clientSecret: "your gmail client secret token",
        refreshToken: "gmail refresh token",
    },
});

// e-mail option
let mailOptions = {
   from: "your gmail address",
   to: "your recipient email address",
   subject: "e-mail subject",
   text: "e-mail body",
};

// Method to send e-mail out
transporter.sendMail(mailOptions, function (err, data) {
    if (err) {
        console.log("Error: " + err);
    } else {
        console.log("Email sent successfully");
    }
});

The above code will serve as our nodemailer template, which we need to find the credentials to, but before we do that, we need to set up a secure way to protect the transporter credentials such as the user, pass, clientId, clientSecret and the refresh token that we be provided by google, so that everyone will not be able to see our application secret details from Github repository when we finally push to production.


Setting up dotenv

dotenv allows us to keep secret credentials or tokens in our project environment (.env file), which will not be visible to either the browser, git version control, or people viewing our project on Github, these are called the Environment Variables.

Ensure to add the .env file to your .gitignore file

Run the code below 👇 to install the dotenv to your project

yarn add dotenv

After the dotenv installation, proceed to create a .env file in your project root directory like this 👇

image.png

This is where we are going to store our Environment-Specific variables in a NAME=value format like below 👇

image.png

To access this enviroment-variable from our JavaScript application we need to require (import) the dotenv package in our index.js and configure it like below 👇

image.png

Or like this 👇

image.png

You can use this method of import when you need to use other dotenv function like parse, but in this article, we are going to make use of the first method 👆 because we only need the config() function.

To learn more about the dotenv package, you can visit the official documentation Here


Accessing our environment-variables from dotenv file

Now that we know how to use the dotenv package to store our enviroment-variable in our JavaScript application let us see how we can then access this variables in our scripts.

Add this line of code below the dotenv configuration 👇

image.png

Output 👇

frame_generic_light (15).png

And that is totally how to set up and use environment-variables with dotenv package in a JavaScript project (Not only in Node JS) 😉.


If you made it this far, you deserve this 👇

standing clapping ovation


FINAL LAP


Gmail OAuth2 Configuration

Now that we have everything setup in our Node JS application, let's proceed to get the following credentials from Google.

  1. clientId : "your gmail client id",
  2. clientSecret : "your gmail client secret token",
  3. refreshToken : "gmail refresh token",

Follow the guide below to setup you Gmail OAuth 2 Configurations

1. Google Cloud Platform Account Setup

Firstly we need to register our Gmail address on the Google Cloud Platform , if you have not done that before you can register HERE.

If your gmail account is logged in on your browser you should have a screen display like this 👇

google cloud platform login to dashboard

Accept the Terms and Condition by checking the box, the Email Updates is optional, and then click on Accept and Continue

2. Setting up a New Project

Next, we need to create a new project, to do this click on the CREATE PROJECT link.

CREATE PROJECT link

  • Setup Project Name

Fill in a name for your project, I am using projectmailSender in this tutorial, note that this project name will be used to generate an id for your project and this cannot be changed later.

  • After you have decided on a project name, click on the Createbutton.

Fill in a name for your project input box circled

  • After a few seconds delay your dashboard should look like this 👇

frame_generic_light (20).png

  • After loading click on Go to APIs review 👇

frame_generic_light (21).png

  • On the APIs review page, click on the OAuth consent screen option on the sidebar

frame_generic_light (22).png

  • Select external 👇 and click on the create button

the OAuth consent Screen

  • The next step is to provide basic information about our app (nodemailSender) 👇

Only three inputs are compulsory, your app name and 2 emails, one for support and the other for contacting the developer.

Click save and continue when you are done.

basic information section

  • The scope section is optional, but if you want to fill it, you can.

I am going to skip it in this tutorial by clicking the Save and continue 👇.

the scope section is option

  • Add your email address as the test user and save 👇

add test user section

  • And finally in the Summary section, review your application data and click on Back to dashboard when you are satisfied.

frame_generic_light (26).png


Now that we have setup our new application, let us proceed to generate the credentials we are going to use in our Nodemailer.

  • From the page we stopped earlier 👆, click on the Credentials option at the sidebar 👇

frame_generic_light (29).png

- On the new page, click on Create Credentials and select the OAuth Client ID option from the dropdown 👇

frame_generic_light (30).png

  • On the next page, select Web application option from the dropdown, since we are building our application for the web 👇.

frame_generic_light (31).png

  • Choose a client name 👇

frame_generic_light (32).png

frame_generic_light (34).png

After adding the URL, click on the Create button and if everything goes well, you should receive an alert that your OAuth credentials have been created, followed by this 👇 popup.

frame_generic_light (33).png

Copy your Client ID and your Client Secret key and keep them safe, because we will use it very soon.

At this point we have gotten 2 out of 3 credentials that we need to configure out nodemailer, the last credential we are looking for is the refreshToken, let's go it em 🎣.


Generating OAuth Refresh Token

We will be using the Client ID and the Client Secret to generate our refresh token.

(I hope you saved it earlier?)

If you, you can retrieve it from the credential tab 👇

frame_generic_light (35).png

  • OAuth Playground

Remember that we set the Authorized redirect urls to https://developers.google.com/oauthplayground ?

Now let's head to the playground to grab our refresh token 💃

click here 👉 https://developers.google.com/oauthplayground

On the OAuth Playground

  1. Click on the settings (gear icon)
  2. Select the Use your own OAuth credentials
  3. Fill in your Client ID and Client Secret keys appropriately
  4. Find and click Gmail API v1 then select mail.googl.com from the dropdown

frame_generic_light (37).png

Click on the Authorize APIs button.

  • You should be redirected to the page below, ensure to select the email address you added as a Test user 👇

frame_generic_light (38).png

If you are denied access, then you should check your test user in the Google Cloud Platform

  • Click on continue 👇

frame_generic_light (39).png

  • Grant the application access to your Gmail account 👇

frame_generic_light (40).png

And 👇

frame_generic_light (41).png

  • You will be redirected back to the OAuth Playground, click on Exchange authorization code for tokens 👇

frame_generic_light (42).png

  • Copy the refresh token and keep it safe 👇

frame_generic_light (43).png

Setting up Google Transporter

Now that we have gotten our required credentials, let's update our nodemailer logic with the googleapis package.

install the google API using the code below

yarn add googleapis

What we want to achieve below is to connect to our Google Playground and create a new token each time we need to send a mail and to achieve this, we need to dedicate a function named createTransport to connect to the playground and always create a new access token when we try to send a new email, with this we will never encounter expired token error.

The createTransport function 👇


// Googleapis
const { google } = require("googleapis");

// Pull out OAuth2 from googleapis
const OAuth2 = google.auth.OAuth2;

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

// 2
  oauth2Client.setCredentials({
    refresh_token: process.env.OAUTH_REFRESH_TOKEN,
  });

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

// 3
  const transporter = nodemailer.createTransport({
    service: "gmail",
    auth: {
      type: "OAuth2",
      user: process.env.SENDER_EMAIL,
      accessToken,
      clientId: process.env.OAUTH_CLIENT_ID,
      clientSecret: process.env.OAUTH_CLIENT_SECRET,
      refreshToken: process.env.OAUTH_REFRESH_TOKEN,
    },
  });

// 4
  return transporter;
};

Explanation

After importing the googleapis and pulling out OAuth2, let's see what we have in the createTransporter function.

1. Remember this 👇?

image.png

We can't always go to connect our application to the Google playground manually, this method here 👇 is automatically doing that for us.

Connect to Google playground

2. Remember the refresh access token 👇 also?

image.png

The method below will automatically do that for us in other to keep the access token active, it requires the refresh token to generate a new access token, that is why we are passing the token from our environment-variable along with it.

Refresh Access Token.png

3. We are authenticating ourselves as the owner of the Gmail account we want to use as the transport.
4. we are returning the response from the transport authentication (approved or declined)

Now that we have an understanding of what is going on, let us update our mailOptions and our sendMail method 👇

 // Route to handle sending mails
app.post("/send_email", (req, res) => {
  attachmentUpload(req, res, async function (error) {
    if (error) {
      return res.send("Error uploading file");
    } else {
      // Pulling out the form data from the request body
      const recipient = req.body.email;
      const mailSubject = req.body.subject;
      const mailBody = req.body.message;
      const attachmentPath = req.file?.path;

      // Mail options
      let mailOptions = {
        from: process.env.SENDER_EMAIL,
        to: recipient,
        subject: mailSubject,
        text: mailBody,
        attachments: [
          {
            path: attachmentPath,
          },
        ],
      };

      try {
        // Get response from the createTransport
        let emailTransporter = await createTransporter();

        // Send email
        emailTransporter.sendMail(mailOptions, function (error, info) {
          if (error) {
            // failed block
            console.log(error);
          } else {
            // Success block
            console.log("Email sent: " + info.response);
            return res.redirect("/success.html");
          }
        });
      } catch (error) {
        return console.log(error);
      }
    }
  });
});

What we are doing above is to create an asynchronous function without our post route, because we are going to be waiting for a response from the createTransportorer function, we also created a new attachment key in our mailOpitons which will hold our attachment.

The try block is to catch any error that is going to occur during the process of connecting to the Google playground and sending out emails.

The if statement which serves as a conditional statement will check if there is an error and log it out, but if there is no error during the process, it will redirect to the success page.

Now let us test our code 💃

Because this article is long already, I have created a Github Gist Here

Update your index.js file with the code from the Git 👆 and fill the form in your browser with an attachment, like below 👇

frame_generic_light (48).png

  • Click on the send mail button, and if all goes well, you should be redirected to the Success Page 👇.

frame_generic_light (52).png

  • Proceed to check your recipient mailbox, your sent e-mail should be received 💃.

frame_generic_light (51).png

  • Full mail body with attachment displayed 👇

image.png


Cleaning Up

Now that our project is complete, there is one important thing to do, which is deleting the attachments from our server document, everytime a mail has been sent, we don't want to keep the user's files, and these files can also take up huge space.

To achieve this, we need to install a package called fs 👇

yarn add fs

The fs package allows us to perform actions with our computer files through Node JS more info HERE

Finally, let us update our sendMail method within the else success block with the code below 👇

// Send email
emailTransporter.sendMail(mailOptions, function (error, info) {
  if (error) {
    // failed block
    console.log(error);
  } else {
    // Success block
    console.log("Email sent: " + info.response);
    fs.unlink(attachmentPath, function (err) {
      if (err) {
        return res.end(err);
      } else {
        console.log(attachmentPath + " has been deleted");
        return res.redirect("/success.html");
      }
    });
  }
});

You can get the completed Github Gist for the index.js file Here

Now try to send another mail with an attachment to test if the sent files still remain in the folder, remember to manually remove the existing ones.

And that is how to implement a mailing application into your NODE JS project.

giphy (6).gif

In this article, you just learned the basics of Node JS which includes Routing, rendering HTML files, integrating packages, working with Google APIs, and file handling to mention a few. This should get you started in using these technologies to build more stuff.

  • Final Project Link:

  • Hosted Version Link:


giphy (5).gif


Wow, what a journey, I am glad you made it to the end of this article, if you enjoyed and learned from this article, I will like to connect with you, so you won't miss any of my upcoming articles.

Let's connect on



See you in the next article. Bye Bye 🙋‍♂️

image.png

If you found this helpful and want to support my blog, you can also buy me a coffee below.

 
Share this