Cookbook
Enrich Lead Data
Enrich, score, and route leads with external APIs and an LLM.
Capture a lead, enrich it with company and person data from Clearbit, score it with Claude, and route it to sales, nurture, or archive.
form new_lead {
label: "New Lead"
enabled: true
schema: @json {
{
"type": "object",
"required": ["email"],
"properties": {
"email": { "type": "string", "title": "Email" },
"company": { "type": "string", "title": "Company" },
"source": { "type": "string", "title": "Source", "enum": ["website", "event", "referral", "other"] },
"notes": { "type": "string", "title": "Notes" }
},
"additionalProperties": false
}
}
}
graph enrich_and_score {
label: "Enrich and Score Lead"
description: "Enrich with external data, score with LLM, route by priority"
persistence {
enabled: true
condition: @ts {
return true
}
}
root {
type: code
label: "Normalize"
inputSchema: @json {
{
"type": "object",
"required": ["email"],
"properties": {
"email": { "type": "string" },
"company": { "type": "string" },
"source": { "type": "string" },
"notes": { "type": "string" }
},
"additionalProperties": false
}
}
outputSchema: @json {
{
"type": "object",
"required": ["email", "company", "source", "notes"],
"properties": {
"email": { "type": "string" },
"company": { "type": "string" },
"source": { "type": "string" },
"notes": { "type": "string" }
},
"additionalProperties": false
}
}
code: @ts {
const { email, company, source, notes } = context.nodes.root.input
return {
email: email.toLowerCase().trim(),
company: (company ?? "").trim(),
source: source ?? "other",
notes: (notes ?? "").trim(),
}
}
}
node enrich_company {
type: http
label: "Enrich company"
method: "GET"
url: @ts {
const domain = context.nodes.root.output.email.split("@")[1]
return `https://api.clearbit.com/v2/companies/find?domain=${domain}`
}
headers: {
"Authorization": @ts { return `Bearer ${context.secrets.CLEARBIT_API_KEY}` }
}
schema: @json {
{
"type": "object",
"properties": {
"name": { "type": "string" },
"industry": { "type": "string" },
"employeesRange": { "type": "string" },
"annualRevenue": { "type": "number" }
}
}
}
}
node enrich_person {
type: http
label: "Enrich person"
method: "GET"
url: @ts {
return `https://api.clearbit.com/v2/people/find?email=${context.nodes.root.output.email}`
}
headers: {
"Authorization": @ts { return `Bearer ${context.secrets.CLEARBIT_API_KEY}` }
}
schema: @json {
{
"type": "object",
"properties": {
"fullName": { "type": "string" },
"title": { "type": "string" },
"seniority": { "type": "string" }
}
}
}
}
node score {
type: ai
label: "Score lead"
kind: object
model: "anthropic/claude-3.5-sonnet"
temperature: 0.2
prompt: @ts {
const { email, company, source, notes } = context.nodes.root.output
const companyData = JSON.stringify(context.nodes.enrich_company?.output ?? {})
const personData = JSON.stringify(context.nodes.enrich_person?.output ?? {})
return `Score this lead from 0-100 based on fit.
Email: ${email}
Company: ${company}
Source: ${source}
Notes: ${notes}
Company data: ${companyData}
Person data: ${personData}
Return JSON with:
- "score": integer 0-100
- "reasoning": one sentence
- "intent_summary": brief buying intent summary`
}
schema: @json {
{
"type": "object",
"required": ["score", "reasoning", "intent_summary"],
"properties": {
"score": { "type": "number" },
"reasoning": { "type": "string" },
"intent_summary": { "type": "string" }
}
}
}
}
node route {
type: switch
label: "Route by score"
cases: ["high", "medium", "low"]
router: @ts {
const score = context.nodes.score.output.score ?? 0
if (score > 70) return "high"
if (score > 40) return "medium"
return "low"
}
}
node notify_sales {
type: email
label: "Notify sales"
from: @ts { return "[email protected]" }
to: @ts { return "[email protected]" }
subject: @ts {
return `High-priority lead: ${context.nodes.root.output.email} (score: ${context.nodes.score.output.score})`
}
text: @ts {
const { email, company } = context.nodes.root.output
const { score, reasoning, intent_summary } = context.nodes.score.output
return `New high-priority lead:
Email: ${email}
Company: ${company}
Score: ${score}/100
Reasoning: ${reasoning}
Intent: ${intent_summary}`
}
}
node add_to_nurture {
type: http
label: "Add to nurture"
method: "POST"
url: @ts { return "https://api.example.com/nurture/enroll" }
headers: { "Authorization": @ts { return `Bearer ${context.secrets.CRM_API_KEY}` } }
body: @ts {
return JSON.stringify({
email: context.nodes.root.output.email,
score: context.nodes.score.output.score,
campaign: "medium-intent"
})
}
}
node archive {
type: code
label: "Archive"
outputSchema: @json {
{ "type": "object", "required": ["status"], "properties": { "status": { "type": "string" } }, "additionalProperties": false }
}
code: @ts {
return { status: "archived" }
}
}
flow {
root -> enrich_company
root -> enrich_person
enrich_company -> score
enrich_person -> score
score -> route
route -["high"]-> notify_sales
route -["medium"]-> add_to_nurture
route -["low"]-> archive
}
}
trigger on_new_lead {
form:new_lead -> enrich_and_score
enabled: true
}How it works
- The form (or a webhook trigger) captures the lead and passes it to the root node, which normalizes the email and trims whitespace.
enrich_companyandenrich_personrun in parallel. Both HTTP nodes call Clearbit using the lead's email domain and address. The DAG executes them concurrently and waits for both before continuing.- The
scoreAI node receives all four inputs: the normalized lead fields plus both enrichment payloads. It returns a score (0-100), one-sentence reasoning, and a buying intent summary. - The
routeswitch node reads the score and branches: above 70 is high, above 40 is medium, and the rest is low. - High-priority leads trigger an email to the sales team with the score and intent summary.
- Medium-priority leads are enrolled in a nurture campaign via a POST to your CRM API.
- Low-priority leads are archived with a status record.
- Persistence is enabled unconditionally, so every score and routing decision is stored for conversion analysis.
Run it
swirls deploy
swirls trigger on_new_lead --data '{"email": "[email protected]", "company": "Acme", "source": "website"}'Customize
- Swap Clearbit for Apollo, ZoomInfo, or another enrichment provider by updating the
urlandheaderson the two HTTP nodes. - Add a review step on the high branch so a sales manager approves leads before the email fires.
- Query the persistence stream to track scoring accuracy and refine score thresholds over time.
- Add a schedule trigger to re-score medium leads monthly with fresh enrichment data.
- Add a webhook trigger for CRM integrations so leads flow in automatically without the form.