WhatsApp calls you
You don't poll for messages. Each one arrives as an HTTP POST to your server, and you reply by calling WhatsApp's API in return. The chat is just two webhooks and an API.
The AI part takes a second: one model call turns the sentence into a task and a time. Everything after that is a database row, a scheduler and the WhatsApp Business Platform's rules about when a business may message you.
One user in Kathmandu (UTC+5:45). The clock skips straight to each reminder, and the model's replies are fixed per message so every run is repeatable.
A WhatsApp message reaches your server as a webhook. One model call turns the sentence into a task and a time, and that becomes a row in a table. Hours later, plain code sends it: no AI at the moment it matters.
Ready. It's Mon 3 Nov 18:30 in Kathmandu. Send the bot a message.
| # | task | due | repeat | sent / failed |
|---|
You don't poll for messages. Each one arrives as an HTTP POST to your server, and you reply by calling WhatsApp's API in return. The chat is just two webhooks and an API.
The model's only job is to turn “tomorrow at 9” into a task and an exact time, as JSON. It can only do that relative to the current time and timezone it's given.
Once saved, a reminder is a row with a due time. A scheduler sends it when it's due, every day if it repeats. No AI at send time, so nothing to misread.
Live: the chat, and the window
Window: closed (the user hasn't written yet)
(no messages yet)Live: the model call
$ prompt: Now: 2026-11-03T18:30 Asia/Kathmandu.
(no model call yet)Live: the latest API call
(nothing sent yet)A business number on the WhatsApp Business Platform gets messages by webhook: Meta POSTs JSON to a URL you register, signed with your app secret in an X-Hub-Signature-256 header. Check the signature, answer 200 quickly, and do the slow work, like calling a model, afterwards. Webhooks that don't get a quick 200 are sent again.
The prompt has to carry the context the model can't know: the current date, time and the user's timezone. Ask for a schema, and allow at: null so “later” produces a question instead of a guess.
Then there's the rule that shapes the whole design. Within 24 hours of a user's last message you may reply freely. After that, a business can only send a template that Meta has reviewed in advance, like reminder_due with the task filled in. A reminder bot spends most of its life outside that window.
Servers run in UTC. Without the user's zone in the prompt, the model does what it's told and every reminder lands hours off. Repeating the time back in the confirmation is how users catch it.
A reminder outside the 24-hour window, sent as plain text, fails with error 131047. It doesn't fail at the API call: it's reported later by a status webhook that many bots never read.
A model asked for a time will usually produce one. If the schema has no way to say “unknown”, “later” quietly becomes 5pm. Make uncertainty a valid answer.
A user in Kathmandu asks for a reminder “tomorrow at 9am”. The prompt tells the model the current time in UTC. When does the reminder arrive?
A daily 8pm reminder is set at 18:30 on Monday, and the user doesn't write again. Without a message template, what happens on Tuesday at 8pm?
Businesses need the user's permission to message them, and should honour “stop” at once. A real bot also handles replies like “snooze” or “done”, which is a second job for the model.
Templates are submitted to Meta and approved or rejected by category, such as utility or marketing. Template messages are charged, at rates that vary by country and category.
A retried webhook would create the same reminder twice. Real bots record each message's id and skip ones they've seen, the idempotency check the AI automations topic shows.
Here the clock jumps to each reminder. A real scheduler runs every minute or uses a delayed-job queue, and has to cope with being down when a reminder was due: send it late, or skip it and say so.