This article is an English translation of the original Japanese article.
I use Cloudflare Workers Cron Triggers to send push notifications on the morning of practice days. The challenge was figuring out how to verify the entire workflow locally without waiting for the scheduled cron time.
In my implementation, I separate the Worker's scheduled
event from the HTTP route that handles the notification logic.
The Worker receives the cron event and calls an internal route handler.
export default {
async fetch(request: Request, env: Env, ctx: ExecutionContext) {
return handler.fetch(request, env, ctx);
},
async scheduled(_controller: ScheduledController, env: Env) {
const request = new Request(
"https://internal/api/cron/morning-reminder",
{
method: "POST",
headers: { authorization: `Bearer ${env.CRON_SECRET}` },
},
);
await handler.fetch(request, env);
},
};
The key is not embedding date logic, user retrieval, and Expo Push API calls directly inside scheduled
. Delegating to an HTTP route means the same logic can be triggered over HTTP, making local verification much easier.
Locally, start Wrangler with the following option:
npx wrangler dev --test-scheduled
Append /__scheduled
to the URL Wrangler displays.
curl "http://localhost:8787/__scheduled?cron=0+22+*+*+*"
Use the same cron expression in the cron
query parameter. Cloudflare interprets cron in UTC, so don't write expressions based solely on JST.
When you only want to verify recipient filtering, you can call the internal route directly.
curl -X POST http://localhost:8787/api/cron/morning-reminder \
-H "Authorization: Bearer $CRON_SECRET"
This route validates CRON_SECRET
. Rather than removing authentication for local development, I store a development value in .dev.vars
. Production secrets never go into source control.
If your local database has no schedules, participants, or push tokens for today, the cron will run but send zero notifications. When testing, verify in order:
scheduled
reach the route?The hard part of cron testing is not how to trigger it, but coordinating test data with the current time. Splitting the core logic into an HTTP route lets you verify event wiring and notification logic separately.