How to make a Gmail Addon
Ryan Baker
Posted on January 13, 2024
Over the past six months, we've started building TinyCadence. 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:
This sidebar lives inside your Gmail inbox
And when you click it, it will take the contact through a flow like this:
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 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 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. Then install 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 to give your script the configuration it needs to run. Here is a small sample to get started:
{
"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:
- Your "runFunction" and "onTriggerFunction" values must be names of functions in your add-on code
- Your "oauthScopes" must be exactly the same between this file, your script project settings and your GCP project OAuth consent screen settings
- 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:
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 to Google's style guide for add-ons (which includes a GSuite UI Kit in Figma).
Now try pushing your code via clasp push
. You should then see your add-on in the sidebar of your Gmail inbox:
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 💻
Posted on January 13, 2024
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.