All work / 02 of 07 · AI engineering

RC Inbound Lead Tracking.

A custom inbound-tracking dashboard for a Langhorne, PA remodeling brand — five intake channels, a dual-agent Voice AI front door that calls within five minutes, JobTread mirroring, ten production automations, and a single funnel view that finally answers “which source converts.”

Client
Remodeling Concepts (Langhorne, PA)
Role
Architect + solo engineer
Year
2025 – 2026
Status
In production, ten automations running daily
01 · Context

Five intake channels, two CRMs, one human bottleneck.

RC was capturing leads from five places: the WordPress site, GHL inline forms on landing pages, Meta Lead Ads, a JF Acquisitions feed, and Fixr’s partner network. Sales lived in GHL; production lived in JobTread. The setter was the human router between everything — including the calls.

By 5pm a lead that came in at 11am was cold. The closers (Closer A (PA), Closer B (NJ)) were showing up to estimate appointments without context. Nobody could answer “which source converts.”

02 · Problem

Five sources. Two systems. Zero shared shape.

  • Five intake channels writing to four different schemas — no normalized view of “what is a lead.”
  • GHL and JobTread weren’t talking. Anything that didn’t mirror between them showed up as a missed handoff a week later.
  • First-touch was capped at the setter’s availability. After hours, weekends, lunch — leads aged.
  • Compliance: A10DLC SMS rules, opt-out handling, do-not-contact propagation — all manual, all fragile.
  • No funnel reporting. “Where do leads die?” had no real answer.
03 · What I built

One contact shape. Voice AI as the front door. Ten automations behind it.

Every intake channel normalizes into a single GHL contact shape with source tagging. A Voice AI agent calls within five minutes, qualifies, and either books on Closer A or B’s calendar or hands the lead to a second Voice AI agent for the Day 2 / Day 4 / Day 7 follow-up rhythm. Behind that front door, nine more automations handle reminders, opt-outs, team notifications, and the bi-directional JobTread mirror.

System map

SOURCES CAPTURE ENGAGE CONVERT ─────── ─────── ────── ─────── Website forms ┐ GHL inline forms │ ┌────────────┐ ┌──────────────┐ Meta Lead Ads ├─────▶ │ GHL │ ─────▶ │ Voice AI │ booked ──▶ Closer A (PA) JF Acquisitions │ │ contact │ │ Agent A │ └──▶ Closer B (NJ) Fixr partner feed ┘ │ normalized │ │ Day 1 · 5m │ round-robin │ + tagged │ └──────┬───────┘ via Google Cal └─────┬──────┘ │ no-book │ ▼ │ ┌──────────────┐ │ │ Voice AI │ still no-book │ │ Agent B │ ──▶ long-term │ │ D2 · D4 · D7│ nurture (12 mo) │ └──────────────┘ │ ─────────────── COORDINATE ──────┼─────────── GOVERN ──────────────── │ ▼ ┌────────────┐ ┌──────────────────┐ │ JT ⇄ GHL │ │ Appt reminders │ 2d email · 2d SMS · 2h SMS │ bi-dir sync│ │ Team notifs │ Closer A email + SMS + GHL note │ disjoint │ │ Opt-out handler │ STOP / "remove me" → DND │ stage own │ │ Stage handler │ "needs follow-up" → JT update │ note cron │ │ Long-term nurture│ 1 SMS/mo · 12 months └────────────┘ └──────────────────┘

Ten production automations

Rendered as they appear in the GHL automation builder. Trigger sits at the top (dark), actions cascade below.

01 · IntakeNew Lead Capture
Active
Trigger · Any source
Form submit · Meta lead · JFA webhook · Fixr feed · Inbound call
F
Set fields · Source tag
Normalize into one contact shape
+ Campaign · Service Interest · Sales Rep · Lead Source ID
T
Add tag · Stage opportunity
new-lead → Retail Lead Pipeline → Retail: New Lead
02 · SyncGHL ⇄ JobTread
Active
Trigger · Stage change
Retail Lead opp → any stage past New Lead
H
HTTP · JobTread API
Create customer + RC-XXXX job
Mirror Lead Status, Trade, Sales Rep, Address into JT custom fields
T
Tag both sides
jt-imported on GHL · JT job number written back as custom field
03 · Day 1Voice AI Outreach (Agent A)
Active
Trigger · Tag added
new-lead · within 5 min of capture
S
Send SMS · A10DLC
"Hey {{first_name}}, this is the RC team — got your request, calling in a minute."
C
Voice AI call · Agent A
Energetic, book-or-die · books on Closer A (PA) or Closer B (NJ)
?
If · booked
→ Workflow 05 (team notif)  ·  else → Workflow 04 (Agent B cadence)
04 · CadenceDay 2 / 4 / 7 Follow-Up (Agent B)
Active
Trigger · Tag added
voice-ai-d1-no-book
W
Wait 1 day · 4 day · 7 day
SMS-then-call rhythm between each wait
C
Voice AI call · Agent B
Empathetic · respects contact fatigue · book or route
T
Apply tag · on D7 still no-book
Long-Term-Nurture → hands off to Workflow 10
05 · TeamAppointment-Set Notification
Active
Trigger · Stage = Appointment Set
Retail Lead Pipeline · any source
Send email · to assigned closer
Closer A (PA) · Closer B (NJ) — full lead context
S
Send SMS · to assigned closer
Time · address · trade type
N
Internal note · Apply tag
appointment-set · contacted
06 · RemindersAppointment Reminders
Active
Trigger · Appointment scheduled
From any RC calendar (PA · NJ · Round-Robin)
Email · 2 days before
Confirm time, address, what to expect
S
SMS · 2 days before
"Looking forward to seeing you Thursday at 2pm."
S
SMS · 2 hours before
Specialist name + reply-to-reschedule link
07 · ComplianceOpt-Out Handler
Active
Trigger · Inbound SMS keyword
STOP · UNSUBSCRIBE · "remove me" (also Voice AI intent)
D
Set DND · all channels
Call · SMS · Email · Voice AI
T
Apply tag · Remove from campaigns
Do-Not-Contact · drops out of every active workflow
08 · SyncNote Sync · GHL → JT
Active
Trigger · Cron · every 10 min
Vercel scheduled function · read-only GHL token
H
Fetch · new notes since last sync
Diff against last noteSyncedAt timestamp per contact
H
POST · JT job comment
Append note as JT comment · update noteSyncedAt
09 · RoutingStage-Change Handler
Active
Trigger · Stage change
"Retail: Needs Follow Up" · "Retail: Quoted" · "Sold"
H
Update JT · Lead Status custom field
Disjoint ownership: sales = GHL writes JT · production = JT writes GHL
N
Notify · apply tag
Slack + tag on contact · keeps both systems honest
10 · NurtureLong-Term Nurture
Active
Trigger · Tag added
Long-Term-Nurture · usually from Workflow 04
W
Wait 30 days · loop ×12
Once per month for one year
S
Send SMS · seasonal
"Hey {{first_name}}, here's our {{season}} project gallery."
?
Check · DND / opt-out
If Do-Not-Contact set, exit loop immediately

Stack

GHL (Private Integration Tokens) GHL Voice AI (Agent A + Agent B) JobTread API Meta Lead Ads (native GHL) Gravity Forms REST JFA + Fixr partner webhooks A10DLC SMS (GHL native) Google Calendar (round-robin) Custom Node.js handlers (Vercel) 10-min cron poller

Key decisions

  • One canonical contact shape, set at capture. Every channel — web form, Meta lead, JFA webhook, Fixr feed, inbound call — normalizes into the same custom-field set with a source tag before anything else fires. Every downstream automation assumes that shape, so each new integration is half a day instead of a week.
  • Voice AI as the front door, not a fallback. Considered “page the setter first, AI on no-answer” — rejected. Setter availability was the bottleneck; making the AI the default opens the calendar to 24/7 lead arrival and frees the human team to close, not dial.
  • Two Voice AI agents, two personalities. Agent A (Day 1) is energetic and book-or-die. Agent B (Days 2/4/7) is empathetic, respects contact fatigue, and routes to long-term nurture instead of pushing a third call. Two agents = clean handoff = no awkward “hi again, hi again, hi again.”
  • JT mirror on qualify, not on creation. Only qualified leads create a JT job. Keeps JobTread from clogging with cold leads and keeps production looking at a list that’s 100% actionable.
  • Disjoint stage ownership across the GHL ⇄ JT sync. Sales stages live in GHL, fulfillment stages live in JT, the sync writes one direction per stage. No loops, no fight-over-truth at 2am.
  • Opt-out is one funnel, not one toggle per channel. “STOP” via SMS, “remove me” via Voice AI, manual DND in GHL — all collapse to the same Do-Not-Contact tag and the same campaign-removal action. A10DLC compliance becomes a property of the contact shape, not a per-workflow decision.
  • Round-robin lives in Google Calendar. Closer A and B already kept their calendars there; the Voice AI checks both for availability and books the first open slot. No new tool for the closers to log into.
  • Read-only scopes wherever possible. The note-sync cron and the funnel event log use read-only GHL scopes; only the automations that need to write hold write tokens. Smaller blast radius if a token leaks.
04 · Selects

Four screens. One lead, end to end.

The Inbox (above) is where the lead lands and the auto-touches fire. From there it moves through four more screens — appointments out to the closers, the sales pipeline, production once it’s qualified into JobTread, and finally the value rollup that answers “which source actually converts.”

Screen 02 · GHL Calendar — Round-Robin between Closer A (PA) and Closer B (NJ), Workflow 06 reminders queued
Screen 03 · GHL Opportunities — Retail Lead Pipeline, real stage names, Sold → creates JT job via Workflow 02
Screen 04 · JobTread — LD-XXXX job numbers, Lead Status custom field mirrored from GHL via Workflow 02
Screen 05 · Lead value — JT revenue rolled back to the source tag set at capture
05 · Outcome

Under a minute to first message. The setter never sleeps.

< 1 min
Leads receiving first message after submit (SMS auto-reply, 24/7)
~12 min
Time saved per AI outbound call (vs. human setter dial + qualify + book)
10
Production automations across the inbound funnel

Multipliers come from the sibling GHL Automation Scorecard project: self-booked appointment = 12 min, auto-reply = 3 min, contact captured via form = 3 min, follow-up step = 2 min. Stacked across RC’s monthly volume the system clears a meaningful chunk of setter time — the real unlock, though, is that Closer A and B now show up to estimate appointments with full context, and the team can finally answer “which source converts.”

06 · Lessons

What I’d do the same. What I’d do differently.

Keep: Centralize on one contact shape with a source tag before wiring anything downstream. Cheapest decision in the project; pays for itself the first time you add a new intake source (Fixr was a 90-minute integration).

Keep: Two Voice AI agents with distinct tones. Trying to do energetic-and-empathetic in one agent meant neither read true. Splitting them was a one-day refactor that doubled the callback rate.

Keep: Disjoint stage ownership across the JT sync. The boring version of bi-directional sync that doesn’t loop.

Change: Build the email + phone dedupe rule before launch. Two early leads created two JT jobs because the contact lookup was email-only and the lead used a different inbox on mobile.

Change: Write SMS copy like a person from day one. The first Day-1 auto-reply read like a workflow because it was a workflow.