Timezone Aware Events with Taskless
Imagine, for a moment, you're building a mobile-first professional networking app. One of its most important features is the "Daily Briefing" where you tell your user about their upcoming day, sent at 8:30 every morning. Because our users are business professionals, they're likely to be traveling and changing time zones frequently.
So, when is 8:30 am for that user? With Taskless, you can make your events timezone aware, and that Daily Briefing arrives exactly when it should.
A completed example is available in the examples folder if you'd like to just look at the final product.
Our Core Event & API
First, it's helpful to be familiar with how jobs are enqueued in Taskless. This is because when a job in Taskless is enqueued with the same Job Identifier
, it automatically updates the matching job if it exists. In database terms, this is an upsert.
All jobs scheduled in Taskless are treated like an
upsert
To keep our data payload as small as possible, we're only going to deal with the userId
we're scheduling a briefing for. In our API endpoint, we would add any additional security checks before we agree to enqueue the job. Our job doesn't do much, but it's easy to see where we'd add all our backend service calls and email generation work. We'll create this queue inside of our Next API folder at /api/queues/briefing.ts
.
1// api/queues/briefing.ts23import { createQueue } from "@taskless/next";45interface DailyBriefing {6 userId: string;7}89export default createQueue<DailyBriefing>(10 "daily-briefing",11 "/api/queues/briefing",12 async (job, api) => {13 console.info(`Daily briefing for ${job.userId}`);14 return { ok: true };15 }16);
We'll also want our API that our mobile app calls, used for setting the timezone. For now, we'll keep that information as placeholders. Our REST-like endpoint will be at /api/update-briefing.ts
.
1// api/update-briefing.ts23import { sleep } from "@/util/sleep";4import type { NextApiRequest, NextApiResponse } from "next";56type Data = {7 ok: boolean;8};910const validateSession = () => sleep(80, "Verify user is valid");1112const handler = async (req: NextApiRequest, res: NextApiResponse<Data>) => {13 await validateSession();14 res.status(200).json({ ok: true });15};1617export default handler;
Mobile: Capturing Timezone
To update our briefing, we'll want to call our API with information about the current user's timezone as an IANA identifier. Mobile devices make this available via various APIs, and we can use these timezone IDs for scheduling our briefing to the correct timezone for the user.
One of the best parts about using IANA identifiers is that the tz database knows what these IDs mean, if they include daylight saving time, and their offsets (❤️ you Chatham Islands, New Zealand and your UTC +12:45
).
1import DeviceInfo from "react-native-device-info";23console.log(DeviceInfo.getTimezone()); // 'America/New_York'
No matter how you get it, we'll make a JSON request to our /api/update-briefing
endpoint with a payload that tells us about the possibly new timezone. For brevity, we'll assume you're using sessions, JWTs, or another means to secure your API endpoint.
1{2 "userId": "03952ed3-d7be-4e26-874a-15052735ee9f",3 "tz": "America/New_York"4}
Upserting Into Taskless
When our API receives a valid request, we can call enqueue()
into Taskless with the new timezone information. To make sure our first run is correct, we'll use the excellent luxon library to set the first run. Once we add the timezone, Taskless can take care of every run after that.
1import type { NextApiRequest, NextApiResponse } from "next";2import { DateTime } from "luxon";3import { sleep } from "@/util/sleep";4import briefing from "./queues/briefing";56type Data = {7 ok: boolean;8};910const validateSession = () => sleep(80, "Verify user is valid");1112const getSession = async () => {13 // these values should come from your session14 return {15 userId: "03952ed3-d7be-4e26-874a-15052735ee9f",16 };17};1819const handler = async (req: NextApiRequest, res: NextApiResponse<Data>) => {20 await validateSession();21 const { userId } = await getSession();2223 const timezone = `${req.body.timezone ?? "UTC"}`;24 const now = DateTime.now().setZone(timezone);2526 briefing.enqueue(27 `${userId}`,28 {29 userId,30 },31 {32 runAt: (now < now.set({ hour: 8, minute: 0, second: 0, millisecond: 0 })33 ? now.startOf("day").set({ hour: 8 })34 : now.startOf("day").plus({ days: 1 }).set({ hour: 8 })35 ).toISO(),36 runEvery: "P1D",37 timezone,38 }39 );4041 res.status(200).json({ ok: true });42};4344export default handler;
What's Next
Timezone aware jobs are just one of the many things you can do with Taskless. Check out the getting started doc for other guides and ideas.