How To Integrate Gmail API In Your Node.js Application
Tags: gmail, gmailclient, gmailapi, node
Published: 2024-06-01 17:02:12

The Gmail API provides a powerful and flexible solution for integrating Gmail's robust features into your app. With it, you can enable users to read, send, and organise their emails, manage drafts, labels and much more. This guide will walk you through the steps to seamlessly integrate the Gmail API, transforming your application into a centralised hub for email management.

Whether you’re developing a productivity tool, a customer relationship management (CRM) system, or any application that benefits from streamlined email interactions, this guide will equip you with the necessary knowledge and tools. We'll cover everything from setting up API access and handling authentication to implementing core email functionalities.

A more robust implementation of a Gmail client using Gmail APIs can be found here [github.com/Goodness-Chukwudi/gmail-client](https://github.com/Goodness-Chukwudi/gmail-client).

## **Setting Up Your Gmail Project On Google Cloud Console**

**1. Create a project**. To create and set up your project on Google cloud console, head over to [console.cloud.google.com](https://console.cloud.google.com) and sign in with your gmail account. From the project dropdown at the top left corner, create a new project or select an existing project if you have any.


![Create a project](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/i3cauuekgj8yhsff1d5q.png)

After creating a project, select `APIs and services` from the navigation menu at the top left corner.

**2. Enable Gmail APIs**. Click on `+ ENABLE APIS AND SERVICES` at the top of the page. This opens the API library page. Search for `Gmail API` select it and click on `Enable` to activate Gmail API.

**3. Generate credentials for your app**. To create the credentials you will use to connect from your app, click on `Credentials` in the sidebar under `APIs and services` and click on `+ CREATE CREDENTIALS` at the top of the screen. Select `OAuth Client ID`, select `Web application` as application type and click `Create`. Copy the `Client ID` and `Client secret`; You will need it to connect to your Google project from your Node.js app.

![Generate credentials](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/1j0v1imftx57ydq3oyaw.png)

**4. OAuth screen Setup**.  You will be using OAuth to give your app access to a user’s mailbox. Select `OAuth consent screen` under `Credentials` and fill the name of the app, support email and other details. Don’t bother setting up the scopes, you will do that from your application. Once this is done, you will head to your system to write some code

## **Code Setup and API Integrations**
This tutorial is meant for people with prior knowledge of Node.js already. Consequently, it guide does not cover installation and setup of a Node.js app.

**1. Project setup and dependency installation**. After setting up your Node.js project, on your terminal run `npm i googleapis` to install googleapis from npm. Add the credentials you created to your `.env` file: `GOOGLE_CLIENT_ID`,  `GOOGLE_CLIENT_SECRET`, `GMAIL_REDIRECT_URL`(during OAuth authentication, google will call this endpoint with the authentication code if successful). The redirect url needs to be a live url on your server's domain, but you can use [ngrok](https://ngrok.com) to expose your localhost to the internet to enable us to get the redirect during OAuth authentication. Head over to [ngrok.com](https://ngrok.com) and follow the instructions there to set up ngrok locally. Once you have set up ngrok, the redirect url should be in this format `<your-ngrok-ur/a-route-of-your-choice>`. Eg. https://my-url/call_back_path.

**2. OAuth authentication**. Create a file, say `gmail_service.js` for your Gmail API calls code. Import googleapis and initialise OAuth2 and Gmail client from googleapis. We will use nodemailer’s MailComposer to compose a MIME message for our emails. So go ahead and install nodemailer from npm: `npm i nodemailer`. You will be needing it later.

```
import { google } from "googleapis";
const MailComposer = require("nodemailer/lib/mail-composer");

const oauth2Client = new google.auth.OAuth2(
  process.env.GOOGLE_CLIENT_ID,
  process.env.GOOGLE_CLIENT_SECRET,
  process.env.GMAIL_REDIRECT_URL
);

google.options({auth: oauth2Client})
const gmailClient = google.gmail('v1');
```
Setting the auth on the Gmail client with the OAuth client authenticates our app's request to our Google project.

**3. Generate the consent page url** using the OAuth2 client and open it on a browser to give access to the app. In a real world scenario, this url is returned to the front end part of your app. This enables the user to grant your app access to their email.

```
const scopes = [
    "https://www.googleapis.com/auth/gmail.labels",
    "https://www.googleapis.com/auth/gmail.modify",
  ];
  const url = oauth2Client.generateAuthUrl({
    access_type: "offline",
    scope: scopes,
    prompt: "consent"
});
console.log(url)
```
The scopes specifies the type of access and access level the app will request from the user. Refer to [developers.google.com/gmail/api/auth/scopes](https://developers.google.com/gmail/api/auth/scopes) for more on scopes.

**4. Extract refresh token from auth code**. You need to specify a route that matches the redirect url you specified in your application. This route must be a GET endpoint. Once the OAuth authentication is complete, Google will call your redirect url with the auth code in the request query. Extract the credentials from the returned code using the OAuth client. The extracted token is an object containing the refresh token among other fields. This refresh token should be saved in a secured place, preferably on your DB. This refresh token is sent alongside and validates every request to the API for this user.

```
import { Router } from "express";

const router = Router();
router.get("[path]", async (req, res) => {
    const code = req.query.code as string;
    const {tokens} = await oauth2Client.getToken(code)
    oauth2Client.setCredentials(tokens);
    console.log("refresh_token =========>  ", tokens.refresh_token);
})

```

## **Accessing Gmail APIs**
Now that your Gmail client is set up and authenticated, you can now call Gmail’s API to manage an email's inbox, send email and much more. Go to [developers.google.com/gmail/api/guides](https://developers.google.com/gmail/api/guides) to see all available APIs and their usages.

**1. Retrieving messages**. The list method retrieves messages in the provided mailbox. `me` refers to the email of the user attached to the refresh token of a request. You can replace `me` with the actual email address.

```
function retrieveMessages() {
  const messagesResponse = await gmailClient.users.messages.list({userId: 'me'});
  const messages = messagesResponse.data.messages;
  console.log("messages =========>  ", messages);
}

await retrieveMessages();
```

**2. Get one message.**

```
function getOneMessage(messageId) {
  const messageResponse = await gmailClient.users.messages.get({userId: 'me', id: messageId});
  const message = messageResponse.data;
  
  let messageBody = "";
  const body = message.payload?.parts ? message.payload.parts[1].body : message.payload?.body;
  
  if (body?.data) {
    const buffer = Buffer.from(body.data, "base64");
    messageBody = buffer.toString("utf-8");
  } 
  
  //For messages with attachments
  if (body?.attachmentId) {
    const textPart = message.payload?.parts[0]?.parts[1]?.body?.data;
    if (textPart) {
      const buffer = Buffer.from(textPart, "base64");
      messageBody = buffer.toString("utf-8");
    }
  }
  
  console.log("message =========>  ", messageBody);
}

await getOneMessage(messageId);
```

**3. List drafts**. The list method lists the drafts in the provided mailbox.

```
function listDrafts() {
  const draftsResponse = await gmailClient.users.drafts.list({userId: 'me'});
  const drafts = draftsResponse.data.drafts;
  console.log("drafts =========>  ", drafts);
}

await listDrafts();
```

**4. Get one draft**

```
function getOneDraft(draftId) {
  const draftResponse = await gmailClient.users.drafts.get({userId: 'me', id: draftId});
  const draft = draftResponse.data;
  const payload = draft.message?.payload;
  
  let messageBody = "";
  const body = payload?.parts ? payload.parts[1].body : payload?.body;
  
  if (body?.data) {
    const buffer = Buffer.from(body.data, "base64");
    messageBody = buffer.toString("utf-8");
  } 
  
  //For drafts with attachments
  if (body?.attachmentId) {
    //@ts-ignore
    const textPart = payload?.parts[0]?.parts[1]?.body?.data;
    if (textPart) {
      const buffer = Buffer.from(textPart, "base64");
      messageBody = buffer.toString("utf-8");
    }
  }
  console.log("draft =========>  ", messageBody); 
}

await getOneDraft(draftId);
```

**5. List labels**.

```
function listLabels() {
  const labelsResponse = await gmailClient.users.labels.list({userId: 'me'});
  const labels = labelsResponse.data.labels;
  console.log("labels =========>  ", labels);
}

await listLabels();
```

**6. Delete a message**. A message is deleted by adding a `TRASH` label to it. Go to [developers.google.com/gmail/api/guides/labels](https://developers.google.com/gmail/api/guides/labels) for more on message labels.

```
function deleteMessage(messageId) {
  await gmailClient.users.messages.trash({userId: 'me', id: messageId});
}

await deleteMessage(messageId);
```

**7. Batch delete messages**. To delete multiple messages at a go, add `TRASH` to the label of each message.

```
function deleteMessages(messageIds) {
  const requestBody = {ids: messageIds, addLabelIds: ["TRASH"]};
  await gmailClient.users.messages.batchModify({userId: 'me', requestBody});
}

await deleteMessages(messageIds);
```

**8. Restore a deleted message**.

```
function restoreMessage(messageId) {
  await gmailClient.users.messages.untrash({userId: 'me', id: messageId});
}

await restoreMessage(messageId);
```

**9. Send mail**. The email API accepts only MIME email messages that's compliant with [RFC 2822](https://datatracker.ietf.org/doc/html/rfc2822) and encoded as a base64 string.

```
function encodeEmail(email) {
  const mail = new MailComposer({
      from: "me",
      to: email.recipient,
      cc: email.cc,
      html: email.body,
      subject: email.subject,
      textEncoding: "base64"
    });

    mail.compile().build((err, message) => {
      if (err) console.log(err);
      const encodedEmail = Buffer
        .from(message)
        .toString('base64')
        .replace(/\+/g, '-')
        .replace(/\//g, '_')
        .replace(/=+$/, '');
        
      return encodedEmail;
    });
}

function sendMessage(emailObject) {
  const requestBody = {
      raw: await encodeEmail(emailObject)
  }
  await gmailClient.users.messages.send({userId: "me", requestBody});
}

await sendMessage(emailObject);
```

**10. Create drafts**. Drafts represent an unsent message with the `DRAFT` label applied. The input will be the same with send email API inputs.

```
function createDraft(emailObject) {
  const requestBody = {
    raw: await encodeEmail()
  }
  
  await gmailClient.users.drafts.create({userId: 'me', requestBody});
}

await createDraft(emailObject);
```

**11. Update drafts**. Messages do not actually get updated. The update request instead, destroys the message attached to the draft you want to update and replaces it with a new message containing the new MIME message you sent.

```
function updateDraft(emailObject, draftId) {
  const requestBody = {
    raw: await encodeEmail()
  }
  
  await gmailClient.users.drafts.update({userId: 'me', id: draftId, requestBody});
}

await updateDraft(emailObject, draftId);
```

**12. Delete drafts**.

```
function deleteDraft(draftId) {
  await gmailClient.users.drafts.delete({userId: 'me', id: draftId});
}

await deleteDraft(draftId);
```

**13. Send drafts**.

```
function sendDraft(draftId) {
  const requestBody = {
    id: draftId
  }
  await gmailClient.users.drafts.send({userId: 'me', requestBody});
}

await sendDraft(draftId);
```

**14. Revoke access**. Though users can revoke access given to your application from their Gmail app, you can also give them a way to revoke the access from within your application using the `revokeToken` API.

```
function revokeAppAccess(refreshToken) {
  await oauth2Client.revokeToken(refreshToken);
}

await revokeAppAccess(refreshToken);
```

## **Conclusion**
A full list and implementation guide for all the features available on the Gmail API is available on [developers.google.com/gmail/api/guides](https://developers.google.com/gmail/api/guides).

## **What’s Next?**
A more robust implementation of a Gmail client app, with user authentication management using Node.js and Express.js and MongoDB is available on [github.com/Goodness-Chukwudi/gmail-client](https://github.com/Goodness-Chukwudi/gmail-client). Please leave a star on the repo if you find it helpful. Also feel free to raise a PR if you want to contribute more features or improve existing ones. Suggestions are welcomed as well. Reach me on [ibechechukwudi@gmail.com](mailto:ibechechukwudi@gmail.com)