Decision in one sentence: never let an AI decide whether a WhatsApp message may be sent; evaluate permission from timestamped conversation state, approved-template status, purpose-specific opt-in, suppression records, and an available human escalation route, then let the model draft only within the permitted envelope.
If your team is building the underlying assistant, the business-chatbot automation guide on Agentic WhatsUp gives the broader workflow context; this article narrows the problem to the last control before a message leaves your system. That distinction matters. A model can write a polite, accurate answer and still produce an impermissible send because it used an old inbound timestamp, treated an approved template as universal permission, or missed an opt-out recorded in another channel.
The reusable asset here is a 16-case synthetic acceptance fixture and a small Python pre-dispatch gate. It is a design exercise, not a production benchmark: no customer conversations, real sends, delivery logs, or template approvals were measured. Its purpose is to make the policy boundary testable before an integration reaches customers.
What the rule actually says
The current WhatsApp Business Messaging Policy says that a business may message people who gave the business their mobile number and opted in to receive subsequent messages. It requires businesses to respect requests to block, discontinue, or otherwise opt out, including requests made outside WhatsApp. For the WhatsApp Business Platform, a business may reply without a Message Template during the 24-hour customer service window. The window opens and resets with each user message. Outside it, the policy says businesses may send messages only through approved Message Templates. The policy also permits automation during the window but requires a prompt, clear, direct path to human help.
Those are platform-policy facts. The inference for system design is that “the model thinks this is useful” is not an eligibility condition. Nor is “we sent a template once” a permanent permission token. A template is a permitted message format for a specific approved use, while consent and suppression determine whether contacting a person is appropriate at all. The recommendation is to separate these checks and fail closed if any required state is missing.
The policy is not a complete statement of every law that could apply. For example, UK ICO guidance treats direct messages on social media as electronic mail for PECR purposes and explains that marketing messages generally need consent or a relevant soft opt-in, with conditions and exceptions. That is UK guidance, not a global WhatsApp rule. A business must apply the law for its audience, purpose, location, and relationship; a WhatsApp template approval does not settle those legal questions. See the ICO’s direct marketing planning guidance and obtain jurisdiction-specific advice for consequential campaigns.
Why teams hit the 24-hour wall
The recurring operational question is simple: “The customer asked us something yesterday, we did not answer in time; can the bot continue in free text today?” A public Front community support discussion records a user asking how to contact a customer after the window expires. A Front product-education representative explains that a vendor cannot override Meta’s window and that an approved template is required. This is one support exchange, not evidence of how common the problem is. It is useful because it exposes the decision teams need to encode: a delayed response is still outside the window unless the customer sends another message or an eligible template is used.
The failure often begins earlier. A ticket is assigned to a queue, an assistant generates a response, a retry is delayed, and a scheduler wakes after the window has closed. If the send service only stores last_message_at on a ticket snapshot, a newer message may exist in a different event stream. If the AI itself chooses a template based on semantic similarity, it can select an approved template for a purpose that does not fit the message. If opt-outs are stored in a marketing CRM but not in the messaging service, the dispatch layer may not see them. These are architecture gaps, not writing-quality defects.
The send decision should therefore be recomputed immediately before dispatch, against a consistent state version. A decision made when the draft was generated is stale as soon as a customer replies, opts out, changes channel preference, or a template is paused. Treat draft creation and send authorization as separate transactions.
The decision model: permission before prose
Represent a proposed send as a structured command, not just text. At minimum it needs recipient and phone-number identity, business account and phone-number identity, proposed purpose, message type, template identifier and version if applicable, most recent inbound customer-message timestamp, consent evidence and scope, current suppression status, locale, ticket or case reference, and the source state version. Avoid putting unnecessary message content into a broad audit log; retain only what is needed to explain the decision and meet your retention requirements.
The gate can be expressed as a sequence:
- Resolve the recipient and business phone number to the correct conversation state.
- Read the latest inbound customer-message event from the authoritative event store.
- Evaluate whether the customer-service window is open at the actual send time.
- If open, check that the reply is relevant to the conversation and that suppression or channel preferences do not prohibit it.
- If closed, require a currently approved template that matches the proposed purpose and locale, plus the required opt-in and no applicable suppression.
- Require an available direct human escalation route when automation is used.
- Record allow or deny, policy version, evidence references, and reason code.
- Dispatch only if the state version has not changed; otherwise reload and evaluate again.
The time calculation is not “24 hours after the last agent message.” The policy describes a user-message window. Store timestamps in UTC, preserve the source event identifier, and compare using a single trusted clock. The exact platform boundary and delivery behavior should be verified against the live product documentation and your provider integration; do not infer success from a local API request returning accepted.
A compact authorization matrix
| Latest known state at dispatch | Proposed send | Gate result | Why |
|---|---|---|---|
| Customer messaged less than 24 hours ago | Relevant free-form support reply | Allow only if other checks pass | Platform policy allows a non-template reply within the open window |
| Customer messaged less than 24 hours ago | Unrelated marketing offer | Deny or route to a separate compliant campaign decision | An open service window is not blanket consent for every purpose |
| No customer message in the last 24 hours | Approved, purpose-matched template | Allow only if opt-in and suppression checks pass | Template status is necessary for this platform path, not sufficient by itself |
| No customer message in the last 24 hours | Free-form AI message | Deny | Outside the window, the policy requires an approved template |
| Any window state | Recipient opted out | Deny and suppress | The policy requires honoring opt-outs, including those made outside WhatsApp |
| Any window state | Automation with no visible escalation route | Deny or pause automation | Policy requires a prompt, clear, direct human path |
| Timestamp absent or stale | Any non-emergency outbound message | Hold and resolve state | Unknown is not equivalent to an open window |
| Template rejected, paused, or wrong locale | Template message | Deny | A previous approval does not establish current approval for this send |
This is an operator matrix, not a substitute for Meta’s complete policy or legal review. It intentionally separates the platform window, contact permission, template qualification, and automation handoff. If your product has a business-initiated conversation path with additional eligibility rules, encode those explicitly rather than adding exceptions to an “open window” boolean.
The 16-case synthetic acceptance fixture
The table below is the article’s original reusable asset. The 16 rows are synthetic test cases drafted on 5 November 2026. The sample size is 16 scenarios; it is not a sample of customer behavior, and no rates, accuracy, or operational outcomes are claimed. The cases deliberately test state changes and boundary conditions, rather than estimating how often each failure occurs. Excluded: real WhatsApp API calls, actual template approval records, jurisdiction-specific legal analysis, media messages, pricing, and account-quality effects.
| ID | Inbound state at send | Permission / template state | Automation / suppression | Expected gate |
|---|---|---|---|---|
| 01 | 23h 59m ago | Support reply, no template | No suppression; human route available | Allow if latest event remains current and reply is relevant |
| 02 | Exactly 24h ago | Free-form support reply | No suppression | Deny unless platform confirms the window is still open; use conservative boundary handling |
| 03 | 24h 1m ago | Free-form support reply | No suppression | Deny; require an eligible approved template path |
| 04 | 30h ago | Approved support template, correct locale | Valid purpose opt-in; no suppression | Allow only after template and recipient checks pass |
| 05 | 30h ago | Approved promotion template | Only service-request context recorded | Deny; the stored permission scope does not match the marketing purpose |
| 06 | 30h ago | Template was approved historically | Current template status unknown | Hold; retrieve current status before sending |
| 07 | 30h ago | Correct template and purpose | Recipient opted out by email | Deny and propagate suppression to the send gate |
| 08 | 30h ago | Correct template and opt-in | Recipient asked to stop in WhatsApp | Deny; acknowledge only if the applicable channel policy permits and suppress |
| 09 | 2h ago | Relevant free-form support draft | No opt-out; human route available | Allow if latest state and content pass checks |
| 10 | 2h ago | Free-form draft | Latest customer message exists in a second event partition | Reload canonical event stream; do not rely on stale ticket snapshot |
| 11 | 28h ago | Template message | Customer sent a new message seconds before dispatch | Recompute; open-window path may now permit relevant free-form reply |
| 12 | 28h ago | Template message | New customer message arrives after gate but before dispatch | State-version mismatch; reload and reauthorize |
| 13 | 4h ago | AI proposes promotional upsell | Consent only covers order updates | Deny or separate for purpose review; window does not broaden consent |
| 14 | 26h ago | Approved template in another language | Recipient locale is unknown | Hold; do not assume the available template is suitable |
| 15 | 1h ago | Relevant support reply | No direct human escalation route configured | Pause automated response until a clear direct path exists |
| 16 | Unknown timestamp | Any generated response | Event ingestion lag or clock anomaly | Hold, reconcile event history, then evaluate again |
The “exactly 24 hours” row is deliberately conservative. The policy says a window lasts 24 hours and resets with each user message, but systems can differ in timestamp precision, rounding, queue delay, and the moment they validate eligibility. A local implementation should test the provider’s boundary behavior and keep a safety margin; it must not claim that one millisecond edge behavior is guaranteed by this fixture.
Executable pre-dispatch gate
This Python example is a policy skeleton. It does not call WhatsApp, validate legal consent, or know whether a Meta template is actually approved. The caller must supply current, authoritative facts. In production, use timezone-aware timestamps, trusted storage, atomic state versions, and a controlled template registry. The code deliberately returns a reason code and fails closed for missing or contradictory input.
from dataclasses import dataclass
from datetime import datetime, timedelta, timezone
from enum import Enum
class Decision(str, Enum):
ALLOW_FREEFORM = "allow_freeform"
ALLOW_TEMPLATE = "allow_template"
DENY = "deny"
HOLD = "hold_for_state_refresh"
@dataclass(frozen=True)
class SendRequest:
now: datetime
latest_user_message_at: datetime | None
purpose: str
consent_purposes: frozenset[str]
opted_out: bool
template_id: str | None
template_approved: bool
template_purpose: str | None
template_locale: str | None
recipient_locale: str | None
human_escalation_available: bool
event_version: str | None
current_event_version: str | None
def authorize(req: SendRequest) -> tuple[Decision, str]:
if req.now.tzinfo is None:
return Decision.HOLD, "send_time_not_timezone_aware"
if req.opted_out:
return Decision.DENY, "recipient_suppressed"
if req.purpose not in req.consent_purposes:
return Decision.DENY, "purpose_not_in_permission_scope"
if not req.human_escalation_available:
return Decision.DENY, "automation_has_no_direct_human_escalation"
if not req.event_version or req.event_version != req.current_event_version:
return Decision.HOLD, "conversation_state_changed_or_unverified"
inbound = req.latest_user_message_at
if inbound is not None:
if inbound.tzinfo is None:
return Decision.HOLD, "inbound_timestamp_not_timezone_aware"
age = req.now.astimezone(timezone.utc) - inbound.astimezone(timezone.utc)
# A safety margin avoids treating a queued send exactly at the edge as open.
if timedelta(0) <= age < timedelta(hours=24) - timedelta(seconds=30):
return Decision.ALLOW_FREEFORM, "open_window_relevant_reply"
if not req.template_id:
return Decision.DENY, "closed_window_requires_approved_template"
if not req.template_approved:
return Decision.DENY, "template_not_currently_approved"
if req.template_purpose != req.purpose:
return Decision.DENY, "template_purpose_mismatch"
if not req.template_locale or req.template_locale != req.recipient_locale:
return Decision.HOLD, "template_locale_unverified"
return Decision.ALLOW_TEMPLATE, "closed_window_template_path_passed"
if __name__ == "__main__":
now = datetime.now(timezone.utc)
req = SendRequest(
now=now,
latest_user_message_at=now - timedelta(hours=23, minutes=50),
purpose="order_support",
consent_purposes=frozenset({"order_support"}),
opted_out=False,
template_id=None,
template_approved=False,
template_purpose=None,
template_locale=None,
recipient_locale="en_GB",
human_escalation_available=True,
event_version="evt-42",
current_event_version="evt-42",
)
print(authorize(req))
The 30-second margin is an engineering recommendation in this example, not a Meta-defined allowance. Choose it from your queue and clock behavior, then measure the actual tail latency. A stricter system might use a larger buffer; a system with an authoritative dispatch-time eligibility check may choose another approach. Whatever the implementation, do not silently turn a held request into a send. Record the reason and make retries pass through the gate again.
State and transaction design
The most important implementation choice is the source of truth for latest_user_message_at. Use the event that represents a customer-originated message accepted by the platform, not an agent reply, outbound template, webhook retry, or ticket update. Deduplicate events by provider event ID. Persist both the event timestamp and ingestion timestamp: the former describes when the message happened, the latter helps diagnose late arrival. If your provider’s timestamp semantics are unclear, document the uncertainty and validate with test events before relying on it.
The check and send are vulnerable to a time-of-check/time-of-use race. Suppose the gate sees no opt-out, returns allow, then an opt-out event arrives before the outbound request is committed. A naïve service still sends. Reduce this risk by serializing sends and suppression updates per recipient/business-number pair, or by checking a monotonic state version at the dispatch boundary. If the version changed, cancel the send and evaluate again. If the provider call itself cannot be atomically coordinated with your database, write an outbox record containing the authorization version, recheck immediately before network dispatch, and make the outbox worker reject stale records.
Retries deserve their own policy. A network timeout does not tell you whether the platform accepted the first attempt. Use provider message IDs and idempotency mechanisms where available; do not generate a second message just because your client did not receive a response. A retry after a delay may cross the 24-hour boundary, so reauthorize at retry time. A template approved when the first attempt began could have changed status before retry. Store the attempted payload classification and permission evidence, then fetch current eligibility before each external attempt.
Consent, purpose, and suppression are separate dimensions
A person’s phone number and a service conversation do not automatically imply permission for every future message. Meta’s policy asks businesses to have the number and opt-in for subsequent messages, and to honor opt-outs. The scope of consent should therefore be explicit: purpose, channel, collection point, wording/version, timestamp, locale, and evidence reference. A vague boolean called whatsapp_opt_in becomes difficult to interpret when the business later wants to send promotions, delivery updates, appointment reminders, and satisfaction surveys.
For UK direct marketing, ICO guidance describes electronic mail to include text, picture, video, voicemail, and direct messaging on social media. It explains the general consent requirement for individual subscribers and conditions around the soft opt-in. This does not mean every WhatsApp support reply is direct marketing, or that UK guidance governs all countries. It means product teams should classify purpose before choosing a lawful basis and should not use a customer-service window as a shortcut around marketing controls. The ICO page should be read with the applicable jurisdiction and current legislation.
Suppression should be a high-priority deny signal with defined precedence. If a customer says “stop” in WhatsApp, a marketing list removal in email, a privacy preference center, or an agent records a phone request, the send gate needs a consistent policy for whether that request suppresses one purpose or all business messaging. Meta says to honor opt-out requests even when received outside WhatsApp; local laws may impose additional requirements. Never let the language model reinterpret a suppression event as “they probably only meant this campaign.” A human may resolve ambiguity through a permitted channel; the automation should not guess and send.
Template governance without template laundering
Templates are often treated as content artifacts: someone gets one approved, then teams reuse it for many flows. For a robust send gate, a template record should contain immutable ID, current platform status, approved language, locale, category or declared purpose, variable schema, owning workflow, approval timestamp, last verification timestamp, and the policy or account context in which it was approved. The sending workflow should reference a specific version. If the message body can be materially changed by AI after approval, you may no longer be sending the approved template in the intended form.
Do not let the model author a free-form paragraph and wrap it in a template-shaped shell. Bind variables to typed fields with maximum lengths and allowed sources. If a variable is populated from a model, validate it against the template’s intended meaning and reject unbounded generated content. Avoid putting sensitive information into a template variable unless that purpose and data use have been reviewed. Keep a human review route for novel or ambiguous template situations.
An approved template also does not prove the recipient opted into that purpose. Template approval is platform content eligibility; contact permission is recipient and jurisdiction eligibility. The gate must require both where applicable. Conversely, a valid opt-in does not permit a non-template message outside the platform’s service window. Treat these as conjunctive checks, not substitutes.
Human escalation is a product path, not a footer
The WhatsApp policy allows automation in the 24-hour window only when a prompt, clear, direct human escalation path is available. A hidden email address in a terms page is not a useful operational handoff. The user should have a straightforward way to reach a person from the automated interaction, and the service should route the conversation with enough context for the human to act. The policy lists examples such as in-chat transfer, phone, email, web support, a store, or a support form.
For an engineering acceptance test, define what “available” means. Is a human queue staffed now? Is there a callback or ticket path outside staffed hours? Does pressing “talk to a person” stop the model from continuing to send? Does the human see the conversation and the reason the automation escalated? Is there a timeout that tells the customer what will happen next? These are design questions, not extra words in Meta’s policy, so label them as recommendations.
The OECD AI Principles call for human agency and oversight, transparency, and accountability across AI system lifecycles. They are high-level recommendations, not a WhatsApp compliance checklist. They support a governance inference: an escalation path should be testable and traceable, rather than existing only in product copy. BCG’s discussion of generative AI in customer service describes human-in-the-loop patterns and recommends starting with bounded use cases; BCG is a consulting perspective, not an independent controlled evaluation. Use both as design context, not proof that one architecture will achieve a performance result.
Observability that answers “why did it send?”
Create a decision log that answers why a particular send was allowed or denied without retaining a shadow copy of every conversation indefinitely. A useful record includes a pseudonymous recipient key, business phone number key, request ID, purpose, template ID and version, latest inbound event reference and timestamp, opt-in evidence reference, suppression lookup result, escalation configuration version, state version, decision, reason code, policy reference version, and dispatch outcome. Store content only when your privacy and retention analysis justifies it.
Useful reason codes are specific enough to guide operations: window_open, template_required, template_not_approved, purpose_mismatch, recipient_suppressed, consent_evidence_missing, timestamp_missing, event_version_changed, escalation_unavailable, and provider_outcome_unknown. Avoid one generic blocked code; it makes incident review and remediation slow. A request held for a missing event should go to state reconciliation, while a suppression denial should not be placed in a retry queue.
Monitor the shape of decisions, not only delivery volume. Track counts of allows, denies, holds, stale-state rechecks, unknown provider outcomes, and human escalations by workflow. These are operational counters, not proof of compliance. A low deny count could mean a well-designed workflow or a broken suppression integration. Review a sample of records against source events and configuration changes. Do not present a synthetic test suite or internal count as an industry benchmark.
A Mermaid flow for the final gate
flowchart TD
A[AI or agent proposes message] --> B[Resolve recipient and business number]
B --> C[Load latest inbound event, consent, suppression, template and escalation state]
C --> D{State complete and current?}
D -- No --> H[Hold and reconcile]
D -- Yes --> E{Suppressed or purpose not permitted?}
E -- Yes --> X[Deny; record reason]
E -- No --> F{Customer-service window open?}
F -- Yes --> G{Relevant reply and human path available?}
G -- No --> X
G -- Yes --> I[Authorize free-form reply]
F -- No --> J{Approved, purpose-matched template and locale?}
J -- No --> X
J -- Yes --> K[Authorize template send]
I --> L[Check state version at dispatch]
K --> L
L --> M{Version unchanged?}
M -- No --> H
M -- Yes --> N[Dispatch and record provider outcome]
The diagram makes one point explicit: the language model proposes; a deterministic gate authorizes. For low-risk support, the model may draft a relevant answer during an open window, but it cannot overrule a suppression record or invent a fresh permission. For an out-of-window use case, an approved template is a separate path, and only after the contact and purpose checks pass.
Failure modes and what to do
The wrong timestamp wins. An agent message updates the ticket’s updated_at, so the system mistakes it for a customer message and opens the window. Store the provider’s customer-message event separately and test event types. A webhook arrives late. The scheduler sees a closed window, but a valid customer message is in transit. Hold briefly or refresh from the authoritative provider state where available; never assume missing means open. A retry crosses the boundary. Recheck at each attempt and do not reuse the initial authorization.
A template was approved once. It was later paused, rejected after edits, or changed in another locale. Resolve current status and exact version at dispatch. A template is mistaken for consent. Add a separate purpose-specific permission check. Consent is stale or unscoped. Preserve evidence of what the person agreed to, when, where, and for which purpose; if the scope cannot be demonstrated, hold or deny according to policy. An opt-out exists elsewhere. Make suppression lookup cross-channel and fail closed when the suppression service is unavailable for a marketing send.
The assistant keeps talking after handoff. Escalation is displayed but does not disable further automation. Make handoff a state transition that stops the bot’s outbox worker for that conversation until a defined release condition. The human route is unavailable. Pause autonomous replies or offer a direct alternative that satisfies the product’s intended handoff. The provider accepted the request but delivery is unknown. Mark outcome unknown, reconcile using provider IDs, and avoid a duplicate retry. The AI generates a new template. Never treat a generated message as approved simply because it resembles approved copy.
The business mixes service and promotion. A support question about an order becomes an upsell because the model sees a sales opportunity. Keep purpose in the request schema and test it independently from message text. A customer reply resets the window but not the consent model. Re-evaluate the platform window while preserving the purpose-specific permission boundary. A policy page changes. Pin a review cadence and store the policy version or retrieval date used for internal guidance; do not freeze external rules into code forever.
How to run the 16-case test
Convert each row into an automated fixture with a fake clock, synthetic recipient, event sequence, template registry, consent ledger, suppression ledger, escalation state, and expected reason code. Run tests at exact boundaries and with out-of-order events. Make one test mutate the conversation version between authorization and dispatch. Make another toggle suppression after draft creation. Include a retry path that starts within the window and fires after it closes. Assert both the decision and the absence of a dispatch call on deny/hold.
For reproducibility, record fixture revision, test code revision, policy page retrieval date, timezone assumptions, and expected reason codes. The 16-case set is a starting checklist, not comprehensive coverage. Extend it for your account’s template lifecycle, supported locales, provider semantics, regulatory markets, and multi-number architecture. When an official policy changes, update the source note, review the decision table, and rerun the fixture. Do not update a threshold just to make tests green; establish why the requirement changed.
An example test assertion might be:
decision, reason = authorize(request_with_opt_out)
assert decision is Decision.DENY
assert reason == "recipient_suppressed"
assert dispatch_calls == []
Also test the gate itself for missing fields and malformed data. A naïve timestamp parser can turn None into “now,” a timezone-naive value can be interpreted in the server’s local timezone, and a stale template cache can outlive an approval change. Keep parsers strict, inject the clock in tests, normalize to UTC, and reject unknown enum values. These are ordinary software correctness controls that matter because a successful API call has an external recipient.
What the external evidence can and cannot establish
Meta’s policy is the source for the platform boundary in this article. The exact live wording should be checked before implementation and after policy changes. The OECD AI Principles provide broad governance concepts around oversight, transparency, and accountability; they do not specify WhatsApp message eligibility. The ICO direct-marketing guidance illustrates that direct-message marketing can fall within electronic-marketing rules in the UK, but it cannot determine the legal result for every country or every service message.
UK ICO guidance is not a global rule, and the applicable legal result depends on market, subscriber type, purpose, and facts. Its value here is to show why the product must classify service and marketing purposes rather than treating the platform window as a legal permission. A service response about an existing order and a promotion embedded in the same response may require different analysis; if the message includes promotional content, have the responsible privacy or legal owner review the classification.
Proof pack
| Claim used in the decision | Source | Verification boundary |
|---|---|---|
| The customer-service window opens and resets with each user message; free-form replies are permitted within it and approved templates are required outside it | WhatsApp Business Messaging Policy | Platform policy; wording and account-specific documentation can change, so re-check before release |
| Automation during the window requires prompt, clear, direct escalation paths | WhatsApp Business Messaging Policy | Platform policy describes available examples; it does not define this article’s staffing or timeout recommendations |
| UK direct marketing by electronic mail includes social-media direct messages; subscriber and soft-opt-in conditions may apply | ICO planning guidance | UK regulator guidance, not a global rule; the ICO page was readable through indexed source content but direct automated retrieval was blocked during this research pass |
| Human agency and oversight, transparency, robustness, and accountability are broad trustworthy-AI principles | OECD AI Principles | Intergovernmental principles updated in 2024; high-level guidance, not WhatsApp-specific legal requirements |
| Bounded use cases and human-in-loop patterns are one consulting perspective for customer-service AI | BCG customer-service analysis | Consulting analysis; not an independent controlled trial or a performance guarantee |
| Teams ask how to contact customers after the 24-hour limit | Front community discussion | One public support exchange; anecdotal only and not evidence of prevalence |
The BCG article is useful as a consulting viewpoint on bounded customer-service automation and human involvement, but it is neither a neutral benchmark nor a measured evaluation of this gate. The Front discussion and other public help/forum material show that users ask how to continue after the window closes; they do not establish prevalence, and forum advice can be incomplete or stale. Video search was performed for current demonstrations and implementation explanations, but a caption transcript could not be obtained for a potentially relevant video during this research pass. No video is used as evidence here. The gap is explicit so that lack of transcript access is not mistaken for evidence that no useful video exists.
Decision checklist before you activate sends
- Can you identify the latest customer-originated event for the exact recipient and business number?
- Is its timestamp timezone-aware, deduplicated, and rechecked at dispatch and retry?
- Does the window calculation use the customer’s latest message rather than an agent or outbound event?
- Is the proposed purpose explicit and supported by permission evidence for that purpose?
- Does suppression include opt-outs recorded outside WhatsApp, and does lookup failure stop sends?
- When the window is closed, is the selected template currently approved, versioned, locale-correct, and purpose-matched?
- Can the model alter only bounded variables rather than rewrite approved template content?
- Does automation expose a prompt, clear, direct human escalation route, and does that route actually stop or pause the bot?
- Do state-version changes between gate and dispatch cancel and re-evaluate the send?
- Can operations distinguish denied, held, accepted, delivered, and unknown provider outcomes?
- Do tests include exact boundary, delayed events, cross-channel opt-outs, stale template status, retries, locale mismatch, and escalation failure?
- Are policy, legal, and provider behavior treated as distinct sources of constraints?
If any answer is “unknown,” do not make the AI more autonomous to compensate. Hold the outbound action, resolve the missing state, and keep the model in a drafting role until the deterministic permission gate can explain its decision.
Practical rollout sequence
Start by mapping every outbound path, including scheduled reminders, human-agent tools, retries, batch jobs, and AI-generated follow-ups. A policy gate placed only in the chatbot endpoint leaves alternate send paths unprotected. Inventory each business phone number, its event source, template registry, consent records, suppression systems, and human escalation destination. Identify which service owns each fact and what happens when it is unavailable.
Next, introduce a shared authorization service or library at the dispatch boundary. Do not rely on each product team to reproduce the window formula. The service should accept a typed request, retrieve current state, return a decision plus reason code, and provide a short-lived authorization token tied to an event version. The sender must reject an expired token or one bound to a different recipient, number, purpose, template, or content classification. Keep permissions narrow: a token for an order-status template should not authorize a promotional send.
Roll out in observation mode only if this does not accidentally send. Observation mode should calculate and log what the gate would decide while existing human-controlled dispatch remains in place. Compare decisions with source records and investigate disagreements. Do not collect full message bodies by default just to make debugging convenient. Once the state mappings and reason codes are stable, enforce the gate for one bounded workflow and monitor held and denied cases with a named operational owner.
The model layer should receive an explicit allowed action description, not raw authority to call a send tool. In an open window it may propose a relevant support reply, but the authorization service still decides. Outside the window, it may draft a permitted template variable or suggest a handoff; it must not create or choose an unapproved free-form workaround. If your assistant has tool use, the send tool itself must enforce authorization. Prompt instructions alone are not access control.
Reconciliation when sources disagree
Several systems may show different conversation times: Meta webhook time, provider receipt time, CRM update time, agent activity time, and local database write time. Do not choose whichever timestamp makes more messages eligible. Define event semantics. For the window rule, the important input is the customer message event recognized by the platform. Store the raw provider timestamp, parsed UTC value, provider event ID, and ingestion timestamp, and make the derived window state traceable to that event. If two events arrive out of order, recompute from the latest valid customer-message event under the provider’s ordering semantics.
Similarly, a CRM may mark consent true while the messaging provider or preference center contains an opt-out. Define the precedence rule before launch. A defensible approach for outbound marketing is to let a valid suppression dominate until explicitly cleared through a verified process, while the scope of the suppression is resolved. That is a recommendation; the exact scope and legal duties depend on the request and jurisdiction. Do not silently erase a suppression because a later import contains a positive consent field.
For template status, cached approval can disagree with the current provider state. The system should have a freshness policy and a failure response. If current status cannot be obtained, hold a campaign send rather than assuming approval persists. The business may choose a cached status for a narrowly defined period if the provider guarantees its semantics and the risk is assessed, but that is a documented engineering assumption, not a fact supplied by the synthetic fixture.
Limits of the sample and recommendation
The asset uses 16 manually designed cases, prepared on 5 November 2026 from the policy boundary and common integration failure patterns. It includes no production data, no measured acceptance rate, no API test, and no statistical inference. There is no random sampling frame and no denominator for “how often” a WhatsApp business encounters a closed-window send. The expected outcomes are design recommendations grounded in the stated policy, not observed system behavior. The test set excludes legal analysis outside the UK example, nuanced template categories, policy enforcement outcomes, and every provider-specific webhook edge case.
The recommendation is strongest where it mirrors explicit platform-policy language: customer-service window, approved templates outside the window, opt-out handling, and human escalation for automation. It is an engineering inference to use a separate deterministic gate, immutable event references, reason codes, and state-version checks. The 30-second buffer in the code is an illustrative safety margin, not a policy requirement; replace it with a documented choice informed by your queue latency and provider validation behavior. A strict API integration may expose a different boundary mechanism, so test against current official documentation and a controlled account.
There is also a practical trade-off. A fail-closed gate can delay a legitimate customer reply when event ingestion or consent data is temporarily unavailable. That is safer than sending without proof, but it still harms service if reconciliation is slow. Pair the gate with operational alerts, fast data repair, a human support route, and clear customer messaging. Do not optimize the deny rate by weakening the permission model. Optimize the time to resolve a hold and the accuracy of the source data.
The final design rule
An AI reply pipeline should answer two different questions in order: “What response would help?” and “May this response be sent to this person through this channel now?” A language model can help with the first. Permission, template eligibility, suppression, purpose, current time, and handoff availability answer the second. Keep those decisions independently testable.
Use the 16 synthetic cases as a starting fixture, add your own provider and legal-market cases, and keep the evidence chain attached to each decision. When the latest user message is unknown, the template state is stale, consent does not match the purpose, an opt-out is present, the state changed before dispatch, or a human route is unavailable, stop and resolve the condition. A polished answer is never a reason to bypass the send gate.
Top comments (0)