Frequency capping + quiet hours
Translator note: machine-translated baseline. Needs human review by a native UK speaker. EN source is canonical.
Два policy gates, через які кожен send (campaign / journey / manual) проходить перед тим, як hit provider.
Frequency capping
Per-tenant config:
{
"messaging": {
"frequency_caps": {
"email": { "per_day": 1, "per_week": 5 },
"sms": { "per_day": 1, "per_week": 3 },
"push": { "per_day": 3, "per_week": 14 }
}
}
}
Cap query counts existing message_sends rows per (player, channel, rolling_window) — only counts rows у (queued, sending, sent, delivered, opened, clicked, bounced) статусі. Suppressed rows (SKIPPED_*) не count toward cap (otherwise cumulative misses would lock you out).
When campaign batch fan-outs, cap check runs as SQL filter directly у segment query — capped гравці never enter batch + не отримують message_sends row at all.
Для journey send_message + manual sends (single-player), checkMessagingPolicies() returns first breached cap.
Quiet hours
Per-tenant config + per-player players.timezone:
{
"messaging": {
"quiet_hours": {
"start": "22:00",
"end": "09:00",
"default_timezone": "Europe/Prague"
}
}
}
Effective timezone resolution: players.timezone (якщо set) → quiet_hours.default_timezone → UTC. Wrap-around supported (start=22:00 end=09:00 = quiet from 22:00 to 09:00 next day).
Implementation uses Intl.DateTimeFormat з timeZone option для DST-aware local-clock conversion.
When campaign sends to гравцеві у його quiet window, casinocrm.io doesn't drop message — inserts row з status=failed + error_code=SKIPPED_QUIET_HOURS, so campaign stats panel shows suppression. Operator може re-target on next campaign run.
Test sends bypass
checkMessagingPolicies({ bypass: true }) skips both checks. Admin "Send test" button uses bypass так operators can test their template without fighting their own policies.
Audit trail
Кожна suppression writes reason у message_sends.error_message:
"player in quiet-hours window (Europe/Prague)"
"player has no email for channel email"
"frequency cap email per_day=1 reached"
Recipients list (GET /v1/campaigns/:id/recipients?status=failed) shows them so operators can debug.