Tous les articles

Messaging & the anti-ban policy

The caps, recipient classes and error codes evaluateSendPolicy enforces on every WhatsApp send.

Pas encore disponible en français

Cet article n'a pas été traduit, il est donc affiché en anglais.

Messaging & the anti-ban policy

A shop's WhatsApp is a real phone number on WhatsApp Web; WhatsApp bans numbers that behave like spam. evaluateSendPolicy (src/libs/services/WhatsAppSendPolicy.ts) refuses on the tenant's behalf before anything is sent, on every route that can put a message on the wire — not only the API.

Recipient classes

Every 1:1 send is classified before the caps below are checked:

| Class | Meaning |
|---|---|
| known | matches a CustomerModel row, or the number has written to the shop before |
| declared | not known, but the tenant has declared it via POST …/contacts/consented and the declaration is still active (not revoked, not expired) |
| cold | neither of the above — a genuine stranger |
| group | the recipient is a WhatsApp group (@g.us), never known/declared/cold |

Caps (1:1)

| Rule | Default | Code |
|---|---|---|
| Content | ≤ 4096 chars, ≤ 1 media, text or media required | MESSAGE_TOO_LONG / TOO_MANY_MEDIA / EMPTY_MESSAGE |
| Opt-out | recipient's last inbound was STOP/ARRÊT/DÉSABONNER/UNSUBSCRIBE | RECIPIENT_OPTED_OUT |
| Warm-up ramp (days since the WhatsApp session connected) | d0–2: 20/day · d3–6: 50 · d7–13: 100 · d14+: 250 | DAILY_CAP_REACHED |
| Hourly | max(10, daily ÷ 6) | HOURLY_CAP_REACHED |
| Cold recipients | 10% of the daily cap, minimum 5 | COLD_RECIPIENT_CAP_REACHED |
| Declared recipients | its own, tenant-configurable budget — separate from cold's, does not spill over into it | DECLARED_RECIPIENT_CAP_REACHED |
| Per recipient | ≤ 3 messages / 24h, ≥ 10 min apart unless they replied since | RECIPIENT_RATE_LIMITED |
| Bulk pattern | identical text to more than 20 distinct numbers within 1h | BULK_PATTERN_BLOCKED |

Tenants can tune coldPerDay/declaredPerDay and related knobs within a platform-configured floor and roof from Settings → Messaging; a value outside [floor, roof] is rejected server-side, never merely clamped silently.

Caps (groups)

Sending to a group uses an entirely separate budget from the 1:1 one, keyed on groupsPerDay/groupPerRecipient24h, because a group is not a person:

| Rule | Code |
|---|---|
| The caller must supply group context (participant count, admin status, announce flag) | GROUP_CONTEXT_REQUIRED |
| The shop's number must administer the group — required for every group, not only "announce" ones, since opt-out cannot work per-member and mere membership is not consent to broadcast | GROUP_NOT_ADMINISTERED |
| Weighted daily cap: one "share" of groupsPerDay per GROUP_SIZE_UNIT participants (rounded up, minimum 1) — a 200-member group costs more than a 5-member one, because it reaches more people | GROUP_DAILY_CAP_REACHED |
| Per group | ≤ groupPerRecipient24h messages / 24h | RECIPIENT_RATE_LIMITED |

GET /shops/{idOrName}/groups returns isAdmin/announce per group so a caller can filter and explain a likely denial before attempting a send — but the policy above is what is actually authoritative; the route's canSend field is advisory.

Enforced inside the bot regardless of caller

The recipient must exist on WhatsApp (rejected, code RECIPIENT_NOT_ON_WHATSAPP); one send at a time per shop with a random 6–20 second gap between sends; a "composing" (typing) presence shown before each message; a 15-minute pause of the shop's whole queue on a rate signal from WhatsApp itself; and on a lost session every queued message fails WHATSAPP_SESSION_LOST and the channel is flagged until reconnected. If a shop's own WhatsApp is disconnected, some sends may route through the platform's shared pool instead, wrapped with a visible "sent via OmniManager" attribution — never silently as the tenant.

Best practice: message people who expect it, vary wording, spread sends over the day, and honour opt-outs and replies immediately.

MarkdownSchéma OpenAPI

Messaging & the anti-ban policy — OmniManager