{
  "name": "AI Lead Scoring to Sheets",
  "nodes": [
    {
      "parameters": {
        "content": "## AI Lead Scoring with GPT to Google Sheets\n\nEvery inbound lead gets a 1-10 score with a one-line reason before a human even looks - hot ones ping Slack, everything lands in a ranked spreadsheet.\n\n**Setup time:** 15 minutes  ·  **Level:** Intermediate\n\n**You'll need accounts/credentials for:** Webhook, OpenAI, Google Sheets, Slack\n\n**Replace these before running:**\n- `REPLACE_WITH_YOUR_SPREADSHEET`\n- `REPLACE_WITH_CHANNEL`\n\n**Setup steps:**\n1. Edit the OpenAI prompt: replace the example ideal-customer-profile paragraph with who YOU sell to.\n2. Add your OpenAI API key.\n3. Connect Google Sheets (columns: Timestamp, Name, Email, Company, Message, Score, Reason).\n4. Connect Slack for the hot-lead channel.\n5. Point your form's POST at the webhook URL (fields: name, email, company, message) and send a test lead.\n\nFree templates and updates: navkhan103.github.io",
        "height": 640,
        "width": 400,
        "color": 4
      },
      "id": "sticky-setup",
      "name": "Setup — read me first",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        -440,
        -120
      ]
    },
    {
      "parameters": {
        "content": "### How it runs\n\nStarts from **Lead Webhook**.\n\n1. A Webhook node receives the lead (from your form, landing page, or another workflow).\n2. An OpenAI node scores the lead against your ideal customer profile - edit the profile text in the prompt to match your business.\n3. A Code node parses the score and reason from the AI reply.\n4. A Google Sheets node appends the scored lead.\n5. An IF node checks score >= 8 and posts hot leads to Slack with the reasoning.",
        "height": 374,
        "width": 400,
        "color": 7
      },
      "id": "sticky-how",
      "name": "How it runs",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        -440,
        560
      ]
    },
    {
      "parameters": {
        "content": "### If something breaks\n\nThe read and AI nodes here **retry 3× automatically** (5s apart), which absorbs rate limits and brief timeouts.\n\nNodes that **send or write deliberately do not retry** — a send that reached the far end but lost its response would otherwise fire twice, so you'd get duplicate messages or duplicate rows.\n\nTo be told when one of those fails, build a workflow starting with an **Error Trigger** node that posts to Slack or email, then set it under **three-dot menu > Settings > Error Workflow**. Do it once and point every workflow at it.",
        "height": 400,
        "width": 400,
        "color": 3
      },
      "id": "sticky-fail",
      "name": "If something breaks",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        -440,
        974
      ]
    },
    {
      "parameters": {
        "httpMethod": "POST",
        "path": "score-lead",
        "responseMode": "onReceived",
        "responseData": "allEntries"
      },
      "id": "a4b5c6d7-0001-4000-8000-000000000001",
      "name": "Lead Webhook",
      "type": "n8n-nodes-base.webhook",
      "typeVersion": 2,
      "position": [
        0,
        0
      ],
      "webhookId": "score-lead"
    },
    {
      "parameters": {
        "modelId": {
          "__rl": true,
          "mode": "list",
          "value": "gpt-5.6-luna"
        },
        "messages": {
          "values": [
            {
              "content": "=You are a sales development rep. Our ideal customer: B2B SaaS or e-commerce companies with 10-200 employees, in North America or Europe, with a named decision-maker role (founder, head of ops, marketing lead). Bad fit: students, agencies reselling, no company given.\n\nScore this lead 1-10 for fit and intent, then give a one-sentence reason.\nReply EXACTLY in this format:\nSCORE: <number>\nREASON: <one sentence>\n\nLead:\nName: {{ $json.body.name }}\nEmail: {{ $json.body.email }}\nCompany: {{ $json.body.company }}\nMessage: {{ $json.body.message }}",
              "role": "user"
            }
          ]
        }
      },
      "id": "a4b5c6d7-0002-4000-8000-000000000002",
      "name": "Score with AI",
      "type": "@n8n/n8n-nodes-langchain.openAi",
      "typeVersion": 1.8,
      "position": [
        220,
        0
      ],
      "retryOnFail": true,
      "maxTries": 3,
      "waitBetweenTries": 5000
    },
    {
      "parameters": {
        "jsCode": "const raw = $input.first().json.message.content;\nconst lead = $('Lead Webhook').first().json.body;\nconst score = parseInt((raw.match(/SCORE:\\s*(\\d+)/) || [])[1] || '0', 10);\nconst reason = ((raw.match(/REASON:\\s*(.+)/) || [])[1] || '').trim();\nreturn [{ json: {\n  Timestamp: new Date().toISOString(),\n  Name: lead.name || '', Email: lead.email || '', Company: lead.company || '',\n  Message: (lead.message || '').slice(0, 300), Score: score, Reason: reason\n} }];"
      },
      "id": "a4b5c6d7-0003-4000-8000-000000000003",
      "name": "Parse Score",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        440,
        0
      ]
    },
    {
      "parameters": {
        "operation": "append",
        "documentId": {
          "__rl": true,
          "mode": "list",
          "value": "REPLACE_WITH_YOUR_SPREADSHEET"
        },
        "sheetName": {
          "__rl": true,
          "mode": "list",
          "value": "Sheet1"
        },
        "columns": {
          "mappingMode": "autoMapInputData"
        }
      },
      "id": "a4b5c6d7-0004-4000-8000-000000000004",
      "name": "Log to Google Sheets",
      "type": "n8n-nodes-base.googleSheets",
      "typeVersion": 4.5,
      "position": [
        660,
        0
      ]
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "conditions": [
            {
              "id": "c1",
              "leftValue": "={{ $json.Score }}",
              "rightValue": 8,
              "operator": {
                "type": "number",
                "operation": "gte"
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "a4b5c6d7-0005-4000-8000-000000000005",
      "name": "Hot Lead?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        880,
        0
      ]
    },
    {
      "parameters": {
        "select": "channel",
        "channelId": {
          "__rl": true,
          "mode": "list",
          "value": "REPLACE_WITH_CHANNEL"
        },
        "text": "=:fire: *Hot lead ({{ $json.Score }}/10):* {{ $json.Name }} at {{ $json.Company }} <mailto:{{ $json.Email }}|{{ $json.Email }}>\n_{{ $json.Reason }}_\n>{{ $json.Message }}",
        "otherOptions": {}
      },
      "id": "a4b5c6d7-0006-4000-8000-000000000006",
      "name": "Alert Slack",
      "type": "n8n-nodes-base.slack",
      "typeVersion": 2.3,
      "position": [
        1100,
        -80
      ]
    }
  ],
  "connections": {
    "Lead Webhook": {
      "main": [
        [
          {
            "node": "Score with AI",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Score with AI": {
      "main": [
        [
          {
            "node": "Parse Score",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Parse Score": {
      "main": [
        [
          {
            "node": "Log to Google Sheets",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Log to Google Sheets": {
      "main": [
        [
          {
            "node": "Hot Lead?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Hot Lead?": {
      "main": [
        [
          {
            "node": "Alert Slack",
            "type": "main",
            "index": 0
          }
        ],
        []
      ]
    }
  },
  "settings": {
    "executionOrder": "v1"
  }
}