Finish the Lead Machine
One SOW to take the concrete-leveling lead pipeline from "proven today" to "runs itself, feeds Dan's CRM, nothing slips." Built for a fresh session — all context included below, nothing assumed.
✓ Where things stand (proven, not assumed)
- Lead delivery works end-to-end: lead qualifies → queued the same second → SMS from (626) 325-0175 to Akash — carrier-confirmed, alarmed if it ever fails.
- All 14 real leads re-sent to Akash today in executive format. Nothing has ever been sent client-facing.
- Dan's DripJobs credentials are already in the vault (both the direct lead-intake webhook URL and his API key, received Jul-27) — the CRM pipe needs wiring, not asking.
- Every session captured & archived; deliverables indexed on the close-out page.
.codex/references/brain.py) and fetch by service/scope/label. Never paste secrets into pages or chat.A Conversation audit — every error found, and its status
Source: full re-read of all 14 lead conversations (Jul 22–27) + delivery records + incident ledger.
| Error found | Leads hit | Status |
|---|---|---|
| Bot promised "estimator will reach out" — nobody ever did (rail was dead) | Ann Z., Scott E., + most of Jul 22–26 | rail fixed calls owed |
| Bot re-asked for info the lead already gave (Ann: "I sent this before") | Ann Z. | bot rule added Jul-23 |
| Info given in chat never saved to the record (ZIP lost) | Nathan G. | backfill → WS-3 |
| Duplicate lead records | Nathan G. ×2 | merge → WS-3 |
| Project words written into city/ZIP fields ("Driveway" as a city) | Twyler W., John M. | cleaned + extractor guards Jul-27 |
| Transfers queued for leads who declined | Bradford C., Mark C. | gate live — both correctly blocked; declines re-verified genuine |
| "Sent" recorded while the message went to a dead channel (old iMessage path) | Sam E., Spencer J. | channel killed; 626 rail live books mismatch → WS-6 |
| Leads stuck silently with no contact info, no alert | Twyler W., Mike Y. | watchdog live rescue cadence → WS-4 |
| Follow-up nudges never fired for one silent lead | Twyler W. | investigate → WS-3 |
| Lead contact records deleted from the CRM | Cindy S., Richard W. | re-create → WS-3 |
| Fake "leads" minted from internal message threads | 3 internal contacts | guard live Jul-27 root cause in CRM UI → WS-4 |
| Job photos/videos not mirrored to durable pages | Joseph, Scott, Spencer | mirror → WS-3 |
| Internal debug text leaking into packet notes | Sam E., Spencer J. packets | scrub template → WS-3 |
1 Instant delivery agent
Outcome: lead-to-phone in seconds, not "up to 5 minutes." Akash already said build it.
- Add a webhook trigger to the sender workflow; database calls it the instant a transfer is queued.
- Keep the 5-minute sweep as the safety net (event first, thin loop as backstop).
- Prove with a synthetic lead: stopwatch from "qualified" to phone buzz. Target < 30 seconds.
Technical context
Sender = n8n workflow6ET0nCwUKY35WBwR "SP Lead Transfer Drain (GHL SMS)" on eyefly.app.n8n.cloud (n8n API cred: vault n8n/agency/rest-api-cowork-claude). Enqueue trigger = Postgres sp_enqueue_qualified_lead_transfer_trg on sp_client_leads (ledger 91882). Options for the push: pg_net HTTP call from the trigger, or a Supabase database webhook on INSERT into sp_client_lead_transfers → new n8n webhook trigger node that runs the same claim→send→verify chain. Test lead recipe: insert into sp_client_leads with source='meta_messenger', meta_page_id='1308868515634109', qualification_state='qualified', is_test=TRUE, metadata.allow_test_transfer='true'. Ledger every change; reversible.
2 DripJobs direct — leads onto Dan's board agent screenshot gate
Outcome: every qualified lead lands as a deal card on Dan's DripJobs pipeline in seconds — Akash's 626 text keeps arriving in parallel. Zero client-facing sends until the gate passes.
- Wire the vaulted direct lead-intake webhook (Angi-format — Dan already provided it Jul-27) as a second delivery destination, disabled.
- Extend the sender to speak that format: name, phone, email, city/ZIP, job description (exec summary), lead source "St. Pierre – Facebook", media page link, street address when known.
- Fire ONE test card "SP-Test Booking DeleteMe" → screenshot to Akash → his approval flips it on.
- Parallel ask to Dan (bundle with service-area text): copy his public Booking Form link (Settings → Booking Form) so leads can self-schedule — then the link rides in every packet.
Technical context
Vault:dripjobs/levelking-webhook = direct lead-intake POST endpoint (Angi-format; anyone with the URL can inject leads — treat as secret, never print). dripjobs/levelking = API key (untested at capture). Field map lives in the Dan guide page + 01-Projects/St_Pierre_Digital/lead-transfer-automation/dan-dripjobs-zapier-setup.md (Zapier path now OBSOLETE for lead intake — the direct webhook replaces it; keep the doc's field map). Yesterday's working context: session "Lead transfer weekend issue" (transcript 60f9290a…jsonl, ~16:14–17:19 block) — card auto-creation via the webhook was proven there. Destination table: sp_client_delivery_destinations (add channel='webhook', provider='dripjobs', is_enabled=FALSE). Drain currently handles channel='sms' only.
3 Fix everything the audit caught agent
Outcome: every open row in the audit table above flips to resolved, with proof.
- Backfill Nathan G.'s ZIP (75022, sitting in his chat) + merge his duplicate record.
- Re-create Cindy S. + Richard W. as CRM contacts from their saved data; find out what deleted them (Akash's own contact was also deleted+recreated Jul-26 — likely same event).
- Mirror Joseph A. / Scott E. / Spencer J.'s job media to durable per-lead pages; write each page URL to the lead + CRM field. Approved format = one media page link per lead in the packet — never raw file attachments.
- Scrub internal debug text from the packet notes template (no "enriched from thread…" lines).
- Investigate why one silent lead never got follow-up nudges when the other did (follow-up config predates her thread?).
Technical context
Media mirror: POSThttps://sp-lead-media.stpierre.workers.dev/mirror body {slug, firstName, city, service, capturedAt, attachments:[{url,type}]}, header x-sp-media-secret (vault cloudflare/stpierre/sp-lead-media-mirror-secret), browser User-Agent required or Cloudflare 403s. Source attachment URLs (GHL durable) are already extracted in scratchpad main-resend-convo-live.json of session b5d855a5 (also re-fetchable via conversations API; GHL PIT via brain.get_pit(location_id='6c4MSgjdfngrPhwWD2cb'), Version 2021-07-28 + Chrome UA). Media page URL → GHL custom field sp_media_link (h6TB4j6dRtIBJda0K61b) + sp_client_leads.metadata.media_url. Lead ids + contact ids in the same scratchpad file. Notes scrub: the drain's formatPacket() already strips phone/zip — extend cleanNotes to drop "Qualified via Messenger bot…/enriched from…" sentences at the SOURCE (project_details writer in the bridge enrich node).
4 Your clicks — CRM & bot Akash, ~20 min total
Outcome: the bot triggers match what we designed, no stray automations, silent leads get rescued.
- Save the Handoff trigger condition (the 496-character version — phone AND location AND final message, decline guard, default-no).
- Paste the 6-step follow-up cadence (each step chases the missing phone/ZIP; steps 5–6 best-effort past Messenger's 24h window).
- Add the hard contact-info gate + verify the "qualified" tag step — numbered click-paths in the audit pack (Items 1–3).
- Flip 6 workflows to draft (list in collapsed notes) — kills the stray-automation class incl. the internal-contact re-fire path.
- Clear the 4 stale needs-review tags.
The 6 draft toggles + the trigger text + cadence copy
Draft these 6 (workflow list page toggles): SP — Pre-Call Nurture (12) · Trigger 2: Qualification Form → Remove from Confirmation Dialer · Trigger 2b: Unqualified → Remove from all workflows · SP - Launch Call Booked Tagger · SP - Launch Prep Packet Task · duplicate "z. Standard: Unsubscribe" (keep ONE live). Do NOT touch: the five *-numbered workflows, "Qualified Tag → Lead Submitted Event", the SP Shim forwarders, the DND pair, the live Unsubscribe, the contractor demo.
Handoff trigger (496 chars, paste exactly):
Trigger ONLY when ALL are true from what the lead actually typed in THIS conversation: (1) they named a service we offer (foundation repair, waterproofing, concrete leveling, crawl space); (2) they confirmed a location - city or ZIP; (3) they provided a valid phone number; (4) the bot has sent the final handoff message to schedule the estimator. Do NOT trigger if the lead declined or disqualified (not interested, project changed, already handled it, no thanks). When in doubt, do NOT trigger.
Cadence (6 steps): 5m "still there?" · 30m "no rush — whenever you're back, just drop your zip and best phone number and i'll get you on the estimator's schedule" · 4h "want me to just have the estimator give you a call? drop your number here and i'll set it up" · 1d "should i close this out or did you still want help?" · 2d "we're scheduling estimates in your area this week — still want yours? just need your zip + number" · 3d "last check-in — if the timing's off, no worries. reply whenever and i'll pick it right back up"
Canonical trigger texts live in the atlas: Build-Plans/ghl-atlas-text-extraction/wf-live-panels-jordan-bot-goals-2026-07-26.md (corrected verbatim Jul-28). GHL UI limit: trigger conditions 10–500 chars, revalidated on any edit.
5 Dress rehearsal — the one test that proves everything agent after WS-4 trigger save
Outcome: a fake homeowner DMs the Facebook page → Jordan qualifies → packet on Akash's phone (and DripJobs card if WS-2 is live) with zero manual pushes. Timed, logged as the official proof.
- Run the synthetic Messenger conversation end-to-end; record per-hop timestamps.
- Then let the 3-day Dallas bot watch (already running as a board card) accumulate real-lead proof.
Technical context
Level King page id1308868515634109 (m.me link on file). Prior "proofs" failed by testing single links — the standard (set Jul-28): no transfer claim without a full synthetic end-to-end run + carrier receipt. Watch for: qualified tag exact string, bridge webhook fire, enqueue same-second, send hop, GHL message status reaching "delivered", media page link presence. Compare total time pre/post WS-1.
6 Housekeeping agent
Outcome: the books always match reality, and known small hazards are closed.
- Bookkeeping fix: record every actual send in the transfer table (today's 626 re-sends of two Jul-28 re-sends exist in GHL but not in our records — that mismatch caused the "3 days of failure" confusion).
- Fix the delivery-verifier bug (route-check ordering) so "verified" stamps are trustworthy.
- Lock the client proof-report link before client #2 (token-keyed access).
- Home-folder brain copy fix — separate task chip already running.
Technical context
Verifier bug:sales-crm/lead-delivery.js verifyGhlLeadDeliveries() — route-check runs before settled-status check (ready task 09315d32 in brain.tasks). The n8n drain's Verify node already has the corrected order — port it back. Bookkeeping: the 03:34 ET Jul-28 manual re-drive sent 4 packets via GHL (message ids in conversation eapXgwOJjmdrSJiTGwWm) with no transfer rows; write catch-up rows with drained_by='manual_redrive_0728'. Client report: stpierre-client-report.pages.dev token enumerable — brain task b995e671.
⛔ Standing gates — the new session must honor these
- Nothing client-facing without Akash's explicit go. All sends → Akash's phone (…5477) only. Dan sees nothing until the DripJobs screenshot gate passes.
- Auto-send to leads stays OFF. Reply drafts are staged, never sent.
- No transfer fix counts without a synthetic end-to-end test + carrier receipt.
- CRM workflow-builder edits are Akash-only (hand him click-paths); Conversation-AI API writes are allowed.
- Never edit ad sets/campaigns with spend; approved copy is frozen. Packet format = executive summary + media page link (no raw attachments).
- Ledger every external write with a rollback note.
🔗 All links
| What | Link |
|---|---|
| Day close-out (all deliverables + task audit) | stpierre-jul28-closeout-docs.pages.dev |
| 30-day war plan | stpierre-dan-100k-warplan.pages.dev |
| CRM automation audit pack (click-paths for WS-4) | stpierre-ghl-audit-pack.pages.dev |
| DripJobs guide for Dan (field map lives here) | stpierre-dripjobs-webhook-docs.pages.dev |
| Task board | linear.app/stpierre |
| Cindie S. media page (format reference) | sp-lead-media…/cindie-stewart |
Key identifiers (for the executing agent)
GHL location6c4MSgjdfngrPhwWD2cb · GHL line +16263250175 · Akash transfer line +14143085477 (contact Zxl4oup63VOYbmKyXqNG) · Level King FB page 1308868515634109 · client row 5cb23431-b9d7-4b9a-87bf-c339058cad3f · drain wf 6ET0nCwUKY35WBwR · poller wf lB6TcayZVpR4yuey · bridge wf jTTms4dkSsa60TfI · watchdog wf UPyCozsmQozGVK55 · Handoff→ *3 (210bddd6) · qualified-webhook wf 47e4e83e · runbooks in 01-Projects/St_Pierre_Digital/lead-transfer-automation/ + lead-transfer-watchdog/.