How to make a Gmail Addon
Tags: webdev, programming, googlecloud, tutorial
Published: 2024-01-13 14:32:08

Over the past six months, we've started building [TinyCadence](https://www.tinycadence.com). TinyCadence is a simple Gmail inbox automation tool that allows you to create email sequences and enroll contacts in them (think: a simpler version of Outreach or Salesloft). We want users to not interrupt their work in order to use the automation, so we decided to build an integrated Gmail experience. Ideally, the users would have an experience akin to this:

![Additional sidebar in Gmail shown to enhance your inbox](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/yu41opkl994q2m5k5nxz.png)
_This sidebar lives inside your Gmail inbox_

And when you click it, it will take the contact through a flow like this:


![Automated sequence of steps for sending emails](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/jw7vqf2oj4v2bagnhvr0.png)
_An example email sequence._

If you haven't worked with Google's developer documentation before, it leaves something to be desired. They have great detail, but are lacking illustrative examples, a unified table of contents, and an index to find the correct documentation.

I decided to write this blog post in order to help the community and demystify the process of making a Gmail add-on as I learn about it.

## Why a Gmail add-on?
For a while, we decided to weigh the options between a Chrome Extension and a Gmail Add-On. Chrome Extensions came with great flexibility, but at a great cost: depending on DOM manipulation. This meant that we just had to cross our fingers and hope that the layout of the Gmail Inbox never changed, or else test in all possible variations of it. The fragility of it kept us from diving in head first to an extension.

Reading the [introduction documentation](https://developers.google.com/apps-script/add-ons/gmail) to Google Add-Ons felt like the right fit based on their pitch:

> The purpose of many emails is to get the recipient to do a specific task or reach a goal, such as adding a calendar event, filling out a form, making a reservation, or using other applications. However, recipients then have to complete the task without any further prompting, often doing a number of manual steps.
> You can save time and effort for your users by automating these tasks with Google Workspace Add-ons.

This seemed to fit what we wanted to do like a glove: provide an integrated Gmail experience with added context and automation.

## The Setup

In order to get started with your Gmail Add-On, you will need:

- a Google cloud project with OAuth consent screen info filled out (_not detailed here_)
- an app server that can serve data to your Gmail add-on client code (_not detailed here_)
- a Google app script project (this is what you will publish as your "Gmail Add-On")
- your add-on code

First, you will need to create an App Script project. Navigate [here](https://script.google.com/home/my) to create one. The easiest way to manage deployments and upload of your add-on code is with clasp. In order to use clasp, turn on [the setting that allows CLI tools](https://script.google.com/home/usersettings). Then install [clasp](https://developers.google.com/apps-script/guides/clasp) and run `clasp login`. Once you login, run `clasp clone <script ID>` in order to get the app scripts code locally. To get your script ID, navigate to the project settings page and scroll down.

Next, you need to tie you script project to a Google Cloud Platform (GCP) project. This is because you need an OAuth consent screen, and the only way to have one and get it approved/verified is through a GCP project. Once you have created your GCP project, copy the project ID. In your App Scripts project settings, click "change project" under "Google Cloud Platform (GCP) Project" and paste in the project number for your Google Cloud Project (found on the cloud project dashboard under "Project Info"). This makes sure that your GCP Project and app script are tied together. You need to tie them together because they are approved by different teams and you need your GCP project approved before your add-on can be approved.

From here, you are ready to begin coding!

## The Code

Once you used clasp to clone the project, you need to edit your project [manifest](https://developers.google.com/apps-script/add-ons/concepts/workspace-manifests) to give your script the configuration it needs to run. Here is a small sample to get started:

```json
{
  "timeZone": "America/New_York",
  "dependencies": {
    "enabledAdvancedServices": [
      {
        "serviceId": "gmail",
        "userSymbol": "Gmail",
        "version": "v1"
      }
    ]
  },
  "exceptionLogging": "STACKDRIVER",
  "runtimeVersion": "V8",
  "addOns": {
    "common": {
      "homepageTrigger": {
        "runFunction": "onHomepage",
        "enabled": true
      },
      "name": "TinyCadence",
      "useLocaleFromApp": true
    },
    "gmail": {
      "contextualTriggers": [
        {
          "unconditional": {},
          "onTriggerFunction": "onGmailMessage"
        }
      ]
    }
  },
  "oauthScopes": [
    "https://www.googleapis.com/auth/gmail.addons.current.message.metadata",
    "https://www.googleapis.com/auth/userinfo.email",
    "https://www.googleapis.com/auth/script.external_request",
    "https://www.googleapis.com/auth/script.locale",
    "https://www.googleapis.com/auth/gmail.addons.execute",
    "https://www.googleapis.com/auth/gmail.modify",
    "https://www.googleapis.com/auth/calendar.events.readonly"
  ],
  "urlFetchWhitelist": [
    "https://*.ngrok.io/",
    "https://*.ngrok-free.app/",
    "https://*.api.your-website.com/"
  ]
}
```

There are a few important things here:

1. Your "runFunction" and "onTriggerFunction" values must be names of functions in your add-on code
2. Your "oauthScopes" must be exactly the same between this file, your script project settings and your GCP project OAuth consent screen settings
3. Your "urlFetchWhitelist" should include some way for you to send requests to a test environment. In this example, I used ngrok to serve API requests from my local server while developing.

In your app scripts project code editor, select the main .gs file and click "Run". This will prompt you to authorize your test add-on. Accept the permissions and then select "test deployments" from the "deployments" dropdown.

Try starting out with this sample code for your script project:

```javascript
const BASE_API_URL = 'https://my-ngrok-id.ngrok-free.app';

const getFetchOptions = (method) => {
  const token = ScriptApp.getOAuthToken();
  return {
    // I tried sending a header with the prefix "X-Google" once
    // be aware that Google will silently drop these, as they
    // reserve those headers for themself
    headers: {'x-app-script-token': token},
    method: method || 'GET'
  };
}

const fetchHomepageData = () => {
  // you need to implement this in your server and return something
  const res = UrlFetchApp.fetch(`${BASE_API_URL}/google-addon/homepage`, getFetchOptions());
  return JSON.parse(res.getContentText());
}

const onHomepage = () => {
  try {
    const {users} = fetchHomepageData();
    console.log(users);
    if (users.length === 0) {
      return makePromptSignupCard();
    } else {
      return makeHelloWorldCard(users);
    }
  } catch (error) {
    console.log('error', error);
    return makePromptSignupCard();
  }
}

const makePromptSignupCard = () => (
  CardService
    .newCardBuilder()
    .setHeader(
      CardService.newCardHeader()
        .setTitle('TinyCadence in Gmail')
        .setSubtitle('Use cadences to supercharge your inbox'))
    .addSection(
      CardService.newCardSection()
        .setHeader('Signup')
        .addWidget(
          CardService
            .newTextParagraph()
            .setText(
              'With TinyCadence, you can automate the boring ' +
              'parts of outreach so you can focus on the engaged ' +
              'respondents without wasting time.'))
        .addWidget(
          CardService
            .newTextButton()
            .setText('Signup')
            .setTextButtonStyle(CardService.TextButtonStyle.FILLED)
            .setBackgroundColor('#A160FF')
            .setOpenLink(
              CardService.newOpenLink().setUrl('https://app.tinycadence.com/auth/signup')
            ))
        )
     .addCardAction(makeVisitTCAction())
     .build()
);

const makeHelloWorldCard = () => (
  CardService
    .newCardBuilder()
    .setHeader(
      CardService.newCardHeader()
        .setTitle('TinyCadence in Gmail')
        .setSubtitle('Welcome back')
    )
    .build()
);
```

Apparently, there's a way to make all this syntax from just JSON, but I haven't take the time to learn that (I'm also not sure it would save much time).

If you're curious what components are available and what they look like, [here is a link](https://developers.googleblog.com/2020/07/introducing-easier-way-to-design-your-gsuite-add-on.html?utm_source=developers.google.com&utm_medium=referral) to Google's style guide for add-ons (which includes a [GSuite UI Kit in Figma](https://www.figma.com/community/file/848275628252039675)).

Now try pushing your code via `clasp push`. You should then see your add-on in the sidebar of your Gmail inbox:


![Image showing a new add-on in the Gmail sidebar](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/rsj4te93uzbh2alz51e2.png)

If you don't see it, make sure that you are signed into the same account that you authorized earlier.

## Conclusion

While the process isn't very easy or clear, hopefully you can learn from my trial and error.

From here, the documentation linked above should be a great help to get more advanced UI and interactions going!

Happy coding 💻