AutomationFlowsAI & RAG › Score and Route Inbound Web Leads with Openai, Gmail and Google Sheets

Score and Route Inbound Web Leads with Openai, Gmail and Google Sheets

ByMilos Vranes @zospirlo21 on n8n.io

This workflow receives web form submissions via webhook, uses OpenAI to score and classify each lead, then emails hot leads through Gmail and logs them to separate Google Sheets tabs for hot versus cold review. Receives a POST webhook request containing lead details (name,…

Webhook trigger★★★★☆ complexity14 nodesHTTP RequestGmailGoogle Sheets
AI & RAG Trigger: Webhook Nodes: 14 Complexity: ★★★★☆ Added:

This workflow corresponds to n8n.io template #15970 — we link there as the canonical source.

This workflow follows the Gmail → Google Sheets recipe pattern — see all workflows that pair these two integrations.

The workflow JSON

Copy or download the full n8n JSON below. Paste it into a new n8n workflow, add your credentials, activate. Full import guide →

Download .json
{
  "meta": {
    "templateCredsSetupCompleted": false
  },
  "name": "AI Lead Qualifier - Score and Route Form Submissions",
  "tags": [],
  "nodes": [
    {
      "id": "cfa94944-e70e-4737-887d-fa81aa345866",
      "name": "Sticky Note",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -368,
        48
      ],
      "parameters": {
        "width": 480,
        "height": 864,
        "content": "## AI Lead Qualifier - Score and Route Form Submissions\n\n### How it works\n\nThis workflow receives a lead form submission, normalizes the core lead fields, and uses an AI API request to score the lead. It parses the AI result, checks whether the lead meets the hot-lead criteria, then routes it to email alerts and the appropriate Google Sheet. Finally, it responds to the original form submission request.\n\n### Setup steps\n\n- Configure the webhook URL in the form tool so submissions are sent to the workflow trigger.\n- Add the OpenAI or compatible API credentials/header used by the AI scoring HTTP request, and verify the prompt and model endpoint are valid.\n- Connect Gmail credentials and set the recipient, subject, and body for hot lead alerts.\n- Connect Google Sheets credentials and select the target spreadsheets/tabs for hot leads and cold-review leads, ensuring the columns match the normalized lead and score fields.\n- Confirm the hot-lead condition in the IF node matches the intended score threshold or qualification rule.\n\n### Customization\n\nAdjust the AI prompt, scoring scale, and hot-lead threshold to match the sales qualification model. You can also change the alert recipients, sheet destinations, or add CRM routing for qualified leads."
      },
      "typeVersion": 1
    },
    {
      "id": "646fb263-a4c7-412e-9c2c-8f7a851b148b",
      "name": "Sticky Note1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        192,
        144
      ],
      "parameters": {
        "color": 7,
        "width": 416,
        "height": 336,
        "content": "## Receive and normalize lead\n\nCaptures a form submission through the webhook and standardizes the incoming lead fields such as name, email, company, and role for downstream processing."
      },
      "typeVersion": 1
    },
    {
      "id": "ff9f25fe-aff8-4d88-8be5-b2e3071de22e",
      "name": "Sticky Note2",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        640,
        160
      ],
      "parameters": {
        "color": 7,
        "width": 416,
        "height": 320,
        "content": "## Score and parse lead\n\nSends the normalized lead details to the AI scoring endpoint, then parses the response and merges the score back into the lead data."
      },
      "typeVersion": 1
    },
    {
      "id": "3383bd4a-bc0e-430f-b67a-04c4768832f7",
      "name": "Sticky Note3",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1104,
        48
      ],
      "parameters": {
        "color": 7,
        "width": 640,
        "height": 576,
        "content": "## Route qualified leads\n\nEvaluates whether the lead is hot and routes the result into the appropriate actions: sending an alert for hot leads, logging hot leads, or logging cold leads for review."
      },
      "typeVersion": 1
    },
    {
      "id": "0b6efb8f-18c0-4593-9c70-4ad3972db389",
      "name": "Sticky Note4",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1776,
        144
      ],
      "parameters": {
        "color": 7,
        "width": 240,
        "height": 336,
        "content": "## Return form response\n\nSends a final response back to the original form webhook after the routed lead handling path completes."
      },
      "typeVersion": 1
    },
    {
      "id": "a1b2c3d4-0001-4000-8000-000000000002",
      "name": "When Lead Received",
      "type": "n8n-nodes-base.webhook",
      "position": [
        240,
        320
      ],
      "parameters": {
        "path": "lead-qualifier",
        "options": {},
        "httpMethod": "POST",
        "responseMode": "responseNode"
      },
      "typeVersion": 2
    },
    {
      "id": "a1b2c3d4-0001-4000-8000-000000000003",
      "name": "Set Lead Fields",
      "type": "n8n-nodes-base.set",
      "position": [
        460,
        320
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "f1-name",
              "name": "name",
              "type": "string",
              "value": "={{ ($json.body?.name || $json.name || '').toString().trim() }}"
            },
            {
              "id": "f2-email",
              "name": "email",
              "type": "string",
              "value": "={{ ($json.body?.email || $json.email || '').toString().trim().toLowerCase() }}"
            },
            {
              "id": "f3-company",
              "name": "company",
              "type": "string",
              "value": "={{ ($json.body?.company || $json.company || '').toString().trim() }}"
            },
            {
              "id": "f4-role",
              "name": "role",
              "type": "string",
              "value": "={{ ($json.body?.role || $json.role || '').toString().trim() }}"
            },
            {
              "id": "f5-message",
              "name": "message",
              "type": "string",
              "value": "={{ ($json.body?.message || $json.message || '').toString().trim() }}"
            },
            {
              "id": "f6-timestamp",
              "name": "timestamp",
              "type": "string",
              "value": "={{ $now.toISO() }}"
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "a1b2c3d4-0001-4000-8000-000000000004",
      "name": "Post to AI Scoring API",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        680,
        320
      ],
      "parameters": {
        "url": "https://api.openai.com/v1/chat/completions",
        "method": "POST",
        "options": {},
        "jsonBody": "={\n  \"model\": \"gpt-4o-mini\",\n  \"response_format\": { \"type\": \"json_object\" },\n  \"messages\": [\n    {\n      \"role\": \"system\",\n      \"content\": \"You score inbound B2B sales leads. Reply with valid JSON only, matching this schema: { \\\"score\\\": <integer 1-10>, \\\"intent\\\": \\\"demo|pricing|info|unrelated\\\", \\\"rationale\\\": \\\"<one sentence>\\\" }. Score 8-10 for senior buyers with clear buying intent. Score 5-7 for relevant but early-stage interest. Score 1-4 for unrelated, students, competitors, or vague messages.\"\n    },\n    {\n      \"role\": \"user\",\n      \"content\": {{ JSON.stringify(\n        'Name: ' + $json.name + '\\nEmail: ' + $json.email + '\\nCompany: ' + $json.company + '\\nRole: ' + $json.role + '\\nMessage: ' + $json.message\n      ) }}\n    }\n  ]\n}",
        "sendBody": true,
        "specifyBody": "json",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth"
      },
      "typeVersion": 4.2
    },
    {
      "id": "a1b2c3d4-0001-4000-8000-000000000005",
      "name": "Parse AI Scoring Response",
      "type": "n8n-nodes-base.code",
      "position": [
        900,
        320
      ],
      "parameters": {
        "jsCode": "// Parse the AI response and merge with the original lead fields\nconst lead = $('Set Lead Fields').item.json;\nconst aiRaw = $json.choices?.[0]?.message?.content || '{}';\n\nlet parsed = { score: 0, intent: 'unrelated', rationale: 'Failed to parse AI response' };\ntry {\n  parsed = JSON.parse(aiRaw);\n  parsed.score = Math.max(1, Math.min(10, parseInt(parsed.score, 10) || 0));\n} catch (e) {\n  parsed.rationale = 'AI returned malformed JSON: ' + aiRaw.slice(0, 200);\n}\n\nreturn [{\n  json: {\n    ...lead,\n    score: parsed.score,\n    intent: parsed.intent,\n    rationale: parsed.rationale\n  }\n}];"
      },
      "typeVersion": 2
    },
    {
      "id": "a1b2c3d4-0001-4000-8000-000000000006",
      "name": "Check for Hot Lead",
      "type": "n8n-nodes-base.if",
      "position": [
        1152,
        320
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 2,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "cond-hot",
              "operator": {
                "type": "number",
                "operation": "gte"
              },
              "leftValue": "={{ $json.score }}",
              "rightValue": 7
            }
          ]
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "a1b2c3d4-0001-4000-8000-000000000007",
      "name": "Send Hot Lead Email",
      "type": "n8n-nodes-base.gmail",
      "position": [
        1376,
        208
      ],
      "parameters": {
        "sendTo": "={{ $vars.alertInbox || 'you@example.com' }}",
        "message": "=<h2>Hot lead just came in</h2>\n<p><b>Score:</b> {{ $json.score }}/10 - <i>{{ $json.intent }}</i></p>\n<p><b>{{ $json.name }}</b> ({{ $json.role }}) at <b>{{ $json.company }}</b><br>\n{{ $json.email }}</p>\n<p><b>Their message:</b><br>\n<blockquote>{{ $json.message }}</blockquote></p>\n<p><b>AI rationale:</b> {{ $json.rationale }}</p>",
        "options": {},
        "subject": "=Hot lead: {{ $json.company }} ({{ $json.score }}/10) - {{ $json.name }}",
        "emailType": "html"
      },
      "typeVersion": 2.1
    },
    {
      "id": "a1b2c3d4-0001-4000-8000-000000000008",
      "name": "Append to Hot Leads Sheet",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        1600,
        208
      ],
      "parameters": {
        "columns": {
          "value": {
            "name": "={{ $json.name }}",
            "role": "={{ $json.role }}",
            "email": "={{ $json.email }}",
            "score": "={{ $json.score }}",
            "intent": "={{ $json.intent }}",
            "company": "={{ $json.company }}",
            "message": "={{ $json.message }}",
            "rationale": "={{ $json.rationale }}",
            "timestamp": "={{ $json.timestamp }}"
          },
          "schema": [
            {
              "id": "timestamp",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "timestamp",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "name",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "name",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "email",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "email",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "company",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "company",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "role",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "role",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "score",
              "type": "number",
              "display": true,
              "required": false,
              "displayName": "score",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "intent",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "intent",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "rationale",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "rationale",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "message",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "message",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            }
          ],
          "mappingMode": "defineBelow",
          "matchingColumns": []
        },
        "options": {},
        "operation": "append",
        "sheetName": {
          "__rl": true,
          "mode": "name",
          "value": "Hot Leads"
        },
        "documentId": {
          "__rl": true,
          "mode": "id",
          "value": "REPLACE_WITH_YOUR_SHEET_ID"
        }
      },
      "typeVersion": 4.5
    },
    {
      "id": "a1b2c3d4-0001-4000-8000-000000000009",
      "name": "Append to Cold Leads Sheet",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        1376,
        464
      ],
      "parameters": {
        "columns": {
          "value": {
            "name": "={{ $json.name }}",
            "role": "={{ $json.role }}",
            "email": "={{ $json.email }}",
            "score": "={{ $json.score }}",
            "intent": "={{ $json.intent }}",
            "company": "={{ $json.company }}",
            "message": "={{ $json.message }}",
            "rationale": "={{ $json.rationale }}",
            "timestamp": "={{ $json.timestamp }}"
          },
          "schema": [
            {
              "id": "timestamp",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "timestamp",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "name",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "name",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "email",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "email",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "company",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "company",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "role",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "role",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "score",
              "type": "number",
              "display": true,
              "required": false,
              "displayName": "score",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "intent",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "intent",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "rationale",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "rationale",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "message",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "message",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            }
          ],
          "mappingMode": "defineBelow",
          "matchingColumns": []
        },
        "options": {},
        "operation": "append",
        "sheetName": {
          "__rl": true,
          "mode": "name",
          "value": "Cold Review"
        },
        "documentId": {
          "__rl": true,
          "mode": "id",
          "value": "REPLACE_WITH_YOUR_SHEET_ID"
        }
      },
      "typeVersion": 4.5
    },
    {
      "id": "a1b2c3d4-0001-4000-8000-000000000010",
      "name": "Send Form Response",
      "type": "n8n-nodes-base.respondToWebhook",
      "position": [
        1824,
        320
      ],
      "parameters": {
        "options": {},
        "respondWith": "json",
        "responseBody": "={{ { \"ok\": true, \"score\": $('Parse AI Scoring Response').item.json.score, \"intent\": $('Parse AI Scoring Response').item.json.intent } }}"
      },
      "typeVersion": 1.1
    }
  ],
  "settings": {
    "executionOrder": "v1"
  },
  "connections": {
    "Set Lead Fields": {
      "main": [
        [
          {
            "node": "Post to AI Scoring API",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Check for Hot Lead": {
      "main": [
        [
          {
            "node": "Send Hot Lead Email",
            "type": "main",
            "index": 0
          },
          {
            "node": "Append to Hot Leads Sheet",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Append to Cold Leads Sheet",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "When Lead Received": {
      "main": [
        [
          {
            "node": "Set Lead Fields",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Send Hot Lead Email": {
      "main": [
        [
          {
            "node": "Send Form Response",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Post to AI Scoring API": {
      "main": [
        [
          {
            "node": "Parse AI Scoring Response",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Append to Hot Leads Sheet": {
      "main": [
        []
      ]
    },
    "Parse AI Scoring Response": {
      "main": [
        [
          {
            "node": "Check for Hot Lead",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Append to Cold Leads Sheet": {
      "main": [
        [
          {
            "node": "Send Form Response",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}
Pro

For the full experience including quality scoring and batch install features for each workflow upgrade to Pro

About this workflow

This workflow receives web form submissions via webhook, uses OpenAI to score and classify each lead, then emails hot leads through Gmail and logs them to separate Google Sheets tabs for hot versus cold review. Receives a POST webhook request containing lead details (name,…

Source: https://n8n.io/workflows/15970/ — original creator credit. Request a take-down →

More AI & RAG workflows → · Browse all categories →

Related workflows

Workflows that share integrations, category, or trigger type with this one. All free to copy and import.

AI & RAG

This workflow receives new Calendly meeting bookings, checks Google Sheets to prevent duplicate briefings, pulls contact and deal context from HubSpot, enriches company data with Clearbit, generates t

Google Sheets, HubSpot, HTTP Request +2
AI & RAG

This workflow receives a webhook trigger, reads exception records from Google Sheets, uses Google Gemini (Generative Language API) to rank and summarize them, posts a prioritized report to Slack, writ

Google Sheets, HTTP Request, Gmail +1
AI & RAG

Instantly map all internal URLs, perform AI-powered (ChatGPT) analysis, and deliver results in HTML via webhook, Google Sheets, or email. All from your own n8n instance!

OpenAI, HTTP Request, XML +3
AI & RAG

Watch on Youtube▶️

HTTP Request, Email Send, Google Sheets +3
AI & RAG

This workflow automates the initial screening process for new job applications, freeing up your recruitment team to focus on qualified candidates. It receives applications from a webhook, uses OpenAI

HTTP Request, OpenAI, Google Sheets +2