Automating email reminders with scheduled functions
by Simon MacDonald
@macdonst
on
Photo by Brett Jordan on Unsplash
Are there any repetitive tasks in your life that would benefit from automation? I run a weekly ice hockey game, and even though it is at the same place and time every week, the dummies that I play with still need a weekly reminder email. So I decided to create a scheduled function to send out that weekly reminder for me.
Provisioning the new scheduled function
The app.arc
file in the root of your project is where you define all of your app’s infrastructure as code. Add an entry to the @scheduled
section to provision a new scheduled function.
@scheduled
weekly-reminder cron(0 8 ? * WED *)
This will schedule a job to invoke the function every Wednesday at 8 am. You can also schedule functions to be invoked at a rate
of your choosing (more information on setting cron and rate).
Then we run arc init
to create the weekly reminder function at src/scheduled/weekly-reminder/index.js
. The source code of the function is pretty basic:
// src/scheduled/weekly-reminder/index.js
exports.handler = async function scheduled (event) {
console.log(JSON.stringify(event, null, 2))
return
}
Install dependencies
Since this function will run 52 times a year, I’m going to bypass setting up AWS SES and instead use my Gmail account and nodemailer
to send the weekly emails.
npm install google-auth-library nodemailer
We’ll use the google-auth-library
to get authenticated with my Gmail account and nodemailer
to send our email.
Note: when dealing with Google APIs in cloud functions, look for smaller dependencies than the 100 MB googleapis package.
Then we need to update our handler to require these new packages:
+ const nodemailer = require('nodemailer')
+ const { OAuth2Client } = require('google-auth-library')
exports.handler = async function scheduled(event) {
console.log(JSON.stringify(event, null, 2))
return
}
Authenticating with Google
First, follow these steps to create OAuth2 credentials for the scheduled function. Once you complete these steps, you will have a client ID and secret you can use to authenticate with Google. Next, you will need to generate a refresh token from the OAuth Playground.
Step 1: Enter credentials
- Open the Google OAuth Playground
- Fill in your Client ID and Client Secret
- And click Close.
Step 2: Authorize your app to access your Gmail account
- Select Gmail API in Side Panel
- Click Authorize APIs
Step 3: Get your refresh token
- Exchange authorization code for tokens
- Copy Refresh token
Woof! That is a bit of a pain but don’t worry; you won’t have to do it again anytime soon if you store your refresh token in a safe place. Also, I’m not writing this blog post to remember how to do this next time. 😅
Sending Emails
Let’s get back to some code and away from ClickOps.
Obtain an authorization token
We need to get an authorization token before sending an email via our Gmail account. Finally, all the pain of getting a refresh token pays off. First, we’ll create a new OAuth 2.0 Client using our client ID and secret. Then we’ll update the client by setting refresh token as our credentials. Finally, we can get the request headers and parse the Authorization header to return our access token.
const oauth2Client = new OAuth2Client(
clientId,
clientSecret,
'https://developers.google.com/oauthplayground'
)
oauth2Client.setCredentials({ refresh_token })
const { Authorization } = await oauth2Client.getRequestHeaders()
const accessToken =
Authorization?.split(' ')[0] === 'Bearer'
? Authorization.split(' ')[1]
: null
Send an email
After all this boilerplate code, we can finally send an email. We’ll use the nodemailer
package to create an SMTP client that authenticates with Gmail using the previously retrieved credentials. Then we send our reminder email out to everyone and close the connection to Gmail.
const smtpTransport = nodemailer.createTransport({
service: 'gmail',
auth: {
type: 'OAuth2',
user: mailUser,
clientId,
clientSecret,
refreshToken: refresh_token,
accessToken: accessToken
}
})
const sendResponse = await smtpTransport.sendMail({
from: mailUser,
// comma separated list of email addresses
to: 'test@begin.com, test2@begin.com',
subject: 'Hockey Reminder',
html: `<b>Don't forget we have hockey on Friday night!`,
generateTextFromHTML: true
})
await smtpTransport.close()
Full source code of the weekly reminder function
const nodemailer = require('nodemailer')
const { OAuth2Client } = require('google-auth-library')
exports.handler = async function scheduled(event) {
const clientId = process.env.GOOGLE_CLIENT_ID
const clientSecret = process.env.GOOGLE_CLIENT_SECRET
const refresh_token = process.env.GOOGLE_REFRESH_TOKEN
const mailUser = process.env.MAIL_USER
const oauth2Client = new OAuth2Client(
clientId,
clientSecret,
'https://developers.google.com/oauthplayground'
)
oauth2Client.setCredentials({ refresh_token })
const { Authorization } = await oauth2Client.getRequestHeaders()
const accessToken =
Authorization?.split(' ')[0] === 'Bearer'
? Authorization.split(' ')[1]
: null
const smtpTransport = nodemailer.createTransport({
service: 'gmail',
auth: {
type: 'OAuth2',
user: mailUser,
clientId,
clientSecret,
refreshToken: refresh_token,
accessToken: accessToken
}
})
const sendResponse = await smtpTransport.sendMail({
from: mailUser,
// comma separated list of email addresses
to: 'test@begin.com, test2@begin.com',
subject: 'Hockey Reminder',
html: `<b>Don't forget we have hockey on Friday night!</b>`,
generateTextFromHTML: true
})
await smtpTransport.close()
return
}
In closing
Well that was a fun diversion and now it has me thinking what other tasks I can automate by using scheduled functions. Let me know on Twitter what use cases you have for scheduled functions.