Scheduled Functions and Cron Jobs with Firebase Cloud Functions
Run a job every 2 minutes with Google Cloud Functions
When a user’s subscription within your app expires, how do the systems you have built know to notify them? If you're building a fintech that is involved in lending, how do you send repayment reminders to users a few days to their repayment and on the repayment day? You may also need to backup your databases at the end of each business day, how do you spin up a service to do that? These and many more are why cron jobs matter. In this tutorial, rather than building out a service that runs cron jobs, we’ll hand all of the work over to a managed service like Google Cloud scheduler.
Google Cloud scheduler combined with Firebase cloud functions allows us to write, deploy and run code that completes a task at any given time. In this tutorial, I’ll show you how we automate loan repayment reminders to our users using firebase functions and the google cloud scheduler.
Getting Started
Set up a new Firebase project by following the Getting Started with Firebase section in my article published here. Once your firebase project is setup, you’ll need to upgrade from Spark which is the free firebase plan to Blaze — a paid plan. The scheduler function we’ll be using is only available on the paid plans of firebase. Follow the instructions here to upgrade your plan.
Install firebase admin and firebase tools globally using the commands
npm install firebase-functions@latest firebase-admin@latest — save
npm install -g firebase-tools
3. If you’re on a Mac or a UNIX based system, you may run into a permissions error during installation, prefix your command line commands with sudo
. With our libraries installed, let’s get into it!
Setting up your Functions
Next, we need to log in to firebase from our command line to connect it to the project we created in Step 1. Run the command
`firebase login`. This will redirect you to your browser, prompting you to login via the UI. Login via the E-mail you connected to your firebase account to continue. Once in, return to the terminal
Navigate into the directory where you’ll like to create your new functions project. Run the command,
firebase init functions
Select the following options in the prompt
-> Use an existing project
-> Select the project you created in Step 1
-> Select JavaScript
-> When prompted to use ESlint, you can select Yes/N. For this example we'll go with N
-> Select Y to install dependencies
By completing the steps above, you should now see a functions folder created for you in your directory. Let’s run some tests to check that everything works. We’ll build a quick app that tells us the time in the US, UK, and Nigeria.
Testing Your Setup using Luxon
Navigate into your functions folder and install the luxon package — a recommended alternative to moment. We’ll be using this package
npm i luxon
In functions > index.js, write the following piece of code. I’ve added comments for clarity.
const { onRequest } = require("firebase-functions/v2/https");
const logger = require("firebase-functions/logger"); // for logging to the terminal
const { DateTime } = require("luxon"); // get the DateTime class from Luxon
exports.timeInDiffPlaces = onRequest((req, res) => {
const dt = DateTime.now();
logger.log(
"timeInLondon",
dt.setZone("Europe/London").toLocaleString(DateTime.DATETIME_FULL)
);
logger.log(
"timeInNewYork",
dt.setZone("America/New_York").toLocaleString(DateTime.DATETIME_FULL)
);
logger.log(
"timeInLagos",
dt.setZone("Africa/Lagos").toLocaleString(DateTime.DATETIME_FULL)
);
res.send("success");
});
9. Test out your code by running the command
firebase emulators:start
This will create endpoints for each function in your index.js file within the functions folder. The endpoint will be displayed in your terminal window e.g 127.0.0.1:5001/adebola-reminder/us-central1..
Now, when you go to the endpoint (in your browser) that is displayed in the terminal, you should see a page that simply displays success as we have specified in our res.send
. But take a look in your terminal window, the logger library will log out the time in New York, London, and Lagos in your terminal like below.
Scheduling E-mails to Users
Now that we have everything working fine, let’s get started writing a function to send E-mails to our users. Later, we’ll set up a cloud scheduler to trigger the sending of these emails.
Setting up Nodemailer and Gmail
We’ll be using nodemailer and Gmail to send our E-mails. Follow my article here on setting up both.
Since that article was written, Google has updated how apps can use your E-mail. Google now requires 2FA to be setup to connect it to Nodemailer. Setup 2FA on your E-mail by going here. Once done, we’ll also need to set up an app password to login to our gmail account remotely.
Learn more about app passwords here. To setup App password,
Go here. Make sure you’re logged in to your Gmail account.
Under How you sign in with Google, select 2-step verification.
You’ll be prompted to sign in to your E-mail again. Sign in
Scroll down to App passwords. Click App passwords
Enter a name for your app. Then, click Generate. A modal will pop up with your app password.
The final code is below.
const hbs = require("nodemailer-express-handlebars");
const nodemailer = require("nodemailer");
const path = require("path");
// initialize nodemailer
var transporter = nodemailer.createTransport({
service: "gmail",
auth: {
user: "youremail@gmail.com",
pass: "your_app_password",
},
});
// // point to the template folder
const handlebarOptions = {
viewEngine: {
partialsDir: path.resolve("./views/"),
defaultLayout: false,
},
viewPath: path.resolve("./views/"),
};
// // use a template file with nodemailer
transporter.use("compile", hbs(handlebarOptions));
exports.sendEmailToUsers = onRequest((req, res) => {
// setup the E-mail options
const mailOptions = {
from: '"Happpiness App" <youremail@gmail.com>', // sender address
to: "your_test_email@gmail.com", // list of receivers
subject: "Welcome!",
template: "email", // the name of the template file i.e email.handlebars
context: {
name: "Adebola", // replace {{name}} with Adebola
company: "My Company", // replace {{company}} with My Company
},
};
// trigger the sending of the E-mail
transporter.sendMail(mailOptions, function (error, info) {
if (error) {
return console.log(error);
}
logger.log("Message sent: " + info.response);
});
res.send("success");
});
Again, test that everything works by hitting the URL in your terminal window followed by /sendEmailToUsers
. Later on, we’ll extract this code into a function of its own rather than having it directly inside the serverless function.
Now for the much-awaited Scheduler
Now that we can trigger E-mails to users, we can setup a scheduler in Google cloud to trigger our function every so often. For this use case, we want to send reminders to users to repay their loan when 3 days to their repayment dates. I’ll skip the code for figuring out when a user’s repayment falls on and go ahead to show you how to set up your scheduler to call the sendEmailToUsers serverless function.
First, extract your E-mail sending logic into a new async function, like below
const sendEmailToUsers = async (emails) => {
// past the code from Setting up Nodemailer and Gmail section above
// remember that E-mails would contain an array of object so adjust your code
// accordingly
}
Next, setup the logic you need to get the names, emails and other details of the users you’ll like to E-mail. Remember to adjust the template in your views folder from the setting up nodemailer and gmail section to reflect your use case.
We’ll also need to import the onSchedule function from firebase into our code. Like below;
const { onSchedule } = require("firebase-functions/v2/scheduler");
Do this at the top of the index.js file where you have your other imports.
Finally, write the scheduler function using the App Engine syntax to trigger the sendEmailForRepayment function every 2 minutes to make sure everything works correctly. The App engine syntax reads like plain English e.g every 2 minutes
compared to the crontab syntax which looks like*/2 * * * *
representing every 1 minute. You can play around with the crontab syntax here. The crontab guru tool will help you generate the appropriate crontab syntax for your scheduler.
You may also use the crontab syntax which most people are familiar with.
exports.sendEmailsForRepayment = onSchedule("every 2 minutes", async (event) => {
// get users whose repayments are due
await getUsersWithDueRepayments()
const emailsWithRepaymentDetails = await getRepaymentDetailsForUsers()
await sendEmailToUsers(emailsWithRepaymentDetails);
logger.log("Schedule done running");
});
Deploying your Scheduled Cloud functions
Make sure to run your code using the firebase emulator to test that everything works on your local before deploying. Deploy your code using
firebase deploy
Your scheduled cloud functions should now be live!
Your schedule function should run every minute as specified in the onSchedule callback.
Logging
To monitor the performance of your serverless functions, head over to your google cloud console. Make sure you’re in the right firebase project by selecting your project name from the dropdown.
Use the searchbar to search for the name of your function, in our case sendEmailsForRepayment
. From here you’ll be able to see all the information on how that function is performing.
Pub/Sub vs HTTPs
In this example, we connected to our serverless functions via HTTP requests. In my next article, we’ll talk about another way to communicate with serverless functions using Pub/sub or publisher and subscriber. Pub/subs are event-driven.