You run an education business in India. Class starts at 09:00 Asia/Kolkata. You want a WhatsApp reminder to land at 08:30 every weekday morning, in the parent's language, with the kid's name and the venue. You open the WhatsApp Business Platform docs to find the schedule field, and there is no schedule field. WhatsApp Business has no scheduler. Meta Cloud API and Twilio WhatsApp both leave the clock to you. Here is the pattern teams use to ship scheduled WhatsApp reminders without rebuilding cron from scratch.
If you just want the short version: build a /whatsapp-notification endpoint in your backend, dispatch the right template based on the eventName query param, and let Crontap fire it every 30 minutes per timezone. You get per-IANA timezones (so 09:00 in Mumbai is not 09:00 in Lagos), failure alerts on 4xx and 5xx, and one dashboard across every venue and every brand you operate, for $3.25 a month billed annually.
Why WhatsApp has no scheduler
Take a look at the WhatsApp Business Platform Cloud API docs. There is a send endpoint (POST /{phone-number-id}/messages), there is a templates endpoint, there is a media endpoint, there is webhook delivery for inbound messages and status callbacks. There is no POST /messages/schedule and no cron field on the send body.
The same is true on Twilio WhatsApp. Twilio gives you a clean abstraction over Meta's Cloud API, plus number routing and template management, plus messaging_service_sid and status_callback. The send is still synchronous. You call it, Twilio relays it to Meta, Meta delivers it to the user. Nothing in that pipeline holds your message for two hours and fires it at 09:00 local.
This is by design. Both vendors treat the send as a real-time event and leave the clock to you, which is the right separation of concerns for a messaging platform but leaves you with one unsolved problem: the clock. Most teams reach for whatever scheduler their stack already has (cron on a VPS, a Cloud Scheduler job, a Zapier Schedule Zap, a Vercel cron) and quickly discover the gap between "I need a clock" and "I need a clock that knows about Asia/Kolkata, Asia/Manila, Africa/Lagos, and America/Bogota at the same time, with retries and failure alerts".
That gap is where an external scheduler earns its keep.
The send-endpoint pattern
The shape works the same on Meta Cloud API and Twilio. Build one route in your backend that takes an eventName (and optionally a timezone) query parameter, looks up which audience and template to send, and posts to the WhatsApp send endpoint.
Crontap (cron) → HTTPS POST → /whatsapp-notification?eventName=class-reminder → WhatsApp Cloud API → user
Crontap owns the clock. Your backend owns the audience query, the template selection, and the WhatsApp credentials. WhatsApp owns the delivery. Three boxes, each doing one thing.
The contract is simple. Crontap fires an HTTPS POST at the right cadence, your route reads eventName, picks the audience, sends the template messages, and returns 200. If anything goes wrong (the audience query times out, WhatsApp returns a rate limit, the template was rejected), the route returns a 4xx or 5xx and Crontap pushes that into your failure alert channel.
You get to keep your existing WhatsApp integration. You only add the clock.
Local time matters
This is the part most teams underestimate until they have customers in two countries.
A class reminder at 08:30 in Asia/Kolkata is not "08:30 in UTC plus a static offset". India does not observe daylight saving, so the offset to UTC is constant, but if you also send reminders in Europe/London or America/New_York, those offsets shift twice a year. UTC-only schedulers force you to either ship two schedules per cohort (one for summer time, one for winter) or compute the offset every fire and accept that the math will break the first DST transition you forget about.
The cleaner shape is to treat timezone as a per-schedule field. One schedule per (eventName, timezone) pair:
class-reminderat09:00 Asia/Kolkatafor the Mumbai venue.class-reminderat09:00 Africa/Lagosfor the Lagos venue.monthly-attendance-recapat19:00 last day of month, America/Bogotafor the Bogota venue.
Crontap stores the IANA name on the schedule and converts to UTC at fire time, including DST. You never write offset math.
The 24-hour messaging window rule
Before the setup, one more constraint to know about. WhatsApp distinguishes session messages (free, sent inside a 24-hour window after the user last messaged you) from template messages (paid, sent any time, must be from a pre-approved template).
If you are sending a reminder before the user has messaged you that day, you are outside the 24-hour window. That is template territory. Pick the right template category in Meta Business Manager (Utility, Marketing, Authentication; see the WhatsApp Business messaging limits docs for category rules), submit it for approval, and reference the template name in your send body.
This is not a Crontap concern. It is a WhatsApp policy concern that bites you on the first send if you skip it. The pattern below assumes templates are approved and your numbers are within the messaging-limit tier you expect.
The Crontap setup
Three steps. The first is in your backend, the second is in the Meta or Twilio dashboard, the third is in Crontap.
Step 1: Build /whatsapp-notification?eventName=... in your backend
The route is a thin dispatcher. It reads the query, picks the audience, picks the template, and sends.
export async function POST(request: Request) {
const auth = request.headers.get("authorization");
if (auth !== `Bearer ${process.env.CRON_SECRET}`) {
return new Response("Unauthorized", { status: 401 });
}
const url = new URL(request.url);
const eventName = url.searchParams.get("eventName");
const timezone = url.searchParams.get("timezone") ?? "UTC";
const audience = await audienceFor(eventName, timezone);
const template = templateFor(eventName);
const results = await Promise.allSettled(
audience.map((user) => sendWhatsAppTemplate(user, template))
);
return Response.json({
eventName,
timezone,
sent: results.filter((r) => r.status === "fulfilled").length,
failed: results.filter((r) => r.status === "rejected").length,
});
}
A few things to call out:
- The route is
POSTand bearer-protected. WhatsApp template sends cost real money on most plans; you do not want a curious crawler triggering them. - The handler returns 200 quickly. If your audience is large, push the actual sends onto a background queue and return a synthesis of the dispatch. The Crontap fire is the trigger, not the worker.
- The
eventNameparameter is the routing key. New event types are a new branch intemplateFor, not a new endpoint.
Step 2: Schedule it per eventName and per timezone
You probably have more than one venue and more than one event. Treat each (eventName, timezone) pair as a separate schedule.
For a typical multi-venue setup, that looks like:
https://yourapp.com/whatsapp-notification?eventName=class-reminder&timezone=Asia/Kolkataat every 30 minutes during business hours, Asia/Kolkata.https://yourapp.com/whatsapp-notification?eventName=class-reminder&timezone=Africa/Lagosat every 30 minutes during business hours, Africa/Lagos.https://yourapp.com/whatsapp-notification?eventName=monthly-attendance-recap&timezone=America/Bogotaat 19:00 on the last day of the month, America/Bogota.
Each schedule is a row in Crontap. Each has its own cadence, its own timezone, its own URL with the right query string baked in. The audience query inside the handler does the heavy lifting (filter the kids whose class is in this timezone within the next 30 minutes), the schedule just keeps the rhythm.
Step 3: Handle the 24-hour messaging window rule
Inside templateFor and sendWhatsAppTemplate, decide whether to send a session message or a template. The cheap heuristic: if the user has messaged you in the last 24 hours, session is free. If not, you are in template land.
A working sketch:
async function sendWhatsAppTemplate(user, template) {
const lastInbound = await lastInboundMessageAt(user.id);
const within24h = lastInbound && Date.now() - lastInbound < 24 * 60 * 60 * 1000;
if (within24h) {
return sendSessionMessage(user, template.fallbackText);
}
return sendTemplate(user, template.name, template.languageCode, [
user.firstName,
user.classTime,
user.venueName,
]);
}
You can layer on additional rules (do not send if the user opted out of the category, suppress if they already received the same template today, route to a different number for a different brand) but the WhatsApp side does not need anything special from the scheduler. The scheduler is a clock; the policy decisions live in the handler.
Fix this in 60 seconds with Crontap. Free tier available. No credit card. Schedule your first job →
Worked example: class reminders at 09:00 Asia/Kolkata
A working customer pattern from the dataset is an Indian education business running an academy with multiple branches. They ship class reminders 30 minutes before class start, every weekday morning, with the parent's language and the venue address baked into the template. Numbers are in the 22-job range across one Crontap account.
The full setup is two routes and a handful of schedules.
The route /whatsapp-notification reads eventName=class-reminder and timezone=Asia/Kolkata, queries the local database for classes starting in the next 30-to-60 minute window in that timezone, and dispatches the approved template (class_reminder_v3 in their case) for each kid registered to that class.
The Crontap schedules look like:
- URL:
https://yourapp.com/whatsapp-notification?eventName=class-reminder&timezone=Asia/Kolkata - Method:
POST - Headers:
Authorization: Bearer <CRON_SECRET> - Cadence:
*/30 6-13 * * 1-5(every 30 minutes from 06:00 to 13:30, weekdays) - Timezone:
Asia/Kolkata - Failure alert: email / webhook (Slack / Discord / Telegram), fire on 4xx/5xx
The cron expression is local-time because the schedule's timezone is Asia/Kolkata. Crontap converts to UTC at fire time. If India ever did adopt DST (it does not), Crontap would handle the transition without code changes.
The same route handles the monthly recap, the Google review nudge, and the trial-class follow-up by reading different eventName values. Each is its own schedule with its own cadence and timezone. One backend route, many schedules, no scheduler infrastructure to operate.
Failure modes and what to do about them
Three failure modes show up in production. The scheduler should help you find each one fast.
WhatsApp returned a rate limit. Meta's messaging limits are tier-based: 1K, 10K, 100K, unlimited unique recipients per 24 hours, depending on quality rating. If you blow past your tier, the send returns a 429-style error. Your route should catch that, log the affected user, and return a 5xx so Crontap pushes a failure alert. You then either request a tier upgrade or batch your sends.
Template was paused or rejected. Meta auto-reviews templates and will pause one that gets too many user reports. Your route gets back a specific error code. Treat it as a 5xx, alert immediately, and route the affected user to a session message instead.
Twilio is fine, your audience query is slow. A 30-second SQL query that the scheduler fires every 5 minutes is a pile-up waiting to happen. Wrap the audience query in a timeout, return 200 with { "skipped": true, "reason": "audience query slow" } if you exceed it, and tune the query. Crontap will only show the route as failing if you actually return a 4xx or 5xx; a 200 with a skip flag stays out of the noise.
In all three cases, the integration loop is the same: the route knows what is wrong, returns a useful status, Crontap delivers the alert. You spend time fixing the cause, not stitching the alert plumbing together.
When WhatsApp's own automation is enough
External cron is a shape, not a religion. There are cases where WhatsApp's built-in primitives are the right answer.
- One-off broadcasts. If you send one notification a month to a static list, the WhatsApp Business app's bulk-send feature does it without code.
- Inbound-driven flows. If the trigger is "user messaged you", WhatsApp's webhooks plus your handler covers it. No clock needed.
- CRM-native journeys. HubSpot, Klaviyo, and similar all integrate with WhatsApp via Meta or Twilio and bring their own scheduler for reminders inside a sequence. If you live in the CRM, use its scheduler.
External cron earns its keep when the trigger is "send this every weekday at 08:30 local across N timezones" and the work lives in your backend, not in a CRM.
FAQ
Does WhatsApp Cloud API support scheduled sending?
No. The Cloud API send endpoint is synchronous. You call it, the message goes out. The scheduler is your responsibility.
Does Twilio WhatsApp support scheduled sending?
Twilio has a scheduling feature on Programmable Messaging that lets you schedule a single send up to 7 days out. It is a per-message scheduled send, not a recurring cron. For "every weekday at 08:30 local", you still need an external scheduler firing the send at the right moment.
How do I avoid the 24-hour messaging window rule?
Use approved templates. Session messages are free inside the 24-hour window after the user last messaged you, but template messages are the right choice any time you initiate the conversation. The WhatsApp Business messaging limits docs cover the category rules in detail.
Can I run this with multiple WhatsApp numbers?
Yes. Each number has its own phone-number-id on Meta or its own messaging service on Twilio. Your route can read which number to send from based on eventName, brand, or venue. Crontap does not care; it is one HTTPS call per fire.
What about retries?
Two layers. WhatsApp itself retries delivery to the user device per its own policy. Your route should retry transient WhatsApp errors with exponential backoff before returning a 5xx. Crontap retries the HTTPS call on 5xx with backoff if you enable retries on the schedule.
Can I do this without a backend?
Technically, with a Zapier or Make scenario in the middle, yes. The trade is the per-task billing on Zapier (one task per fire) and the lower visibility on errors. For more than a handful of schedules, a thin route in your backend is the cheaper and more debuggable shape.
References
- WhatsApp Business Platform Cloud API
- WhatsApp Business messaging limits
- Twilio WhatsApp documentation
Related on Crontap
- Scheduled WhatsApp Business messages use case. The use-case-first guide for WhatsApp teams wiring up a scheduler.
- Scheduled team notifications. The same pattern for Slack, Discord, and Telegram.
- Cron syntax cheat sheet. Every cron expression you will reach for, with examples.
