Google Cloud Tasks is provided by Google Cloud Platform (GCP). It is a fully managed service that allows you to create, manage, and execute asynchronous tasks in a reliable and scalable way. These tasks are independent pieces of work that are to be processed outside your application flow using handlers that you create. These handlers are essentially the endpoints or services that process your tasks. You can use HTTP, app engine, pub/sub, or even custom backend handlers. In this article, we will look through the cloud task workflow and how to effectively schedule cloud functions using cloud tasks.
How does Google Cloud Tasks Work
Google Cloud Task is essentially used to manage the execution of asynchronous tasks on a high level. Here is a step-by-step description of its workflow:
Tasks Creation:
Your application creates a task with specific data (payload) and adds it to a task queue.
You can specify:
- The target handler (HTTP endpoint, App Engine, or Pub/Sub).
- Task execution time (immediate or delayed).
- Retry policies (e.g., how often and when to retry failed tasks).
Tasks Queuing:
Tasks are stored in a queue until they are ready to be executed. With queues, you can organize tasks by priority or function.
Tasks Execution:
When a task is ready to execute (immediately or after a specified delay), Google Cloud Tasks sends the task to the specified handler.
Handlers can be:
- HTTP(S) endpoints.
- App Engine services.
- Pub/Sub topics.
Task Processing:
The handler processes the task using the data provided in the task payload. After processing, the handler responds with a success or failure status. If successful, the task is marked as complete and removed from the queue else, the task is retried based on the retry policy.
You can monitor the status of tasks and queues using the Google Cloud Console or APIs.
Google Cloud Tasks vs Google Cloud Scheduler
Google Cloud Scheduler is a fully managed cron job service provided by Google Cloud Platform (GCP). It allows you to schedule and automate the execution of tasks, such as running scripts, triggering APIs, or executing Cloud Functions, at specified times or intervals.
Here is a comparative analysis of Google Cloud tasks and Google Cloud Scheduler, understanding what these two services offer will help you choose what service is the best fit for your project.
Comparative Analysis: Google Cloud Scheduler vs. Google Cloud Tasks
Aspect |
Google Cloud Scheduler |
Google Cloud Tasks |
---|---|---|
Purpose |
Automates and schedules recurring tasks or cron jobs. |
Manages asynchronous or one-off tasks with controlled execution. |
Task Execution |
Executes tasks based on a fixed schedule (e.g., hourly, daily). |
Executes tasks triggered by application events or logic. |
Concurrency |
Executes tasks one at a time per job. |
Can handle multiple tasks concurrently with queue management. |
Scalability |
Suitable for a moderate number of scheduled jobs. |
Highly scalable for managing thousands of tasks. |
Example Scenario |
Run a database cleanup script every Sunday at midnight. |
Queue a task to process an image upload triggered by a user action. |
Schedule Cloud Function using Cloud Tasks
In this example, we will go through the process of creating a cloud function that is to be evoked at a later time using cloud tasks.
Prerequisites
To make the most of this tutorial, you should have the following:
Once you’ve checked off all the prerequisites, enable the Google Cloud Tasks API from the Google Cloud console.
Once this has been successfully enabled, create a queue. You can create a queue with Google Cloud (gcloud) CLI or right there in the Google Cloud console. To create a queue via gcloud CLI then you have to first install the gcloud SDK and have it configured to your Firebase project. Once it’s been configured, run this command from the terminal to create your queue.
gcloud tasks queues create QUEUE_ID --location=LOCATION
Replace QUEUE_ID and LOCATION with your preferred values. For more details on queue creation via gcloud CLI, see here.
To create a queue directly from Google Console, navigate to Cloud Tasks and click on the create queue option to create your queue.
Now that Cloud Tasks has been set up you can now use it in your functions. To use, first install the Cloud Task client.
npm install @google-cloud/tasks
In this example, we will create a cloud function that sends emails to the email addresses in our Firestore collection. Using Google Cloud Task, we will schedule this cloud function to be called at a specified time. Let’s dive in.
Install nodemailer
, because we will first create a sendEmail
function.
npm install nodemailer
Here is the send email function:
import * as functions from "firebase-functions";
import * as admin from "firebase-admin";
import * as nodemailer from "nodemailer";
import {OAuth2Client} from "google-auth-library";
admin.initializeApp();
const transporter = nodemailer.createTransport({
service: "gmail",
auth: {
user: "[email protected]",
pass: "sender pass",
},
});
/**
* This function sends emails.
*/
export const sendEmail = functions.https.onRequest(async (req, res) => {
const projectId = JSON.parse(process.env.FIREBASE_CONFIG!).projectId;
const location = "us-central1";
const authorizationHeader = req.headers.authorization;
if (!authorizationHeader) {
res.status(401).send("unauthorized token");
return;
}
// if authorizationHeader is not null access the token
const token = authorizationHeader.split(" ")[1];
// verify ID token
try {
await verifyToken(token, location, projectId);
} catch (error) {
console.log(error);
res.status(401).send("Unauthorized token");
return;
}
try {
const snapshot = await admin
.firestore()
.collection("email_addresses")
.get();
if (snapshot.empty) {
res.status(404).send("No email addresses found in the collection.");
return;
}
const emailAddresses: string[] = [];
snapshot.forEach((doc) => {
const data = doc.data();
if (data.email) {
emailAddresses.push(data.email);
}
});
if (emailAddresses.length === 0) {
res.status(404).send("No valid email addresses found.");
return;
}
const promises = emailAddresses.map((email) => {
const mailOptions = {
from: "[email protected]",
to: email,
subject: "Welcome to Our Service!",
text: `Hello, ${email}! Welcome to our platform.
We're excited to have you on board!`,
};
return transporter.sendMail(mailOptions);
});
await Promise.all(promises);
res.status(200).send("Emails sent successfully!");
} catch (error) {
console.error("Error sending emails:", error);
res.status(500).send("An error occurred while sending emails.");
}
});
This function extracts the list of email addresses in the collection and then using the nodemailer
, it sends emails to every one of those email addresses. This function also has an auth guard which prevents it from being called by those that aren’t authorized. This auth guard first checks if the auth token is contained in the header, if not it throws an error. However, if a token is contained, using the verifyToken
function, it verifies it.
For the verifyToken function install the google-auth-library. This is what will be used to verify the token.
npm install google-auth-library
/**
* This function verifies token
* @param {string} token
* @param {string} location
* @param {string} projectId
* @return {Promise<object>}
*/
async function verifyToken(
token: string,
location: string,
projectId: string,
): Promise<object> {
const client = new OAuth2Client();
const ticket = await client.verifyIdToken({
idToken: token,
audience: `https://${location}-${projectId}.cloudfunctions.net/sendEmail`,
});
const payload = ticket.getPayload();
if (!payload) {
throw new Error("Invalid token: Payload is undefined.");
}
return payload;
}
Now, to the fun part, scheduling this function using the cloud tasks. The creation of this task will be triggered when a new document is created for the email_addresses collection in Firestore. Note that you can use any user-related action to trigger the creation of your task. Your task can’t be created without a trigger.
Import the following to your file:
import {CloudTasksClient, protos} from "@google-cloud/tasks";
import * as functions from "firebase-functions";
Next, define the Firestore onCreate trigger function
export const onCreateEmail = functions.firestore
.document("/email_addresses/{emailId}")
.onCreate(async (snapshot) => {
});
Next, add the logic for defining and creating a task
export const onCreateEmail = functions.firestore
.document("/email_addresses/{emailId}")
.onCreate(async (snapshot) => {
const data = snapshot.data();
console.log(data);
const projectId = JSON.parse(process.env.FIREBASE_CONFIG!).projectId;
const location = "us-central1";
const queue = "my-scheduler";
const taskClient = new CloudTasksClient();
const queuePath: string = taskClient.queuePath(projectId, location, queue);
const url = `https://${location}-${projectId}.cloudfunctions.net/sendEmail`;
const taskName =
`projects/${projectId}/locations/${location}/queues/${queue}/tasks`;
const serviceAccountEmail =
"SERVICE-ACCOUNT-EMAIL";
const task = {
name: `${taskName}/myTask-${Date.now()}`,
httpRequest: {
httpMethod: protos.google.cloud.tasks.v2.HttpMethod.POST,
url: url,
headers: {
"Content-Type": "application/json",
},
oidcToken: {
serviceAccountEmail,
audience: `https://${location}-${projectId}.cloudfunctions.net/sendEmail`,
},
body: Buffer.from(JSON.stringify({})).toString("base64"),
},
scheduleTime: {
seconds: Math.floor(Date.now() / 1000) + 2 * 60,
},
};
const request = {parent: queuePath, task: task};
const [response] = await taskClient.createTask(request);
functions.logger.info(
`Task ${response.name} scheduled for 2mins later for user`,
);
});
Breaking down the code snippet above, you’ll notice that:
- We first defined all the variables required to create a task. These variables include
- the
projectId
refers to your FirebaseprojectId
. You can either hardcode it or use the env value as can be seen above. This is ideal when you have more than one project using the same function. - the
location
andqueue
are the same values that you defined when creating the queue - the
url
which is the cloud function to be executed - the
taskClient
for the cloud task sdk - the
queuePath
which is gotten from thetaskClient
- the
taskName
is created using a hierarchical naming scheme, which allows Google Cloud Tasks to uniquely identify tasks, across multiple projects, locations, and queues. This helps to prevent conflicts. - the
serviceAccountEmail
is used as an extra security layer. This is optional but setting up your service account helps to ensure that thesendEmail
function can only be called via the authenticated service account.- To get your service account email:
- create a service account by going to the IAM & ADMIN section in your project on the Google console
- select the Service Accounts option, on the sidebar that has all the options under IAM & ADMIN
- click on Create Service Account
- name your service account and assign Cloud Function Invoker, Cloud Tasks Enqueuer, and, Service Account Token Creator roles to it
- retrieve your service account email
- select the IAM option on the sidebar
- click on grant access and in the field New Principals add your service account email there and grant the above roles to it
- To get your service account email:
- the
- Next, we configured the cloud task:
- In the task name, we added a
Date.now()
value so that all the created task names are unique - In the
oidcToken
field, we passed the service account email and audience. In the audience field, we pass the endpoint that we initially defined.- Using the endpoint as an audience helps to ensure that the token is used for the right service.
- If your endpoint has query params, remove the query params and pass just the endpoint to the audience field.
- The service account email generates an OpenId Connect (OIDC) token when the task is about to be executed.
- The OIDC token is sent as part of the authorization header. This is then validated by the cloud function which ensures the request is authenticated and authorized. The cloud function also validates the audience value, confirming that it matches its URL and that the token is intended for this service.
- the body field is used in passing payload. In this case, we don’t need to pass any payload, so it is empty.
- in the
scheduleTime
we defined when this task should be executed
- In the task name, we added a
- Finally, using the
taskClient
, we define thecreateTask
function.
Now when a new email is added to the email_addresses collection, a new task is created and queued which will call the sendEmail
function at the scheduled time.
Conclusion
Finally, we have come to the end of this article. So far, we defined the Google Cloud Tasks and its workflow. We also explored the differences between Google Cloud Task and Google Cloud Scheduler, which are similar but offer different services.
With what you have learned from this article you can schedule cloud functions or any of the other handlers to be called at the specified time, you can also add a service account to your task configuration to ensure security is covered and much more.
If you found this article helpful, you can support it by leaving a like or comment. You can also follow me for more related articles.