{
  "name": "AI Lead Intake, Qualification & CRM Routing",
  "nodes": [
    {
      "id": "webhook_lead_intake",
      "name": "Lead Form Webhook",
      "type": "n8n-nodes-base.webhook",
      "typeVersion": 2.1,
      "position": [0, 300],
      "parameters": {
        "httpMethod": "POST",
        "path": "lead-intake",
        "responseMode": "onReceived",
        "options": {}
      }
    },
    {
      "id": "code_spam_filter",
      "name": "Spam / Junk Filter",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [220, 300],
      "parameters": {
        "language": "javaScript",
        "mode": "runOnceForAllItems",
        "jsCode": "const disposableDomains = ['mailinator.com','tempmail.com','guerrillamail.com','10minutemail.com','yopmail.com'];\nconst out = [];\nfor (const item of $input.all()) {\n  const d = item.json;\n  const email = String(d.email || '').trim().toLowerCase();\n  const message = String(d.message || d.needs || '').trim();\n  const emailValid = /^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/.test(email);\n  const domain = email.split('@')[1] || '';\n  const isDisposable = disposableDomains.includes(domain);\n  const isEmpty = message.length < 10;\n  const isSpam = !emailValid || isDisposable || isEmpty;\n  const spamReason = !emailValid ? 'invalid_email' : isDisposable ? 'disposable_domain' : isEmpty ? 'empty_message' : null;\n  out.push({ json: { ...d, email, message, isSpam, spamReason } });\n}\nreturn out;"
      }
    },
    {
      "id": "if_is_spam",
      "name": "Is Spam?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.3,
      "position": [440, 300],
      "parameters": {
        "conditions": {
          "options": { "caseSensitive": true, "leftValue": "", "typeValidation": "strict", "version": 2 },
          "combinator": "and",
          "conditions": [
            {
              "id": "spam-check-1",
              "leftValue": "={{ $json.isSpam }}",
              "rightValue": "",
              "operator": { "type": "boolean", "operation": "true", "singleValue": true }
            }
          ]
        }
      }
    },
    {
      "id": "noop_discarded",
      "name": "Discarded (Spam)",
      "type": "n8n-nodes-base.noOp",
      "typeVersion": 1,
      "position": [660, 460],
      "parameters": {}
    },
    {
      "id": "openai_qualify",
      "name": "AI Lead Qualification",
      "type": "n8n-nodes-base.openAi",
      "typeVersion": 1.1,
      "position": [660, 200],
      "onError": "continueRegularOutput",
      "parameters": {
        "resource": "chat",
        "operation": "complete",
        "model": "gpt-4o-mini",
        "prompt": {
          "messages": [
            {
              "role": "system",
              "content": "You are a B2B lead qualification engine for a Fractional COO / automation consultancy. Read the inbound lead message and return ONLY a single JSON object — no prose, no markdown fences — matching exactly this schema: {\"budget_tier\": \"low|mid|high\", \"timeline\": \"immediate|30d|60d_plus|unclear\", \"requirements_summary\": \"one sentence\", \"lead_score\": 0-100, \"classification\": \"high_value|nurture\"}. Classify as high_value only if budget_tier is mid or high AND timeline is immediate or 30d. Be conservative — when uncertain, classify nurture."
            },
            {
              "role": "user",
              "content": "={{ \"Name: \" + $json.name + \"\\nEmail: \" + $json.email + \"\\nMessage: \" + $json.message }}"
            }
          ]
        },
        "maxTokens": 400,
        "options": { "temperature": 0.2 }
      }
    },
    {
      "id": "code_parse_qualification",
      "name": "Parse Qualification",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [880, 200],
      "parameters": {
        "language": "javaScript",
        "mode": "runOnceForAllItems",
        "jsCode": "const out = [];\nfor (const item of $input.all()) {\n  const raw = item.json?.message?.content ?? item.json?.text ?? item.json?.choices?.[0]?.message?.content ?? '';\n  let parsed;\n  try {\n    const match = String(raw).match(/\\{[\\s\\S]*\\}/);\n    parsed = JSON.parse(match ? match[0] : raw);\n  } catch (e) {\n    parsed = { budget_tier: 'unknown', timeline: 'unclear', requirements_summary: 'LLM output failed to parse — routed to nurture as a safe default, not dropped', lead_score: 0, classification: 'nurture', parseError: true };\n  }\n  out.push({ json: { ...item.json, qualification: parsed } });\n}\nreturn out;"
      }
    },
    {
      "id": "if_high_value",
      "name": "High Value?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.3,
      "position": [1100, 200],
      "parameters": {
        "conditions": {
          "options": { "caseSensitive": true, "leftValue": "", "typeValidation": "strict", "version": 2 },
          "combinator": "and",
          "conditions": [
            {
              "id": "hv-check-1",
              "leftValue": "={{ $json.qualification.classification }}",
              "rightValue": "high_value",
              "operator": { "type": "string", "operation": "equals" }
            }
          ]
        }
      }
    },
    {
      "id": "hubspot_upsert_hot",
      "name": "Upsert CRM Contact (Hot)",
      "type": "n8n-nodes-base.hubspot",
      "typeVersion": 2.2,
      "position": [1320, 80],
      "onError": "continueRegularOutput",
      "parameters": {
        "authentication": "apiKey",
        "resource": "contact",
        "operation": "upsert",
        "email": "={{ $json.email }}",
        "additionalFields": {
          "firstName": "={{ $json.name }}",
          "leadStatus": "Hot — auto-qualified",
          "customPropertiesUi": {
            "customPropertiesValues": [
              { "property": "lead_score", "value": "={{ $json.qualification.lead_score }}" },
              { "property": "budget_tier", "value": "={{ $json.qualification.budget_tier }}" },
              { "property": "timeline", "value": "={{ $json.qualification.timeline }}" },
              { "property": "requirements_summary", "value": "={{ $json.qualification.requirements_summary }}" }
            ]
          }
        }
      }
    },
    {
      "id": "slack_alert_hot",
      "name": "High-Priority Slack Alert",
      "type": "n8n-nodes-base.slack",
      "typeVersion": 2.4,
      "position": [1540, 80],
      "onError": "continueRegularOutput",
      "parameters": {
        "resource": "message",
        "operation": "post",
        "select": "channel",
        "channelId": { "__rl": true, "mode": "name", "value": "#hot-leads", "cachedResultName": "hot-leads" },
        "text": "={{ \":rotating_light: *Hot lead* — \" + $json.name + \" <\" + $json.email + \">\\nScore: \" + $json.qualification.lead_score + \" · Budget: \" + $json.qualification.budget_tier + \" · Timeline: \" + $json.qualification.timeline + \"\\n\" + $json.qualification.requirements_summary }}"
      }
    },
    {
      "id": "email_scheduling_link",
      "name": "Send Scheduling Link",
      "type": "n8n-nodes-base.emailSend",
      "typeVersion": 2.1,
      "position": [1760, 80],
      "onError": "continueRegularOutput",
      "parameters": {
        "resource": "email",
        "operation": "send",
        "fromEmail": "hello@REPLACE_WITH_YOUR_DOMAIN.com",
        "toEmail": "={{ $json.email }}",
        "subject": "Thanks for reaching out — grab a time that works",
        "emailFormat": "text",
        "text": "={{ \"Hi \" + $json.name + \",\\n\\nThanks for the note — based on what you described, this looks like a great fit. Grab a time here: https://REPLACE_WITH_YOUR_SCHEDULING_LINK\\n\\nTalk soon.\" }}"
      }
    },
    {
      "id": "hubspot_nurture",
      "name": "Add to Nurture List",
      "type": "n8n-nodes-base.hubspot",
      "typeVersion": 2.2,
      "position": [1320, 340],
      "onError": "continueRegularOutput",
      "parameters": {
        "authentication": "apiKey",
        "resource": "contactList",
        "operation": "add",
        "by": "email",
        "email": "={{ $json.email }}",
        "listId": 0
      }
    }
  ],
  "connections": {
    "Lead Form Webhook": {
      "main": [[{ "node": "Spam / Junk Filter", "type": "main", "index": 0 }]]
    },
    "Spam / Junk Filter": {
      "main": [[{ "node": "Is Spam?", "type": "main", "index": 0 }]]
    },
    "Is Spam?": {
      "main": [
        [{ "node": "Discarded (Spam)", "type": "main", "index": 0 }],
        [{ "node": "AI Lead Qualification", "type": "main", "index": 0 }]
      ]
    },
    "AI Lead Qualification": {
      "main": [[{ "node": "Parse Qualification", "type": "main", "index": 0 }]]
    },
    "Parse Qualification": {
      "main": [[{ "node": "High Value?", "type": "main", "index": 0 }]]
    },
    "High Value?": {
      "main": [
        [{ "node": "Upsert CRM Contact (Hot)", "type": "main", "index": 0 }],
        [{ "node": "Add to Nurture List", "type": "main", "index": 0 }]
      ]
    },
    "Upsert CRM Contact (Hot)": {
      "main": [[{ "node": "High-Priority Slack Alert", "type": "main", "index": 0 }]]
    },
    "High-Priority Slack Alert": {
      "main": [[{ "node": "Send Scheduling Link", "type": "main", "index": 0 }]]
    }
  },
  "settings": {
    "executionOrder": "v1"
  }
}
