The 24-hour window versus the approval queue
Every agent on Volty starts in shadow mode. It reads the conversation, drafts the reply, and waits. A human approves or rejects from the inbox, and only then does the message go out. This is the approval-gate pattern I keep coming back to: the agent proposes, staff decide in three seconds, nothing irreversible happens unilaterally.
WhatsApp has its own opinion about time. From the customer's last message, the business has a 24-hour window to reply freely. After it closes you can't just send a message any more. You're limited to pre-approved templates, and the conversational thread is effectively dead.
So there are two clocks. The approval queue runs on human time: staff are with a customer, it's 11pm, it's a Sunday. The window runs on Meta's time and doesn't care. A drafted reply sitting in the queue while the window ticks down is a collision between your safety model and your channel.
When we surveyed how helpdesk vendors handle this (Intercom's Fin, Zendesk, Gorgias, Front, the AI-native ones too), we couldn't find one that solves the collision natively. Copilot-style approval queues are everywhere. The window is treated as someone else's problem.
#The three options
Auto-approve when the window is about to close. The deadline is exactly the wrong moment to skip review. Deadline pressure is when a wrong draft does the most damage, because nobody is looking and the customer is already impatient. This option quietly converts your approval gate into a decoration.
Page staff harder. Escalating alerts as the window shrinks. Some of this is right (the approval UI should absolutely show the countdown), but paging louder doesn't make a human present at 2am, and approval gates only work when humans can decide in three seconds. Teach people that every alert is an emergency and they stop reading all of them.
Buy time without committing substance. Send something pre-approved and content-free to keep the thread alive, while the real reply keeps waiting for its human. We went with this.
#How the sweep works
Every customer-facing proposal in the approval queue carries its conversation's window countdown, so staff see "window closes in 3h" next to the draft.
Behind that, a sweep runs every ten minutes. It looks for one specific shape: a pending customer-facing proposal whose conversation window closes within the org's configured threshold, on a channel that has a fallback template configured. When it finds one, it sends the template. A zero-parameter, pre-approved utility message along the lines of "we're still looking into this, reply any time to continue". If the customer replies, the 24-hour window resets, and the substantive draft is still sitting in the queue waiting for a person.
Nothing is ever auto-approved. That's the invariant the whole design hangs off. The keep-alive can't say anything wrong because it commits to nothing; the reply that says something still goes through a human.
Since July 2025, WhatsApp prices utility templates at zero inside the window. The keep-alive is free precisely when we send it.
#The edge cases were the actual work
The happy path above is an afternoon of work. The details are why it took longer.
The live window, not the snapshot. The window expiry gets re-read from the conversation's live state at sweep time, not from whenever the proposal was created. A customer who sends another message moves the window, and a stale snapshot would fire the keep-alive at the wrong time or not at all.
Once per window. Sending the keep-alive stamps every pending proposal in that conversation. A second draft raised inside the same window doesn't re-fire the template. One keep-alive per window is a courtesy; two is spam from a business that clearly isn't reading the thread.
Fail closed. The send runs as the conversation's own agent, under the same row-level security as everything else the agent does, audited like any other send. An agent is staff, and the keep-alive is that staff member saying "give me a minute" under their own name. An agent that's lost its inbox enrolment sees zero pending proposals, so the conversation gets skipped rather than messaged by something that no longer has the right to speak.
Stale config burns once, not forever. If the template was deleted or paused on Meta's side, the send fails. The sweep stamps the conversation anyway and logs the error. Without the stamp, every ten-minute tick would retry the same broken send until the window died. One error row is a signal; four hundred of the same one is noise.
#Where this sits
The countdown and the sweep are both in main as of this week. The first customers are being invited onto Volty now, all of them starting in shadow mode, so the sweep hasn't met a real 2am yet. I'll know in a month whether the threshold defaults are right and whether customers actually reply to the keep-alive. The design assumes they sometimes won't, which is fine: the thread dies exactly as it would have, minus the part where we auto-sent something a human never read.