{
  "id": "QqxwtgvbrQ3Tac2p",
  "name": "New Lead Auto-Enricher and Router",
  "tags": [],
  "nodes": [
    {
      "id": "7821af61-021c-4244-8bef-1c970215e334",
      "name": "Sticky Note",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -384,
        -16
      ],
      "parameters": {
        "color": 2,
        "width": 560,
        "height": 1072,
        "content": "# New Lead Auto-Enricher and Router\n\nWhen someone fills your contact form, this workflow enriches their submission with website content, scores the lead against your ICP, writes a personalized first-reply draft, logs everything to Airtable, and notifies you instantly before you've opened your inbox.\n\n\n## How it works\n\n1. **Receive Lead** webhook fires when a form is submitted (Typeform, Tally, JotForm, or any webhook).\n2. **Normalize Form Data** maps the form fields to a consistent shape regardless of which tool sent it.\n3. **Scrape Website** calls Jina AI Reader (free, no key) to pull the lead's website content. Skipped gracefully if no website was provided.\n4. **Claude** analyzes the lead: scores 0-100, classifies tier (hot/warm/cold), detects red flags, writes an enriched summary and a personalized first-reply draft.\n5. **Build Actions** constructs the Airtable record and Slack notification.\n6. **Log to Airtable** stores the full enriched lead record.\n7. **Notify via Slack** sends an instant alert with score, summary, and recommended action.\n8. **If Hot Lead** also sends the first-reply draft directly to your Gmail drafts for one-click sending.\n\n\n## Setup steps\n\n- [ ] Connect your form tool to the **Receive Lead** webhook URL (copy it from the node)\n- [ ] Create an Airtable base with fields matching the ones in the Log to Airtable node\n- [ ] Add Anthropic credentials to the Anthropic Chat Model node\n- [ ] Connect Airtable credentials and select your base/table\n- [ ] Connect Slack credentials and set your notification channel\n- [ ] Connect Gmail credentials for the hot-lead draft step\n- [ ] Customize the ICP scoring criteria in the Claude system prompt to match your own ideal client\n\n\n## Supported form tools\n\nTypeform \u00b7 Tally \u00b7 JotForm \u00b7 Any webhook (raw JSON)\n\nAll sources normalize to the same internal schema field labels just need to include words like \"name\", \"email\", \"company\", \"website\", \"message\"."
      },
      "typeVersion": 1
    },
    {
      "id": "7b109987-e926-47c5-98e6-e13e4faa6b4e",
      "name": "Sticky Note1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        240,
        -16
      ],
      "parameters": {
        "color": 7,
        "width": 560,
        "height": 624,
        "content": "## Receive and normalize\n\nWebhook accepts any form tool. Normalize maps Typeform, Tally, and raw payloads to one consistent shape."
      },
      "typeVersion": 1
    },
    {
      "id": "53fad6b0-fbaa-4ff7-b31c-04b9e50d14ac",
      "name": "Sticky Note2",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        864,
        -16
      ],
      "parameters": {
        "color": 7,
        "width": 736,
        "height": 624,
        "content": "## Enrich and score\n\nJina AI scrapes the lead's website (free). Claude scores the lead, detects red flags, and writes a personalized reply draft."
      },
      "typeVersion": 1
    },
    {
      "id": "61f29218-c16d-4acf-8531-df5cbc88d54d",
      "name": "Sticky Note3",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1664,
        -16
      ],
      "parameters": {
        "color": 7,
        "width": 576,
        "height": 624,
        "content": "## Log and route\n\nAll leads go to Airtable + Slack. Hot leads additionally get a Gmail draft created for one-click sending."
      },
      "typeVersion": 1
    },
    {
      "id": "dee9bc6a-42fb-4a54-a952-ddc6cb20aaf1",
      "name": "Receive Lead",
      "type": "n8n-nodes-base.webhook",
      "position": [
        272,
        224
      ],
      "parameters": {
        "path": "lead-intake",
        "options": {},
        "httpMethod": "POST",
        "responseMode": "lastNode"
      },
      "typeVersion": 2
    },
    {
      "id": "091cefdb-7c28-45ee-b794-85f7796d67d8",
      "name": "Acknowledge Receipt",
      "type": "n8n-nodes-base.respondToWebhook",
      "position": [
        672,
        416
      ],
      "parameters": {
        "options": {
          "responseCode": 200
        },
        "respondWith": "json",
        "responseBody": "{\"status\": \"received\", \"message\": \"Thanks \u2014 we'll be in touch shortly.\"}"
      },
      "typeVersion": 1.1
    },
    {
      "id": "ef780057-08f1-44e1-aaeb-e243e007a3fb",
      "name": "Normalize Form Data",
      "type": "n8n-nodes-base.code",
      "position": [
        672,
        224
      ],
      "parameters": {
        "jsCode": "// Normalize the webhook payload from any form tool\n// Supports Typeform, Tally, JotForm, or raw webhook\nconst body = $input.first().json.body || $input.first().json;\n\nlet normalized = {\n  name: null,\n  email: null,\n  company: null,\n  website: null,\n  message: null,\n  submitted_at: new Date().toISOString()\n};\n\n// Typeform format\nif (body.form_response?.answers) {\n  const answers = body.form_response.answers;\n  const fields = body.form_response.definition?.fields || [];\n  for (let i = 0; i < answers.length; i++) {\n    const a = answers[i];\n    const label = fields[i]?.title?.toLowerCase() || '';\n    const val = a.text || a.email || a.url || a.choice?.label || '';\n    if (label.includes('name')) normalized.name = val;\n    else if (label.includes('email')) normalized.email = val;\n    else if (label.includes('company')) normalized.company = val;\n    else if (label.includes('website') || label.includes('url')) normalized.website = val;\n    else if (label.includes('message') || label.includes('project') || label.includes('tell')) normalized.message = val;\n  }\n  normalized.submitted_at = body.form_response.submitted_at || normalized.submitted_at;\n}\n// Tally format\nelse if (body.data?.fields) {\n  for (const field of body.data.fields) {\n    const label = (field.label || '').toLowerCase();\n    const val = Array.isArray(field.value) ? field.value.join(', ') : String(field.value || '');\n    if (label.includes('name')) normalized.name = val;\n    else if (label.includes('email')) normalized.email = val;\n    else if (label.includes('company')) normalized.company = val;\n    else if (label.includes('website') || label.includes('url')) normalized.website = val;\n    else if (label.includes('message') || label.includes('project') || label.includes('tell')) normalized.message = val;\n  }\n  normalized.submitted_at = body.data.createdAt || normalized.submitted_at;\n}\n// Raw / custom webhook\nelse {\n  normalized.name = body.name || body.full_name || null;\n  normalized.email = body.email || null;\n  normalized.company = body.company || body.company_name || null;\n  normalized.website = body.website || body.url || null;\n  normalized.message = body.message || body.project || body.description || null;\n  normalized.submitted_at = body.submitted_at || normalized.submitted_at;\n}\n\nif (!normalized.email) throw new Error('No email found in form submission. Check your form field labels contain \"email\".');\nif (!normalized.message) throw new Error('No message found in form submission. Check your form field labels contain \"message\" or \"project\".');\n\nreturn [{ json: normalized }];"
      },
      "typeVersion": 2
    },
    {
      "id": "bdf35de1-2363-42ac-98e3-f9e3af35cde6",
      "name": "Scrape Website",
      "type": "n8n-nodes-base.code",
      "position": [
        896,
        224
      ],
      "parameters": {
        "jsCode": "// Attempt to scrape the lead's website via Jina AI Reader (free, no key needed)\n// If no website or scrape fails, pass null \u2014 the AI prompt handles missing content gracefully\n\nconst data = $input.first().json;\n\nif (!data.website) {\n  return [{ json: { ...data, website_content: null } }];\n}\n\n// Clean the URL\nlet url = data.website.trim();\nif (!url.startsWith('http')) url = 'https://' + url;\n\nlet content = null;\ntry {\n  const jinaUrl = `https://r.jina.ai/${url}`;\n  const response = await $helpers.httpRequest({\n    method: 'GET',\n    url: jinaUrl,\n    headers: { 'Accept': 'text/plain' },\n    timeout: 12000\n  });\n  // Truncate to 2000 chars \u2014 enough for Claude to classify\n  content = (response || '').toString().slice(0, 2000).trim() || null;\n} catch (e) {\n  content = null;\n}\n\nreturn [{ json: { ...data, website_content: content } }];"
      },
      "typeVersion": 2
    },
    {
      "id": "cbeaa112-92b0-4ee0-a07d-e28f092baa15",
      "name": "Enrich and Score Lead",
      "type": "@n8n/n8n-nodes-langchain.agent",
      "position": [
        1104,
        224
      ],
      "parameters": {
        "text": "=Analyze this inbound lead and return a qualification report.\n\nName: {{ $json.name }}\nEmail: {{ $json.email }}\nCompany: {{ $json.company || 'Not provided' }}\nWebsite: {{ $json.website || 'Not provided' }}\nMessage: {{ $json.message }}\nWebsite content scraped: {{ $json.website_content || 'Could not scrape \u2014 no website provided or scrape failed' }}\nSubmitted at: {{ $json.submitted_at }}\n\nReturn ONLY valid JSON following your schema.",
        "options": {
          "systemMessage": "You are a lead qualification specialist for a freelance agency or small business. You analyze inbound leads and score them against an Ideal Customer Profile (ICP).\n\n## Scoring criteria (adjust weights to match your ICP)\n- Company size match: solo/startup/SMB/enterprise \u2014 score based on fit\n- Industry relevance: how closely their industry matches your services\n- Project clarity: how clearly they described what they need\n- Budget signals: any mention of budget, timeline, or urgency\n- Red flags: vague requests, no email, suspiciously short message\n\n## Output rules\n- Score 0-100. Above 70 = hot lead. 50-69 = warm. Below 50 = cold.\n- Be direct. One sentence per field. No fluff.\n- enriched_summary: 2-3 sentences max \u2014 what this company does, why they reached out, and your read on them\n- first_reply_draft: a short, professional, personalized first reply (4-6 sentences). Reference something specific from their message. End with one clear next step.\n- do NOT invent facts not present in the input \u2014 only work with what was submitted"
        },
        "promptType": "define",
        "hasOutputParser": true
      },
      "typeVersion": 3
    },
    {
      "id": "c309c5bc-225e-4aa5-bfaa-7725f68a4cca",
      "name": "Anthropic Chat Model",
      "type": "@n8n/n8n-nodes-langchain.lmChatAnthropic",
      "position": [
        1104,
        464
      ],
      "parameters": {
        "model": {
          "__rl": true,
          "mode": "list",
          "value": "claude-sonnet-4-5-20250929",
          "cachedResultName": "Claude Sonnet 4.5"
        },
        "options": {
          "temperature": 0.2
        }
      },
      "typeVersion": 1.3
    },
    {
      "id": "c8f73171-31d2-42f1-be3b-17d44d7fb14e",
      "name": "Structured Output Parser",
      "type": "@n8n/n8n-nodes-langchain.outputParserStructured",
      "position": [
        1344,
        464
      ],
      "parameters": {
        "autoFix": true,
        "schemaType": "manual",
        "inputSchema": "{\n  \"type\": \"object\",\n  \"properties\": {\n    \"score\": {\n      \"type\": \"number\",\n      \"description\": \"0-100 lead quality score\"\n    },\n    \"tier\": {\n      \"type\": \"string\",\n      \"enum\": [\n        \"hot\",\n        \"warm\",\n        \"cold\"\n      ]\n    },\n    \"industry_guess\": {\n      \"type\": \"string\"\n    },\n    \"company_size_guess\": {\n      \"type\": \"string\",\n      \"enum\": [\n        \"solo\",\n        \"startup\",\n        \"smb\",\n        \"enterprise\",\n        \"unknown\"\n      ]\n    },\n    \"project_clarity\": {\n      \"type\": \"string\",\n      \"enum\": [\n        \"clear\",\n        \"partial\",\n        \"vague\"\n      ]\n    },\n    \"red_flags\": {\n      \"type\": \"array\",\n      \"items\": {\n        \"type\": \"string\"\n      }\n    },\n    \"enriched_summary\": {\n      \"type\": \"string\"\n    },\n    \"first_reply_draft\": {\n      \"type\": \"string\"\n    }\n  },\n  \"required\": [\n    \"score\",\n    \"tier\",\n    \"industry_guess\",\n    \"company_size_guess\",\n    \"project_clarity\",\n    \"red_flags\",\n    \"enriched_summary\",\n    \"first_reply_draft\"\n  ],\n  \"additionalProperties\": false\n}"
      },
      "typeVersion": 1.2
    },
    {
      "id": "8e3cfcf1-e676-41ec-a9af-9d89e27c1772",
      "name": "Build Actions",
      "type": "n8n-nodes-base.code",
      "position": [
        1424,
        224
      ],
      "parameters": {
        "jsCode": "// Build Airtable record, route lead, and prepare notification\nconst ai = $input.first().json.output || $input.first().json;\nconst lead = $('Scrape Website').first().json;\n\nconst tierConfig = {\n  hot:  { emoji: '\ud83d\udd25', action: 'Respond within 2 hours', color: 'redBright' },\n  warm: { emoji: '\ud83d\udfe1', action: 'Respond within 24 hours', color: 'yellow' },\n  cold: { emoji: '\ud83e\uddca', action: 'Add to nurture list', color: 'blue' }\n};\nconst cfg = tierConfig[ai.tier] || tierConfig.cold;\n\nconst slackMessage = [\n  `${cfg.emoji} *New ${ai.tier.toUpperCase()} Lead \u2014 Score: ${ai.score}/100*`,\n  '',\n  `*Name:* ${lead.name}  \u00b7  *Email:* ${lead.email}`,\n  lead.company ? `*Company:* ${lead.company}` : '',\n  `*Industry:* ${ai.industry_guess}  \u00b7  *Size:* ${ai.company_size_guess}`,\n  `*Project clarity:* ${ai.project_clarity}`,\n  '',\n  `*Summary:* ${ai.enriched_summary}`,\n  ai.red_flags?.length ? `*Red flags:* ${ai.red_flags.join(', ')}` : '',\n  '',\n  `*Action:* ${cfg.action}`\n].filter(Boolean).join('\\n');\n\nreturn [{\n  json: {\n    // Airtable record\n    lead_name: lead.name,\n    lead_email: lead.email,\n    company: lead.company || '',\n    website: lead.website || '',\n    message: lead.message,\n    score: ai.score,\n    tier: ai.tier,\n    industry: ai.industry_guess,\n    company_size: ai.company_size_guess,\n    project_clarity: ai.project_clarity,\n    red_flags: (ai.red_flags || []).join(', '),\n    enriched_summary: ai.enriched_summary,\n    status: 'new',\n    submitted_at: lead.submitted_at,\n    // Reply draft\n    first_reply_draft: ai.first_reply_draft,\n    reply_to: lead.email,\n    // Routing\n    isHot: ai.tier === 'hot',\n    slackMessage\n  }\n}];"
      },
      "typeVersion": 2
    },
    {
      "id": "35cdd120-01fb-44c9-8e3a-343fc413e64f",
      "name": "Log to Airtable",
      "type": "n8n-nodes-base.airtable",
      "position": [
        1760,
        224
      ],
      "parameters": {
        "base": {
          "__rl": true,
          "mode": "id",
          "value": "REPLACE_WITH_YOUR_BASE_ID"
        },
        "table": {
          "__rl": true,
          "mode": "id",
          "value": "REPLACE_WITH_YOUR_TABLE_ID"
        },
        "columns": {
          "value": {
            "Name": "={{ $json.lead_name }}",
            "Tier": "={{ $json.tier }}",
            "Email": "={{ $json.lead_email }}",
            "Score": "={{ $json.score }}",
            "Status": "={{ $json.status }}",
            "Company": "={{ $json.company }}",
            "Message": "={{ $json.message }}",
            "Summary": "={{ $json.enriched_summary }}",
            "Website": "={{ $json.website }}",
            "Industry": "={{ $json.industry }}",
            "Red Flags": "={{ $json.red_flags }}",
            "Company Size": "={{ $json.company_size }}",
            "Submitted At": "={{ $json.submitted_at }}",
            "Project Clarity": "={{ $json.project_clarity }}"
          },
          "mappingMode": "defineBelow"
        },
        "options": {},
        "operation": "create"
      },
      "typeVersion": 2.1
    },
    {
      "id": "4b8d418b-adec-4498-987a-01e3d86b9ab3",
      "name": "Notify via Slack",
      "type": "n8n-nodes-base.slack",
      "position": [
        2016,
        224
      ],
      "parameters": {
        "text": "={{ $json.slackMessage }}",
        "select": "channel",
        "channelId": {
          "__rl": true,
          "mode": "name",
          "value": "#new-leads"
        },
        "otherOptions": {
          "includeLinkToWorkflow": false
        },
        "authentication": "oAuth2"
      },
      "credentials": {
        "slackOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 2.4
    },
    {
      "id": "0ad629e1-2a03-4ac7-b161-5c84409b3c35",
      "name": "If Hot Lead",
      "type": "n8n-nodes-base.if",
      "position": [
        1760,
        448
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 3,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "hot-check",
              "operator": {
                "type": "boolean",
                "operation": "equals"
              },
              "leftValue": "={{ $json.isHot }}",
              "rightValue": true
            }
          ]
        }
      },
      "typeVersion": 2.3
    },
    {
      "id": "2b160c83-a145-4022-97f2-4456cf7a3bbd",
      "name": "Create Reply Draft",
      "type": "n8n-nodes-base.gmail",
      "position": [
        2016,
        448
      ],
      "parameters": {
        "operation": "createDraft"
      },
      "credentials": {
        "gmailOAuth2": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 2.1
    }
  ],
  "active": false,
  "settings": {
    "binaryMode": "separate",
    "executionOrder": "v1"
  },
  "versionId": "09c8b08c-27e1-4d2c-9e3b-6ba8078e45f9",
  "connections": {
    "If Hot Lead": {
      "main": [
        [
          {
            "node": "Create Reply Draft",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Receive Lead": {
      "main": [
        [
          {
            "node": "Normalize Form Data",
            "type": "main",
            "index": 0
          },
          {
            "node": "Acknowledge Receipt",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Build Actions": {
      "main": [
        [
          {
            "node": "Log to Airtable",
            "type": "main",
            "index": 0
          },
          {
            "node": "Notify via Slack",
            "type": "main",
            "index": 0
          },
          {
            "node": "If Hot Lead",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Scrape Website": {
      "main": [
        [
          {
            "node": "Enrich and Score Lead",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Normalize Form Data": {
      "main": [
        [
          {
            "node": "Scrape Website",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Anthropic Chat Model": {
      "ai_languageModel": [
        [
          {
            "node": "Enrich and Score Lead",
            "type": "ai_languageModel",
            "index": 0
          },
          {
            "node": "Structured Output Parser",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    },
    "Enrich and Score Lead": {
      "main": [
        [
          {
            "node": "Build Actions",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Structured Output Parser": {
      "ai_outputParser": [
        [
          {
            "node": "Enrich and Score Lead",
            "type": "ai_outputParser",
            "index": 0
          }
        ]
      ]
    }
  }
}