← Back to Work
Dead Lead Reactivation[AGENT][AUTOMATION][REAL BUILD SHOWN]

Ember

Reactivation Agent

An n8n agent that finds the cold GoHighLevel leads worth re-approaching, drafts a personal message for each, and waits for a person to approve every send.

My role
Designed and built the agent end to end: scoring logic, n8n workflow, GPT-4o-mini drafting, and the Telegram approval gate.
Built with
n8n, GoHighLevel, OpenAI API, Telegram
Status
Built and running

Before

The problem

Cold CRM leads were revisited on no consistent schedule, or not at all. A flat contact list gave no way to tell a lead worth trying from one too recent or too far gone, and hand-writing a message for each one didn't scale.

The system

How the pieces connect

  1. GoHighLevelFetch cold contacts
  2. n8nFilter and score
  3. GPT-4o-miniDraft a message
  4. TelegramHuman approval
  5. GoHighLevelSend and tag

Proof

The real build

Real buildn8n workflow
The real workflow. Contacts are normalized and scored, then each qualifying lead loops through its own draft and Telegram approval. Approve sends (SMS is mocked until Twilio is connected) and tags the contact. Decline is logged.
n8n workflow · original resolution

Step by step

How it works

  1. 1Pulls cold contacts from GoHighLevel and drops anyone on Do Not Disturb, without an enquiry note, or already attempted.
  2. 2Scores the rest on recency, enquiry note, and tag richness. Only a score of 6 or more moves on.
  3. 3Drafts a personal message for each qualifying lead with GPT-4o-mini.
  4. 4Sends each draft to Telegram with the score and signals, one lead at a time.
  5. 5Approve sends it and tags the contact as attempted. Decline logs and stops.
Activity Log — Ember
SCANNING
Planned — not yet built
WAITmonitoring for an inbound reply
REPLYinbound message received
CLASSIFYintent label + confidence score
ESCALATEhanded to owner for follow-up

SIM: reconstructed from the real workflow with placeholder demo contacts, not live leads. Trigger, scan, score, draft, the Telegram approval gate, and the tag write-back are all real. The SMS send is mocked (Twilio pending), and reply monitoring is planned, not built.

Control

Human in the loop

Nothing is sent until a person taps Approve in Telegram. Every lead gets its own approval, never a bulk send, and the reviewer sees the score and the reasons behind it before deciding.

After

What changed

› A human approves or declines every message individually before it goes out — the agent drafts, it never sends unsupervised.
› Already-attempted leads are excluded automatically, so re-running the agent can't double-message someone.
› Caught and fixed a subtler bug during testing: scoring originally used the CRM's generic "last updated" timestamp as a stand-in for "last talked to," which broke the moment an unrelated field got edited for reasons that had nothing to do with outreach. Scoring no longer depends on that timestamp.
› No performance numbers here — this runs against a demo CRM with placeholder contacts, not live leads. The scoring logic, the approval gate, and the tag write-back are all real; what's mocked is named explicitly in the caption under the activity log.

Stack

For technical readers

Technical detail

The problem, in detail

Cold leads that go quiet in a CRM either get manually revisited on no consistent schedule, or never get revisited at all — there's no systematic way to decide which of dozens of stale contacts are actually worth a human's time to re-approach.

› A lead who went quiet 3 weeks ago and one who went quiet 8 months ago look identical in a flat contact list — nothing separates "still worth trying" from "too recent to bother" or "too far gone."
› Manually drafting a personalized re-engagement message for every stale lead doesn't scale, and a generic blast reads as spam.

How it decides

Recency bucket+1 to +1072 days since last contact → +10 (60–180d is the reactivation sweet spot)
Has a real enquiry note+2"Asked about a 2BR near downtown…" on file → +2
CRM tag richness+1 to +3 (capped)2 tags on record (e.g. "high priority", "warm lead") → +2
Qualifying thresholdscore ≥ 6Below 6 → not drafted this run

Only leads that clear the scoring threshold reach the gate — everything else is filtered out before a draft is ever written.

› What queues: a GPT-4o-mini draft addressed to one specific lead, paired with that lead's score and the signals behind it.
› What the human sees: a Telegram message with the lead's name, score, days cold, original enquiry, and the full draft text — with Approve and Decline buttons.
› What approval releases: the (mocked) SMS send, followed by a tag write-back marking the lead as attempted. Decline logs the rejection and stops there — nothing else fires.

Build notes

An n8n agent that fetches cold GoHighLevel contacts, scores them, drafts outreach, and holds for approval — one lead per gate, not a mass send.

› Fetches and normalizes GoHighLevel contacts, filtering out anyone on Do Not Disturb, anyone missing a real enquiry note to personalize from, and anyone already tagged from a previous reactivation attempt.
› Scores the remainder on three signals — see "How it decides" below — and only drafts outreach for leads that clear the bar.
› Each qualifying lead gets its own GPT-4o-mini draft and its own Telegram approval message with the score and signals attached. A Split In Batches loop fixed a real bug found during testing: the approval node only ever resumed for the first item in a multi-lead batch, silently dropping every lead after it.
› Approving sends the (currently mocked) SMS and writes a `reactivation-attempted` tag back to the contact, so the same lead can't re-enter a future run. Declining logs the decision and stops — nothing else fires.