Rex Automaton
All posts
AI Voice & Chat AgentsJuly 28, 202610 min read

How to Migrate to an AI Dialer at Scale

A practical, production-proven migration playbook for replacing an outbound dialer with an AI calling system that qualifies large lead backlogs without burning human dialer hours.

By Jacky Lei

An AI dialer migration replaces your legacy power dialer with an orchestrated engine that runs scheduled calling campaigns, classifies outcomes with an LLM classifier, and writes structured dispositions back into the CRM so reps only handle high-value callbacks. We built and shipped this pattern in production for a real-estate brokerage where the dialer worked a 3,000+ lead backlog and reduced false bookings through deterministic guards and data hygiene. This guide shows what we built, how it works in production, migration steps, and the gotchas that tripped us up.

The problem it solves

Answer-first: migrating to an AI dialer solves scale and human-cost problems. It lets you process large lead backlogs automatically, surface genuine booking opportunities, and reduce wasted human dialer time.

Manual process. A team exports a lead list, uploads to a dialer, waits for reps to call, copies dispositions to the CRM, and chases missed numbers. This is repeated every campaign. It costs rep hours, introduces stale-lead risk, and produces noisy booking data when classification is imperfect.

Automated process. The AI dialer polls a single source-of-truth list, attempts calls on a human-like cadence, classifies outcomes, applies deterministic guards to prevent false bookings, and writes only verified opportunities to the CRM for a human to finalize.

ManualAutomated
Export CSV, manual uploads, and repeated re-keysSingle source-of-truth, scheduled automated runs, and idempotent writes
Reps spend hours dialing marginal leadsAI processes bulk lead backlog overnight and surfaces qualified callbacks
High rate of false bookings and stale dialsDeterministic guards, stale-lead eviction, and pause-on-quota protect costs
No easy audit trail of calls vs dispositionsAppend-only audit log and per-call tracebacks for compliance

How the automation works

Answer-first: the migration is an orchestration that moves leads into a controlled queue, runs AI-powered call sessions, classifies outcomes with layered checks, and updates the CRM while keeping a human approval path for sensitive cases.

Architecture overview: a scheduled importer normalizes and deduplicates leads into a database; a scheduler enqueues calls with per-tenant pacing; a dialer runtime runs human-like sessions and streams transcript + classifier outputs to an orchestration layer; deterministic guards and quota checks prevent bad writes; the CRM or campaign system receives idempotent upserts and a lightweight reply queue surfaces hand-offs for human review.

Components we ran in production:

  • Source-of-truth lead store: A single Postgres table (or managed datastore) that holds canonical lead rows, call-state, and a last-seen fingerprint. We treated this table as the true master so re-runs did not duplicate sends.

  • Scheduler / Wedge engine: The scheduler builds short human-like sessions: batch of 40 leads, 12, 18 minute session duration, randomized inter-call delays. This prevents high cadence that looks automated.

  • Dialer runtime (accented engine): The AI dialer emulated a human session: click-to-call or vendor call-init, media playback, voice STT transcript capture, and an LLM classifier that suggested a disposition. The engine recorded audio and the final structured disposition.

  • Deterministic guard layer: A rule engine that prevents borderline or model-hallucinated bookings from writing to the CRM. It requires a classification threshold and at least one deterministic signal: explicit verbal confirmation, call length minimum, or human approval if the confidence is low.

  • Pause and quota monitor: Tracks vendor TTS/STT budget and pauses campaigns automatically when overage risks appear, then resumes after a scheduled refresh.

  • CRM sink with idempotency: Upserts were idempotent and ledgered. Each write included the call UUID and a checksum so retries never created duplicate bookings.

AI dialer migration workflow showing leads -> scheduler -> AI dialer engine -> guard layer -> CRM writes and human queue

Step-by-step: how to build it

Step 1: freeze a single source-of-truth

We moved all candidate lists into one Postgres table and rejected CSV uploads that were not reconciled to that table. The table holds lead_id, contact fields, fingerprint hash, and state columns. This eliminated duplicate sends when backfills were re-run.

-- small example: canonical leads table
CREATE TABLE leads (
  id serial PRIMARY KEY,
  external_id text,
  email text,
  phone text,
  fingerprint text UNIQUE,
  status text DEFAULT 'queued',
  last_seen timestamptz DEFAULT now()
);
 
-- upsert helper used by the importer
INSERT INTO leads (external_id,email,phone,fingerprint)
VALUES ($1,$2,$3,$4)
ON CONFLICT (fingerprint) DO UPDATE SET last_seen = now();

Key gotcha: accept only one owner of the connection that performs scheduled imports so you do not accidentally swap credentials and create duplicate sessions.

Step 2: build a scheduler that runs human sessions

We wrote a small scheduler that picks a wedge of leads per tenant, schedules a session, and marks each lead as in_progress before dialing. The scheduler randomized inter-call pauses and enforced per-tenant caps.

// scheduler excerpt: pick leads and enqueue
const leads = await db.query("SELECT id,phone FROM leads WHERE status='queued' LIMIT $1", [batchSize]);
for (const l of leads.rows) {
  await db.query("UPDATE leads SET status='in_progress' WHERE id=$1", [l.id]);
  await queue.enqueue('call', { leadId: l.id, phone: l.phone });
}

Key gotcha: mark rows in-progress under the same DB transaction that enqueues the job to avoid races that cause double-enqueue.

Step 3: implement the dialer runtime and classifier

The runtime handled call lifecycle: dial, connect, record, transcribe, classify. We kept the classifier call-out generic: send transcript to the model and return a structured disposition. The runtime always produced a call UUID and wrote a full audio+transcript artifact to object storage for audit.

// call run outline
const call = await callClient.dial(phoneNumber);
if (!call.connected) return markFailed(leadId, 'no_answer');
await call.recordStart();
const transcript = await call.transcribe();
const disposition = await classifier.classify(transcript);
await call.recordStop();
await storeCallArtifacts(callId, transcript, recordingUrl);

Key gotcha: always persist call artifacts before making any CRM writes. If a downstream write fails, you still have the evidence for later reconciliation.

Step 4: add deterministic guards and a write policy

We layered deterministic rules on top of model output. A booking only wrote to the CRM when: classifier_confidence >= X and (explicit_confirmation_detected OR call_length >= Y). If confidence was low but a potential booking existed, we routed that call to a human queue instead of creating a booking.

-- simple ledger write with guard check
BEGIN;
SELECT confidence INTO conf FROM call_meta WHERE call_id = $1 FOR UPDATE;
IF conf >= 0.85 AND exists_confirmation THEN
  INSERT INTO crm_upserts (lead_id,call_id,created_at) VALUES ($2,$1,now());
  UPDATE leads SET status='booked' WHERE id=$2;
ELSE
  UPDATE leads SET status='needs_review' WHERE id=$2;
END IF;
COMMIT;

Key gotcha: guard thresholds must be tuned on real traffic. Too low and false bookings appear. Too high and you create manual workload.

Step 5: implement quota and overage protection

We added a quota checker that periodically queries vendor usage and pauses the scheduler when projected monthly spend or character counts hit a threshold. Pause is an automated stop that writes campaign state and notifies ops.

# cron: daily quota check
node scripts/checkQuota.js --projectId=acme
# checkQuota will POST /control/pause if usage > threshold

Key gotcha: vendor quotas can overrun while a session is mid-flight. We wrote a short cached quota check with a 10 minute TTL and a mid-session guard to pause new sessions if usage looks risky.

Where it gets complicated

Classifier misclassifications. Early runs produced false-positive bookings. We fixed this with a two-layer approach: tighten classifier prompts, then add deterministic guards that require explicit verbal confirmation or a minimum call length. The classifier changes alone did not solve the problem.

Stale leads and upsert semantics. The local dialer cache sometimes dialed leads that were archived in the CRM. We implemented an evict-on-sync strategy that removed rows missing from a full WATCH import. Evicting stale leads retired 640 leads in one real deployment and prevented wasted calls.

Vendor TTS/STT overage. One deployment accrued nontrivial TTS overage before guard logic ran. We added a pause/resume flow and a cached quota endpoint. The pause must be reversible by ops and transparent to clients.

False booking rollbacks. Before adding deterministic guards we saw classifier-driven false bookings. We added a retract endpoint that the CRM can call to remove a booking and mark the lead for human review. Planning for retracts upfront saved hours of cleanup.

TCPA and DNC compliance. Call cadence, opt-out handling, and call recording laws vary by region. We enforced per-tenant DNC filters at import time and recorded all opt-out events in the audit log. Compliance is not optional.

Auth and credential ownership. Credential ownership confusion caused silent failures during rollout. Decide who owns the sending connection and who rotates keys before you flip production; if the scheduler runs under a user account, their token expiry can quietly stop calls.

Human-in-the-loop UX. The hand-off UI must be fast and contextual: link to call audio, transcript highlight, and a one-click accept/reject for booking confirmations. Slow or incomplete context made human reviewers derail the flow.

What this actually changes

Answer-first: you move from manual dialing and re-keying to an automated, auditable funnel that runs large lead backlogs overnight, surfaces verified booking opportunities, and pauses safely on vendor and compliance risk.

Real results we observed in production for a real-estate ISA dialer we built and shipped:

  • Coverage snapshot and throughput: the system processed a call universe of 3,130 callable leads and processed them end-to-end.
  • Call volume and outcomes: total calls recorded in the deployment were 5,573 and the dialer produced approximately 12 verified bookings after iterative tuning and guard fixes.
  • Data hygiene wins: evict-on-sync retired 640 stale leads and shrank retry queues dramatically, which removed repeated calls to inactive numbers.
  • Accuracy fixes: classifier and server-side deterministic guards eliminated a high rate of misclassified bookings after fixes deployed 2026-05-14.

These changes were structural: instead of burning rep hours on low-yield dialing, we forwarded human attention only to high-confidence opportunities, and we added automated defenses against vendor overage and false bookings.

One external benchmark that explains the urgency to act: leads are time sensitive. Responding faster dramatically improves odds of qualification. See a practical summary on lead response timing: https://www.leadresponsemanagement.org/how-long-does-it-take-to-respond-to-a-lead/

Frequently asked questions

How long does a migration take? A focused migration to an AI dialer typically took us 3 to 6 weeks from kickoff to safe pilot for a single tenant. That includes importing and deduping the canonical lead table, a limited scheduler and session runtime, guard rules, and a monitored dry-run. The exact time depends on CRM complexity and compliance gaps.

What does this cost to run monthly? Recurring costs split into vendor voice/TTS/STT usage and modest hosting. The dialer we ran had nontrivial vendor usage early during training runs; protecting against overage with automatic pause logic kept the recurring bill predictable. We review billing in the first two weeks of live traffic and platform-tune quota thresholds.

How do you avoid false bookings? We layered classifiers with deterministic signals: explicit confirmation phrases, minimum connected time, and match of caller-stated booking details to lead fields. Low-confidence results go to a human review queue. That two-layer approach removed the majority of false bookings in our deployment.

Can you keep humans in the loop? Yes. The migration pattern we used always retains human approval for money or contract events. You can route only low-friction confirmations for auto-upsert and leave holds for higher-risk dispositions.

Do I need a developer to set this up? Yes, the migration requires engineering: scheduler, dialer runtime, classifier tuning, guard rules, and CRM idempotent upserts. Non-technical owners can run the configuration and review UI once the system is deployed, but the build needs an engineer or partner.

How do you handle compliance like TCPA and DNC? DNC and consent lists are applied at import time. We keep append-only audit logs for each call and expose opt-out webhooks to update the lead store instantly. Compliance planning is part of the migration scope, not an afterthought.

We built and shipped this migration pattern for a production real-estate ISA dialer and saw the benefits described above. If you are planning to replace a legacy dialer and want to avoid the common traps: consolidate to one source-of-truth, run human-like sessions, add deterministic write guards, and protect against vendor overage with an automated pause. Book a short discovery call and we will tell you in the first five minutes whether your setup maps to this pattern or needs a custom approach.

Related: our live case study on Follow Up Boss AI calling is here: /blog/automate-follow-up-boss-ai-calling. See our AI voice agent services at /services#ai-voice-agents. To schedule a discovery call, go to /book.

Want us to build this for you?

15-minute discovery call. No pitch. We tell you what to automate first.

Book a Discovery Call

Related reading