ProjectsBlueprint · n8n / lead-triage

Lead triage: one form, three outcomes

A lead arrives from a website form. A model reads the message and scores its intent and urgency. A few lines of code turn that score into one of three tiers, and a switch sends each tier down its own branch: hot books a slot and alerts the owner, warm starts a nurture, junk goes to its own branch. The page that sent the lead gets an answer either way.

Demo Built to prove the mechanism, not run for a paying client. The workflow below is the real export; credentials and personal details are removed and nothing else is.

nodes
12
tiers
3
model call
1
score fields
4
reply node
1
  • n8n
  • OpenAI gpt-4o-mini
  • GoHighLevel API
  • Gmail
The flow

How it works

A form posts the lead to a webhook. The lead is cleaned and capped. A lead with no usable email or phone number goes straight to junk without a model call; every other lead goes to the model with one instruction: judge only from the message. The model answers in a fixed shape. Code reads that answer and picks hot, warm or junk. A switch with three outputs sends the lead down its branch, and all three branches meet again at the one node that answers the form.

Every box is a node in the workflow, with its real name. Drag to pan, scroll or pinch to zoom.
Open the diagram full size
The core idea

The model scores. Code decides.

The model never picks the branch. It returns four fields under a strict JSON schema, at temperature 0, and three lines of plain code turn those fields into a tier. Want urgency 3 to count as hot? That is one number in one line of code, not a rewritten prompt and a week of hoping the model reads it the same way every time.

FieldTypeWhat the model is told
intentone of: emergency, quote, question, spamWhat the person wants
urgencywhole number, 1 to 55 means active damage or danger, 1 means idle curiosity
is_junktrue or falseTrue for spam, sales pitches and gibberish
reasontextOne line on why, which ends up in the owner alert
What it handles

Five things that would otherwise go wrong

Handled

A lead nobody can contact never reaches the model

Has Contact? sits straight after Clean And Validate. A lead with no name, or with neither a real looking email nor a phone number, goes to Log And Drop without a model call. Nobody pays to score a lead that cannot be answered, and an urgent sounding message with no way to reply can never book a slot.

Handled

A model outage turns into a warm lead, not a lost one

Score The Intent is set to continue on error. If the model API is down or times out, the run carries on, Decide The Tier finds no reply it can read, and the lead lands in the warm nurture. The form still gets its answer.

Handled

An unreadable model reply still lands somewhere

Decide The Tier parses the model's reply inside a try. If the reply cannot be read, the score falls back to a question with urgency 2, which is warm. A real lead goes into the nurture instead of disappearing.

Handled

The model only ever sees a bounded input

Before the model call, the name is cut to 80 characters, the message to 2,000, the phone number to digits and a plus sign, and the email is lowercased. One pasted essay cannot run up the bill, and every lead costs about the same to score.

Handled

Every branch answers the form

Hot, warm and junk all end at the same Respond to Webhook node, which returns ok and the tier. The person who filled in the form is never left looking at a spinner because their lead took the junk branch.

Node by node

The twelve nodes, in four steps

  1. Take the lead in and clean it

    Lead Submitted is a webhook that accepts a POST and is set to answer through a Respond to Webhook node, so the form waits for the real result. Clean And Validate trims every field, applies the caps above, and checks that there is a name and either an email that looks real or a phone number. Has Contact? is an IF node: a lead that failed that check goes straight to Log And Drop, and every other lead goes on to be scored.

  2. Score the message

    Score The Intent sends the name and the message to gpt-4o-mini with a short system instruction: you triage inbound leads for a service business, judge only from the message, never invent detail. The request asks for the four fields above as strict JSON, at temperature 0, capped at 200 tokens. The node is set to continue on error, so an outage never stops the run.

  3. Decide the tier, then route it

    Decide The Tier applies the rules below in order. Route By Tier is a switch with three named outputs that matches the tier exactly.

    If the score saysThe tier is
    is_junk is true, or intent is spamjunk
    urgency is 4 or 5, or intent is emergencyhot
    anything else, including a reply that could not be readwarm
  4. Act on it, then answer the form

    TierNodesWhat happens
    HotBook The Slot, Alert The OwnerA booking request goes to the GoHighLevel calendar API with the contact, the urgency and the reason. Then the owner gets an email headed "HOT lead", with the intent, urgency, the reason and the full message.
    WarmStart The Nurture, Tag For Follow UpA contact is created in GoHighLevel tagged warm plus the intent, and the lead is marked for follow up 24 hours later.
    JunkLog And DropReached two ways: from the switch, or straight from Has Contact? for a lead with no contact details. Only the reason, the name, the message and a timestamp are kept. Nothing is sent to anyone.
    All threeTell The Page It WorkedReturns ok and the tier to the form that sent the lead.
Gotchas

Three n8n details that are easy to get wrong

Gotcha

The webhook has to be told to wait

A Respond to Webhook node only answers if the webhook's Respond setting says to use it. Leave the webhook on its default and n8n answers the form straight away, before the lead has even been scored, so the form never learns the tier.

Gotcha

After an API call, $json is the API's answer

Once Book The Slot has run, the item flowing on is the calendar API's response, not the lead. So Alert The Owner reads the name, urgency and message from $("Decide The Tier") by name. Point it at $json and the alert arrives with the fields empty.

Gotcha

Strict JSON can still arrive broken

A strict schema fixes the shape of the answer, but a reply that hits the 200 token cap is cut off mid-object and will not parse. That is why the parse sits in a try with a warm fallback rather than being trusted.

Known limits

What this demo does not do yet

Labelled honestly, the same as everything else on this site. A client build closes both.

Limit

The CRM calls carry the contact, not the account

Book The Slot and Start The Nurture send the contact's details only. A real build adds the calendar, the time slot, the GoHighLevel location and the API credential for the client's own account.

Limit

Junk is kept on the run, not in a table

Log And Drop shapes a record, but nothing writes it anywhere. It survives as long as n8n keeps the execution. A real build writes it to a sheet or a table, so a lead wrongly scored as junk can be found and rescued.

Make it yours

What changes for a real business

  • What counts as hot. One line in Decide The Tier. A plumber's emergency and a law firm's emergency are not the same word.
  • The categories. The intent list is four words in the schema. Swap them for the jobs you actually quote.
  • Where hot goes. Gmail is one node. It can just as well be a text message, a Slack channel or a call task.
  • The CRM. The two GoHighLevel calls are plain HTTP requests, so HubSpot or any CRM with an API takes their place.
Before it goes live

Build checklist

  1. The webhook answers through the Respond to Webhook node, not straight away.
  2. Test leads for all three tiers, plus one with no contact details, one with a broken model reply, and one with the model unreachable.
  3. The model credential, the CRM credential and the alert inbox are the client's own.
  4. The hot threshold and the intent list are agreed with whoever answers the phone.
  5. Junk lands somewhere a person can look at it.
  6. The form shows a sensible message for every tier, and for an error.

Want this on your own enquiries?

Thirty minutes, no deck. If I cannot see anything worth automating, I will tell you that.