
AI Outbound Calling System
AI outbound calling system for a real estate lead marketplace: texts and calls at the lead's local hour, retries, opt-out checks and a live A/B test.
- n8n
- Retell
- PostgreSQL
- RabbitMQ
- GoHighLevel
The business
This is a real estate lead marketplace. Home sellers who need to sell fast leave a request through ads and the website. The marketplace qualifies each seller and passes the good ones to an investor. A lead counts as a win only when the seller is qualified and the phone number is live and picks up. The AI outbound calling system we built is the part that reaches those sellers by text and by voice.
The problem
Every lead is paid for before anyone calls it. If the first call lands at the wrong hour, or the number turns out to be a landline, the money is gone and the seller is cold. The marketplace felt this daily.
All contact lived inside GoHighLevel, and its built-in Voice AI placed the calls. It worked on paper. In practice the team ran into the same walls week after week:
- SMS sends failed, and unsubscribes were handled unevenly.
- Few calls were answered, and nobody could see why.
- There was no control over how often a lead was contacted.
- Retries were unreliable and the analytics were thin.
- The Voice AI was closed, so a bad week could not be debugged.
The sharpest risk was invisible. Without a rule engine, nothing stopped a second call to someone who had just replied, or a call at 6 a.m. to a seller in another time zone. Calling an opted-out number twice is the kind of mistake that ends up with a lawyer, not in a report.
Contact before the system: SMS, calls and retries inside one CRM, with no control over cadence and little reporting.
Why not stay with the CRM's voice agent
The founder did not want to guess. The comparison below is what pushed the contact logic out of the CRM, and the A/B test came from the same reasoning: measure both, then decide.
| Built-in CRM voice agent | What we built | |
|---|---|---|
| When to call and text | Fixed behavior, no say in cadence | Our own scheduler decides, per lead, in lead local time |
| Retries | Unreliable | After 1 hour, then 6 hours, moved into the calling window |
| Opt-outs and replies | Handled unevenly | Checked before every call, blocked and logged |
| Invalid numbers | Dialed anyway | Carrier lookup first, so they cost no calls |
| Reporting | Thin | Every event in PostgreSQL, funnel and A/B report on top |
| Fixing a bad week | Closed system | Open workflow with a state per lead |
How the AI outbound calling system works
We moved contact out of the CRM into its own workflow in n8n. Intake is separate from processing, so a burst of new leads never slows the calls that are already due. The route has ten steps, and the last check before the phone rings is the one that matters most.
The route of one lead. The pre-call check is the control point: no call is dialed until it passes.
- A new lead goes into a RabbitMQ queue and waits five minutes. The number is normalized to E.164.
- A carrier lookup checks that the number is a mobile, active, allows SMS and calls, and is not high risk. Invalid numbers are marked and cost no calls.
- The system checks whether this number ever unsubscribed. If so, the contact is blocked and logged.
- It finds the lead's time zone from the ZIP code.
- Leads alternate between variant A (Retell) and variant B (GoHighLevel).
- The lead gets an SMS from a dedicated number, and the event is recorded.
- A call job is created for the lead's local time. Calls go out only between 08:00 and 21:00 there.
- A dispatcher picks up due jobs every minute. At most 18 calls run at once, and a database lock keeps two runs from taking the same job.
- Right before dialing, the system checks again for a reply, an opt-out or an earlier answered call. Any of them stops the job.
- Retell places the call, the result is parsed and written to the CRM. If nobody answers, a retry follows after one hour and then after six hours, moved into the daytime window.
Call queue. Jobs come from the database, and the slot counter shows the cap of 18. Redrawn with demo data.
One lead from first touch to investor. The third call never dialed because the lead had already replied. Redrawn with demo data.
Replies, callbacks and the investor handoff
Leads do not follow the script. They reply to the text, call the number back, or answer on the second try. Inbound SMS and calls go into the same events table as everything else, so the next decision always sees the full history.
When a lead calls back, a separate callback agent answers. If the seller qualifies, the call is transferred live to an investor. A repeat call is scheduled for sellers who asked to be reached later. The same Retell setup also runs an inbound voice agent for the website.
The hard parts
Never calling twice
The dispatcher runs every minute, so two runs can overlap. A database lock stops them from taking the same job, and the cap of 18 simultaneous calls keeps the load predictable. The pre-call check closes the remaining gap: if the lead replied, called in, unsubscribed or already picked up, the job ends without dialing.
Calling at a sane hour
Leads come from many time zones and calls are allowed only in the daytime. Each job carries the lead's local time. A retry that would land at 21:25 moves to 08:00 the next morning instead of firing on a fixed delay.
Comparing two systems fairly
To choose with data, we split leads between Retell and GoHighLevel and measured both. The first version of the comparison math was off. We fixed the calculations in September 2026, and every number below uses the corrected version.
Numbers and SMS errors
Failed sends were one of the original complaints. We moved telephony and number management to Telnyx, so the team now controls the sender numbers and sees delivery per message.
Result
The system has run in daily production since summer 2026. In about 19 days of September 2026 it handled 974 leads, 965 of them valid, and 955 received a first contact. 535 were reached (55.44%). It placed 1,858 calls, 1.91 per lead, and 323 were answered (17.38%). It sent 957 SMS messages, 912 delivered (95.30%), and 31.73% got a reply.
Funnel and variant comparison, counted from the events table. Redrawn with demo data.
The A/B test is the more useful result. Today GoHighLevel answers better, and we say so. The old setup gave the team no such comparison at all.
Answer rates by variant, September 2026, about 19 days, corrected calculations.
| Before | After | |
|---|---|---|
| Calling rules | Out of the team's hands | Window, cap and cadence set by the team |
| Invalid numbers | Dialed anyway | Filtered by a carrier lookup |
| Replies and opt-outs | Handled unevenly | Checked before every call |
| Retries | Unreliable | 1 hour, then 6 hours, in the daytime window |
| Voice setup | One closed agent | Two variants compared on the same leads |
| Reporting | Thin | Funnel, calls, SMS and A/B from one events table |
The marketplace now decides where traffic goes from its own numbers instead of a vendor's promise, and it can switch the voice provider without rebuilding the route.
Our part
We built the orchestration in n8n, the queue, the event model, the call scheduler, the Retell voice agents for outbound and callback calls, the Telnyx number setup and the analytics, and we run them day to day. The marketplace backend belongs to another developer, and the client's manager owns the strategy for raising the contact rate. We started in June 2026.
The same approach to calls shows up in the AI phone dispatcher we built for a home services company. For the engineering side, read why a queue makes webhook automation reliable and how to model CRM funnel events.
Running a process like this by hand?
Tell us what your team repeats every week. We reply within one business day with a plan and a price.