Skip to content

Lead Lists

Describe lead lists and leads: the contacts a campaign dials, how they are imported, and how they move through their lifecycle.

A lead list (lead_lists) is a named set of contacts attached to a campaign, with its own caller_id (the CLI presented when dialing its leads). A disabled list (active = false) contributes zero dialable leads.

Each lead (leads) holds phone_numbers (JSONB, ≥1), a primary_phone, a resolved timezone, an attempt_count, a next_attempt_at (NULL = eligible now), and arbitrary CSV columns in attributes. A lead’s state moves fresh → attempted → contacted → closed; closed is absorbing, so a closed lead is never re-dialed.

Lists are uploaded and managed in the tenant app (and the /campaigns/{id}/lead-lists endpoints). The CSV importer creates one fresh lead per row with ≥1 parseable phone number, mapping a timezone column when present (else the campaign default), and reports per-row rejections.

phone,first_name,last_name,timezone
+15551230001,Ada,Lovelace,America/New_York
+15551230002,Alan,Turing,America/Chicago

The pacer selects eligible leads (state IN ('fresh','attempted'), not excluded, next_attempt_at due, not on DNC) and atomically claims each one — the claim flips it to attempted and sets a short lock so no two pacer ticks (or edges) dial the same lead.

  • Numbers are normalized; the DNC anti-join compares on digits with the leading + stripped, so +441… and 441… match.
  • Per-lead timezone drives calling-window compliance — carry it where possible.
  • Optimistic concurrency (version) resolves the pacer-vs-admin edit race.