Skip to content

In n8n, copy this file and paste it onto the canvas, or use Import from File.

{
  "name": "18 - Lead Enrichment and Scoring",
  "nodes": [
    {
      "parameters": {
        "httpMethod": "POST",
        "path": "leads/enrich",
        "responseMode": "responseNode",
        "options": {}
      },
      "id": "m18-webhook",
      "name": "Enrich Lead",
      "type": "n8n-nodes-base.webhook",
      "typeVersion": 2,
      "position": [
        0,
        200
      ],
      "webhookId": "course-18-enrich"
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "s1",
              "name": "offer",
              "value": "We build automation and AI follow-up systems (online booking, reminders, lead follow-up) for local service businesses: clinics, dentists, law firms, home services and real estate teams.",
              "type": "string"
            },
            {
              "id": "s2",
              "name": "icp",
              "type": "object",
              "value": "={{ {\n  industries: {\n    'healthcare-clinics': ['dental', 'dentist', 'clinic', 'medical', 'chiropract', 'physio'],\n    'home-services': ['plumb', 'roofing', 'hvac', 'electrician', 'landscap', 'cleaning service'],\n    'legal': ['law firm', 'attorney', 'lawyer', 'legal services'],\n    'real-estate': ['real estate', 'realtor', 'property management', 'homes for sale'],\n    'ecommerce': ['online store', 'shop now', 'free shipping', 'add to cart'],\n    'saas': ['software', 'saas', 'platform', 'api']\n  },\n  target_industries: ['healthcare-clinics', 'home-services', 'legal', 'real-estate'],\n  good_tech: ['wordpress', 'hubspot', 'google_analytics', 'wix', 'squarespace']\n} }}"
            },
            {
              "id": "s3",
              "name": "hot_threshold",
              "value": 70,
              "type": "number"
            },
            {
              "id": "s4",
              "name": "warm_threshold",
              "value": 40,
              "type": "number"
            },
            {
              "id": "s5",
              "name": "sheet_url",
              "value": "https://docs.google.com/spreadsheets/d/YOUR_SHEET_ID/edit",
              "type": "string"
            }
          ]
        },
        "includeOtherFields": true,
        "options": {}
      },
      "id": "m18-settings",
      "name": "Settings (edit me)",
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        220,
        200
      ]
    },
    {
      "parameters": {
        "jsCode": "// Clean the incoming lead and work out which website to look at.\nconst FREE_MAIL = ['gmail.com', 'yahoo.com', 'hotmail.com', 'outlook.com', 'icloud.com', 'aol.com', 'proton.me', 'protonmail.com', 'live.com', 'msn.com'];\nconst req = $('Enrich Lead').first().json;\nconst b = req.body ?? {};\n\nconst email = String(b.email ?? '').trim().toLowerCase();\nconst emailDomain = email.includes('@') ? email.split('@')[1] : '';\nconst freeEmail = FREE_MAIL.includes(emailDomain);\n\nlet website = String(b.website ?? '').trim();\nif (!website && emailDomain && !freeEmail) website = emailDomain; // business email → guess the site\nif (website && !/^https?:\\/\\//i.test(website)) website = 'https://' + website;\n\n// Hostname without www (a regex: the Code sandbox may not provide URL())\nconst domain = website ? website.replace(/^https?:\\/\\//i, '').split(/[/:?#]/)[0].replace(/^www\\./, '').toLowerCase() : '';\n\nreturn [{\n  json: {\n    email,\n    name: String(b.name ?? '').trim(),\n    company: String(b.company ?? '').trim(),\n    website,\n    domain: domain || (freeEmail ? '' : emailDomain),\n    free_email: freeEmail,\n    valid: Boolean(email || website),\n    dry_run: req.query?.dry_run === 'true',\n    use_ai: req.query?.dry_run !== 'true' && req.query?.ai !== 'false',\n  },\n}];\n"
      },
      "id": "m18-normalize",
      "name": "Normalize Lead",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        440,
        200
      ]
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "strict",
            "version": 2
          },
          "conditions": [
            {
              "id": "v1",
              "leftValue": "={{ $json.valid }}",
              "rightValue": "",
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "m18-valid",
      "name": "Valid?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        660,
        200
      ]
    },
    {
      "parameters": {
        "respondWith": "json",
        "responseBody": "={{ { error: 'send at least an email or a website: {\"email\": \"...\", \"website\": \"...\", \"name\": \"...\", \"company\": \"...\"}' } }}",
        "options": {
          "responseCode": 400
        }
      },
      "id": "m18-400",
      "name": "Respond Invalid",
      "type": "n8n-nodes-base.respondToWebhook",
      "typeVersion": 1.1,
      "position": [
        880,
        400
      ]
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "strict",
            "version": 2
          },
          "conditions": [
            {
              "id": "w1",
              "leftValue": "={{ $json.website }}",
              "rightValue": "",
              "operator": {
                "type": "string",
                "operation": "notEmpty",
                "singleValue": true
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "m18-hasweb",
      "name": "Has Website?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        880,
        120
      ]
    },
    {
      "parameters": {
        "url": "={{ $json.website }}",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "User-Agent",
              "value": "Mozilla/5.0 (compatible; lead-enrichment/1.0)"
            }
          ]
        },
        "options": {
          "response": {
            "response": {
              "fullResponse": true,
              "neverError": true,
              "responseFormat": "text"
            }
          },
          "timeout": 10000,
          "redirect": {
            "redirect": {
              "maxRedirects": 5
            }
          }
        }
      },
      "id": "m18-fetch",
      "name": "Fetch Website",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        1100,
        40
      ],
      "onError": "continueRegularOutput"
    },
    {
      "parameters": {
        "jsCode": "// Read the homepage HTML and pull out buying signals. No AI, no paid data provider.\nconst lead = $('Normalize Lead').first().json;\nconst fetched = $('Fetch Website').isExecuted ? $('Fetch Website').first().json : null;\nconst status = fetched?.statusCode ?? null;\nconst html = typeof fetched?.body === 'string' ? fetched.body : typeof fetched?.data === 'string' ? fetched.data : '';\nconst siteOk = Boolean(html) && status !== null && status < 400;\n\nconst grab = (re) => (html.match(re)?.[1] ?? '').replace(/\\s+/g, ' ').trim();\nconst text = html.replace(/<script[\\s\\S]*?<\\/script>|<style[\\s\\S]*?<\\/style>/gi, ' ').replace(/<[^>]+>/g, ' ').replace(/&[a-z#0-9]+;/gi, ' ').replace(/\\s+/g, ' ').trim();\nconst lower = (html + ' ' + text).toLowerCase();\nconst uniq = (a) => [...new Set(a)];\n\nconst TECH = {\n  wordpress: /wp-content|wp-includes/, shopify: /cdn\\.shopify\\.com|myshopify\\.com/, hubspot: /js\\.hs-scripts\\.com|hs-analytics/,\n  google_analytics: /googletagmanager\\.com|google-analytics\\.com/, wix: /wix\\.com|wixstatic/, squarespace: /squarespace/, webflow: /webflow/,\n  intercom: /intercom/, stripe: /js\\.stripe\\.com/,\n};\nconst BOOKING = /calendly\\.com|acuityscheduling|cal\\.com\\/|booksy|squareup\\.com\\/appointments|zocdoc/;\n\nconst signals = siteOk ? {\n  title: grab(/<title[^>]*>([\\s\\S]*?)<\\/title>/i),\n  description: grab(/<meta[^>]+name=[\"']description[\"'][^>]+content=[\"']([^\"']*)[\"']/i),\n  headline: grab(/<h1[^>]*>([\\s\\S]*?)<\\/h1>/i).replace(/<[^>]+>/g, ''),\n  emails: uniq((text.match(/[\\w.+-]+@[\\w-]+\\.[\\w.-]+/g) ?? []).map((e) => e.toLowerCase())).slice(0, 5),\n  phones: uniq(text.match(/(?:\\+?\\d{1,2}[\\s.-]?)?\\(?\\d{3}\\)?[\\s.-]\\d{3}[\\s.-]\\d{4}/g) ?? []).slice(0, 3),\n  socials: uniq((html.match(/https?:\\/\\/(?:www\\.)?(?:linkedin\\.com|facebook\\.com|instagram\\.com|x\\.com|twitter\\.com|youtube\\.com)\\/[^\\s\"'<>]+/gi) ?? [])),\n  tech: Object.entries(TECH).filter(([, re]) => re.test(lower)).map(([k]) => k),\n  has_booking: BOOKING.test(lower),\n  hiring: /we['’]re hiring|careers|join our team|open positions/.test(lower),\n  location: grab(/<address[^>]*>([\\s\\S]*?)<\\/address>/i) || (text.match(/[A-Z][a-zA-Z]+,\\s?[A-Z]{2}\\s\\d{5}/)?.[0] ?? ''),\n} : null;\n\nreturn [{\n  json: {\n    ...lead,\n    site_ok: siteOk,\n    site_status: status,\n    site_error: siteOk ? null : fetched?.error?.message ?? fetched?.error ?? (lead.website ? `HTTP ${status ?? 'no response'}` : 'no website to check'),\n    signals,\n    page_text: siteOk ? text.slice(0, 4000) : '',\n  },\n}];\n"
      },
      "id": "m18-extract",
      "name": "Extract Signals",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1320,
        120
      ]
    },
    {
      "parameters": {
        "jsCode": "// Score the lead against your ideal customer profile (ICP) with transparent points.\nconst icp = $('Settings (edit me)').first().json.icp;\nconst lead = $input.first().json;\nconst sg = lead.signals;\nconst reasons = [];\nlet score = 0;\nconst add = (pts, why) => { score += pts; reasons.push(`+${pts} ${why}`); };\n\n// Industry: first keyword match in title/description/headline/page text\nconst hay = sg ? `${sg.title} ${sg.description} ${sg.headline} ${lead.page_text}`.toLowerCase() : '';\nconst industry = Object.entries(icp.industries).find(([, words]) => words.some((w) => hay.includes(w)))?.[0] ?? null;\n\nif (industry && icp.target_industries.includes(industry)) add(25, `target industry (${industry})`);\nif (!lead.free_email && lead.email) add(15, 'business email');\nif (lead.site_ok) add(10, 'website reachable');\nif (sg?.has_booking) add(10, 'uses online booking');\nif (sg?.hiring) add(10, 'hiring (growing)');\nconst goodTech = (sg?.tech ?? []).filter((t) => icp.good_tech.includes(t));\nif (goodTech.length) add(10, `tech fit (${goodTech.join(', ')})`);\nif (sg && (sg.emails.length || sg.phones.length)) add(10, 'contact details on site');\nif (sg?.socials.length) add(10, 'social presence');\n\nreturn [{ json: { ...lead, industry, rule_score: Math.min(score, 100), rule_reasons: reasons } }];\n"
      },
      "id": "m18-score",
      "name": "Rule Score",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1540,
        120
      ]
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "strict",
            "version": 2
          },
          "conditions": [
            {
              "id": "a1",
              "leftValue": "={{ $json.use_ai && $json.site_ok }}",
              "rightValue": "",
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "m18-useai",
      "name": "Use AI?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        1760,
        120
      ]
    },
    {
      "parameters": {
        "text": "=OUR OFFER:\n{{ $('Settings (edit me)').first().json.offer }}\n\nLEAD: {{ $json.name || 'unknown' }} <{{ $json.email || 'no email' }}>\nCOMPANY WEBSITE ({{ $json.domain }}):\nTitle: {{ $json.signals.title }}\nDescription: {{ $json.signals.description }}\nTech: {{ $json.signals.tech.join(', ') || 'none detected' }} | Online booking: {{ $json.signals.has_booking }} | Hiring: {{ $json.signals.hiring }}\nPage text: {{ $json.page_text }}",
        "attributes": {
          "attributes": [
            {
              "name": "summary",
              "description": "One sentence: what this company does and for whom",
              "required": true
            },
            {
              "name": "industry",
              "description": "Short industry label, e.g. 'dental clinic', 'B2B SaaS', 'personal blog'",
              "required": true
            },
            {
              "name": "size_guess",
              "description": "One of: solo, small (2-20), medium (21-200), large (200+), unknown",
              "required": true
            },
            {
              "name": "fit_score",
              "type": "number",
              "description": "0-100: how well this company matches OUR OFFER's ideal customer. 80+ = clearly the target market with a visible need.",
              "required": true
            },
            {
              "name": "pitch_angle",
              "description": "One sentence a salesperson could open with, tied to something specific on their website",
              "required": true
            }
          ]
        },
        "options": {
          "systemPromptTemplate": "You research B2B leads from their website. Use only the information provided; never invent facts about the company."
        }
      },
      "id": "m18-ai",
      "name": "AI Profile",
      "type": "@n8n/n8n-nodes-langchain.informationExtractor",
      "typeVersion": 1.2,
      "position": [
        1980,
        40
      ],
      "onError": "continueRegularOutput"
    },
    {
      "parameters": {
        "model": {
          "__rl": true,
          "mode": "list",
          "value": "gpt-5-mini",
          "cachedResultName": "gpt-5-mini"
        },
        "options": {}
      },
      "id": "m18-model",
      "name": "OpenAI Chat Model",
      "type": "@n8n/n8n-nodes-langchain.lmChatOpenAi",
      "typeVersion": 1.3,
      "position": [
        1980,
        260
      ]
    },
    {
      "parameters": {
        "jsCode": "// Final record: rules alone, or 50/50 with the AI fit score when AI succeeded.\nconst s = $('Settings (edit me)').first().json;\nconst lead = $('Rule Score').first().json;\nconst aiItem = $('AI Profile').isExecuted ? $input.first().json : null;\nconst ai = aiItem && !aiItem.error && aiItem.output ? aiItem.output : null;\nconst aiScore = ai ? Math.max(0, Math.min(100, Math.round(Number(ai.fit_score) || 0))) : null;\nconst score = ai ? Math.round(0.5 * lead.rule_score + 0.5 * aiScore) : lead.rule_score;\nconst tier = score >= s.hot_threshold ? 'hot' : score >= s.warm_threshold ? 'warm' : 'cold';\nconst sg = lead.signals ?? {};\n\nreturn [{\n  json: {\n    email: lead.email,\n    name: lead.name,\n    // Given name → site title → business email domain → site domain\n    company: lead.company || (lead.site_ok && sg.title ? sg.title.split(/[|–-]/)[0].trim() : '') || (!lead.free_email && lead.email ? lead.email.split('@')[1] : '') || lead.domain,\n    domain: lead.domain,\n    website: lead.website,\n    industry: ai?.industry || lead.industry,\n    score,\n    tier,\n    rule_score: lead.rule_score,\n    ai_score: aiScore,\n    method: ai ? 'rules+ai' : 'rules',\n    ai_error: aiItem && !ai ? (typeof aiItem.error === 'string' ? aiItem.error : aiItem.error?.message ?? 'AI step returned no output (check the OpenAI credential)') : null,\n    reasons: lead.rule_reasons,\n    summary: ai?.summary ?? (sg.description || null),\n    pitch_angle: ai?.pitch_angle ?? null,\n    signals: {\n      tech: sg.tech ?? [], has_booking: sg.has_booking ?? false, hiring: sg.hiring ?? false,\n      emails: sg.emails ?? [], phones: sg.phones ?? [], socials: sg.socials ?? [], location: sg.location ?? '',\n    },\n    site_ok: lead.site_ok,\n    site_error: lead.site_error,\n    dry_run: lead.dry_run,\n    enriched_at: DateTime.now().toISO(),\n  },\n}];\n"
      },
      "id": "m18-combine",
      "name": "Combine",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        2200,
        120
      ]
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "strict",
            "version": 2
          },
          "conditions": [
            {
              "id": "d1",
              "leftValue": "={{ $json.dry_run }}",
              "rightValue": "",
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "m18-dry",
      "name": "Dry Run?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        2420,
        120
      ]
    },
    {
      "parameters": {
        "respondWith": "json",
        "responseBody": "={{ $json }}",
        "options": {}
      },
      "id": "m18-respond-dry",
      "name": "Respond Enriched",
      "type": "n8n-nodes-base.respondToWebhook",
      "typeVersion": 1.1,
      "position": [
        2640,
        0
      ]
    },
    {
      "parameters": {
        "operation": "appendOrUpdate",
        "documentId": {
          "__rl": true,
          "mode": "url",
          "value": "={{ $('Settings (edit me)').first().json.sheet_url }}"
        },
        "sheetName": {
          "__rl": true,
          "mode": "name",
          "value": "Enriched Leads"
        },
        "columns": {
          "mappingMode": "defineBelow",
          "value": {
            "email": "={{ $json.email }}",
            "name": "={{ $json.name }}",
            "company": "={{ $json.company }}",
            "domain": "={{ $json.domain }}",
            "industry": "={{ $json.industry }}",
            "score": "={{ $json.score }}",
            "tier": "={{ $json.tier }}",
            "summary": "={{ $json.summary }}",
            "pitch_angle": "={{ $json.pitch_angle }}",
            "signals": "={{ $json.reasons.join('; ') }}",
            "enriched_at": "={{ $json.enriched_at }}"
          },
          "matchingColumns": [
            "email"
          ],
          "schema": [
            {
              "id": "email",
              "displayName": "email",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "name",
              "displayName": "name",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "company",
              "displayName": "company",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "domain",
              "displayName": "domain",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "industry",
              "displayName": "industry",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "score",
              "displayName": "score",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "tier",
              "displayName": "tier",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "summary",
              "displayName": "summary",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "pitch_angle",
              "displayName": "pitch_angle",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "signals",
              "displayName": "signals",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "enriched_at",
              "displayName": "enriched_at",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            }
          ]
        },
        "options": {}
      },
      "id": "m18-sheet",
      "name": "Save to Sheet",
      "type": "n8n-nodes-base.googleSheets",
      "typeVersion": 4.7,
      "position": [
        2640,
        220
      ],
      "onError": "continueRegularOutput"
    },
    {
      "parameters": {
        "jsCode": "// The Sheets step runs with \"On Error: Continue\"; saved only if the row came back with our email.\nconst lead = $('Combine').first().json;\nconst r = $input.first().json;\nconst saved = !r.error && r.email === lead.email;\nreturn [{\n  json: {\n    ...lead,\n    saved,\n    save_error: saved ? null : (typeof r.error === 'string' ? r.error : r.error?.message) ?? 'no row returned (check the Google Sheets credential and sheet URL)',\n  },\n}];\n"
      },
      "id": "m18-save",
      "name": "Save Result",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        2860,
        220
      ]
    },
    {
      "parameters": {
        "respondWith": "json",
        "responseBody": "={{ $json }}",
        "options": {}
      },
      "id": "m18-respond",
      "name": "Respond Saved",
      "type": "n8n-nodes-base.respondToWebhook",
      "typeVersion": 1.1,
      "position": [
        3080,
        140
      ]
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "strict",
            "version": 2
          },
          "conditions": [
            {
              "id": "h1",
              "leftValue": "={{ $json.tier }}",
              "rightValue": "hot",
              "operator": {
                "type": "string",
                "operation": "equals"
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "m18-hot",
      "name": "Hot Lead?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        3080,
        320
      ]
    },
    {
      "parameters": {},
      "id": "m18-alert",
      "name": "Alert Sales (Exercise 1)",
      "type": "n8n-nodes-base.noOp",
      "typeVersion": 1,
      "position": [
        3300,
        320
      ]
    }
  ],
  "connections": {
    "Enrich Lead": {
      "main": [
        [
          {
            "node": "Settings (edit me)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Settings (edit me)": {
      "main": [
        [
          {
            "node": "Normalize Lead",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Normalize Lead": {
      "main": [
        [
          {
            "node": "Valid?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Valid?": {
      "main": [
        [
          {
            "node": "Has Website?",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Respond Invalid",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Has Website?": {
      "main": [
        [
          {
            "node": "Fetch Website",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Extract Signals",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Fetch Website": {
      "main": [
        [
          {
            "node": "Extract Signals",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Extract Signals": {
      "main": [
        [
          {
            "node": "Rule Score",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Rule Score": {
      "main": [
        [
          {
            "node": "Use AI?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Use AI?": {
      "main": [
        [
          {
            "node": "AI Profile",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Combine",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "OpenAI Chat Model": {
      "ai_languageModel": [
        [
          {
            "node": "AI Profile",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    },
    "AI Profile": {
      "main": [
        [
          {
            "node": "Combine",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Combine": {
      "main": [
        [
          {
            "node": "Dry Run?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Dry Run?": {
      "main": [
        [
          {
            "node": "Respond Enriched",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Save to Sheet",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Save to Sheet": {
      "main": [
        [
          {
            "node": "Save Result",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Save Result": {
      "main": [
        [
          {
            "node": "Respond Saved",
            "type": "main",
            "index": 0
          },
          {
            "node": "Hot Lead?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Hot Lead?": {
      "main": [
        [
          {
            "node": "Alert Sales (Exercise 1)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "pinData": {},
  "settings": {
    "executionOrder": "v1"
  }
}