Dentolize · WhatsApp Paid Messages Walkthrough
On this pageTermsData model additionsRedis keys

Glossary & Data Model

Terms

Service window — The pre-existing 24-hour window that opens each time a contact sends a message; the clinic may reply with a free-form message (not a template) during it. Tracked implicitly via OnlineConversation.lastMessage.

Free window — New in this PR. A window during which a reply costs nothing, tracked explicitly via OnlineConversation.freeUntil. Opened for 24 hours the moment an ad-referred contact writes in, and extended to 72 hours if the clinic answers within that first day.

Ad referral / click-to-WhatsApp ad — A contact who reached the clinic's WhatsApp by clicking a Meta ad configured to open a WhatsApp chat. Detected by the presence of referral.ctwa_clid on the inbound webhook payload. Tracked via OnlineConversation.adReferralAt.

Message category — What WhatsApp bills a given outgoing message as: SERVICE (free-form reply), UTILITY (transactional template, e.g. an appointment reminder), MARKETING (promotional template), or AUTHENTICATION (OTP/verification template). Stored per-message on ConversationMessage.category.

Free / Paid — Whether a specific outgoing message happened to be sent inside an active free window (ConversationMessage.free = true) or not (false). null means the message predates this feature and was never classified.

Unknown — The UI's label for messages with free: null (cost report) or category: null (category breakdown) — always because the message was sent before this feature shipped, never a live/current state.

Daily paid message limit — A per-company, per-sender-kind (bot or user) cap on how many paid replies may be sent in a rolling ~24-hour window, configured via WAConfig.maxBotPaidMessages / maxUserPaidMessages.

VIEW_WA / VIEW_CREATED_WA — The two existing permissions that gate seeing WhatsApp conversations at all; also gate the new cost report and analytics (no new, narrower permission was introduced for cost data).

Data model additions

OnlineConversation

FieldTypeMeaning
adReferralAtDateTime?When an ad-click-originated message arrived. Cleared to null once the free window it can open actually opens.
freeUntilDateTime?Timestamp until which replies to this contact are free.

ConversationMessage

FieldTypeMeaning
freeBoolean?Whether this specific outgoing message was free when sent. null = predates this feature.
categoryWhatsAppMessageCategory?What this message is billed as. null = predates this feature.

WAConfig

FieldTypeMeaning
maxBotPaidMessagesInt?Daily cap on paid replies sent by the automated bot. null = no cap.
maxUserPaidMessagesInt?Daily cap on paid replies sent by clinic staff. null = no cap.

New enum: WhatsAppMessageCategory

SERVICE | UTILITY | MARKETING | AUTHENTICATION

Defined in packages/prisma/schema.prisma and mirrored in packages/server/src/enums.graphql. Distinct from the pre-existing TemplateCategory enum (UTILITY | MARKETING), which classifies a template, not an individual sent message — WhatsAppMessageCategory is the superset used for the per-message cost record, adding SERVICE (any free-form reply) and AUTHENTICATION.

New GraphQL queries

conversationMessagesInfo(rangeDate: [DateTime!]!, filters: Filter, conversation: ID): [TypeSum!]!
conversationMessagesReport(orderBy: String!, skip: Int!, take: Int!, rangeDate: [DateTime!]!, filters: Filter): [ConversationMessage]!

TypeSum (an existing shared aggregation type) gained a free: Boolean field alongside its pre-existing type: String.

New GraphQL mutation arguments

updateWAConfig(..., maxBotPaidMessages: Int, maxUserPaidMessages: Int).

Redis keys

whatsapp:phoneId:paid:{bot|user}:{companyId} — a per-company, per-sender-kind counter of paid messages sent in the current rolling window, with an 86400-second (24h) TTL that restarts from the first paid message of each window. See Daily Spending Limits.