Compliance & Contacts

Contacts messaged at the right time. In their timezone.

Business hours are configured per campaign and enforced before every outbound send. The scheduler checks eligibility before enqueuing — not after. No message leaves the platform outside your configured windows.

Per-campaigntimezone configuration — multi-segment hour strings supported
7 dayssilence timeout — conversations close without contact activity
30 daysarchival — closed conversations archived after 30 days
0 out-of-hourssends — business hours checked before every bump is enqueued
How scheduling works

Bumps fire when they're supposed to. Not when a job happens to run.

The scheduler checks three things before enqueuing a bump: whether the contact is eligible based on bump intervals, whether the current time falls within the campaign's business hours, and whether the conversation has already exceeded its maximum bumps. All three must pass — no partial checks.

  • Bump intervals array defines minimum hours between each touch
  • max_bumps derived from array length — no separate field to manage
  • Business hours checked before enqueuing — never checked after
  • Campaign timezone applied — contacts in different timezones never receive out-of-hours messages
  • Multi-segment hour strings supported — different windows for weekday and weekend
  • FOR UPDATE SKIP LOCKED prevents concurrent workers processing the same conversation
Bump scheduling check
  1. 1
    Interval check
    Last outbound + bump_intervals[bump_count] hours elapsed?
  2. 2
    Cap check
    bump_count < len(bump_intervals)?
  3. 3
    Business hours check
    Current time within campaign hours in campaign timezone?
  4. 4
    SKIP LOCKED
    FOR UPDATE SKIP LOCKED prevents concurrent worker collision
  5. 5
    Bump enqueued
    Job queued — worker sends message at configured time
  6. 6
    Turn persisted
    bump_count incremented, next interval calculated
Conversation lifecycle

Conversations that close themselves.

Silence timeouts and archival are automated. Conversations with no activity for 7 days close automatically. Closed conversations archive after 30 days. No manual cleanup required.

7-day silence window

If a contact hasn't replied within 7 days, the conversation is automatically closed with state TIMED_OUT. No follow-up message is sent — the contact is treated as disengaged and the conversation exits the bump queue.

How it works: Measured from the last inbound message, or last outbound if no inbound exists. Enforced by the scheduler's silence timeout job.

30-day archival

Conversations that have been in a closed state for 30 or more days are moved to ARCHIVED. Archived conversations remain visible in the operator platform but no longer appear in active campaign views.

How it works: Archival job runs nightly — no manual intervention required. Archived conversations are still accessible for compliance review and export.

Reach-back-out scheduling

When a contact says they are busy and asks to be followed up later, the AI returns a REACH_BACK_OUT directive. The scheduler sets reach_back_out_at on the conversation and fires a follow-up at the appropriate time.

How it works: reach_back_out_enabled is a per-campaign setting. If disabled, the directive is overridden to PAUSE — no follow-up is scheduled.

Burst traffic handling

All scheduler eligibility queries use FOR UPDATE SKIP LOCKED — multiple workers can run simultaneously without selecting the same conversation for bumping twice, even under heavy load from large campaigns.

How it works: Queue-backed via Redis + RQ — worker restarts don't lose queued jobs. All state is persisted in PostgreSQL before the job is enqueued.

Want to see how scheduling
works across timezones?

We'll configure a campaign with multi-segment business hours and show the scheduler in action.