Making a Gmail bot with Apps Script and TypeScript

Google Apps Script is one of the best hidden features of Gmail.

Did you ever want just a bit more flexibility from a filter? Maybe the ability to remove a label, or match on a header, or just decide the order they are applied in.

Apps Script can do all that and then some. They are simple JavaScript programs with access to the Gmail API that run as cron jobs on Google servers. They are free and don't require a GCP account. They can even send emails.

One could build some pretty complete bots by giving them their own Gmail account, but here I just wanted to mark Gerrit CL threads as read when I was the last to act on them.

I know I could just write a bot in Go using the APIs, but then I'd have to take care of deployment, and authentication, and it's just not worth it anymore. Apps Script are point and click.

This is a "Technical note" post, if you want to only follow a subset of this blog check out the tags.

Setting one up

a Script ready to go

For setting up a script I'll point you to one of Benjojo's projects, who told me about this feature. It has screenshots and everything.

Come back when you have it running. You can use this minimal script as a first program just to trigger permissions:

function ProcessInbox() {  
    GmailApp.getInboxUnreadCount();
}

There's no deployment or authentication effort beyond setting the schedule and clicking through the OAuth dialog.

Let's get serious

The IDE at scripts.google.com makes an attempt at tracking types for autocompletion, but it doesn't even cross forEach or function boundaries. Not enough to make tolerable a language that has 1K points controversial StackOverflow answers on how to iterate an array.

And anyway I want to use my editor and git, like a proper homo sapiens, so let's see how to develop scripts locally with clasp and TypeScript. BTW, TypeScript is awesome.

Start by installing clasp, and don't skip the part about enabling the Apps Script API. Do a clasp login, too.

Then in a new folder create a .clasp.json file with this content:

{
    "scriptID": "1...",
    "rootDir": "built"
}

You'll find the scriptID in the scripts.google.com URL, before the /edit part. We use built as the rootDir so that we can put our TypeScript source outside of it.

Just once run clasp pull to populate the built/appsscript.json file. clasp push will upload to our Script the content of built which we will generate soon.

Create a TypeScript config file tsconfig.json file like this (most things are preferences, but notice the target, lib and outDir):

{
    "compilerOptions": {
        "outDir": "./built",
        "noImplicitAny": true,
        "strictNullChecks": true,
        "noImplicitThis": true,
        "noEmitOnError": true,
        "target": "ES3",
        "lib": ["ES2015"]
    },
    "include": [
        "./src/**/*"
    ]
}

Then put some TypeScript code in src/Code.ts and run tsc --pretty to generate JavaScript in built!

And here's the kicker: running npm install --save @types/google-apps-script will make type definitions for the Gmail API available to TypeScript, so an editor like Visual Studio Code will come with proper autocompletion out of the box.

This is getting a bit too hip, so we add an old-fashioned Makefile:

.PHONY: build deploy

build:  
    npm install --silent
    tsc --pretty

deploy: build  
    clasp push

And sprinkle .gitignore to taste. I like to exclude the generated JavaScript (but not the JSON metadata) and the node_modules as we have a package-lock.json:

/built/*.js
/node_modules

Now you can develop in TypeScript inside src, with full types and autocompletion support, and deploy to your Apps Script with make deploy. Google will run the Script every 5 minutes (if you followed Ben's instructions).

For more yak shaving, follow me on Twitter.