AutomationFlowsMarketing & Ads › Score and Route Website Leads with Google Sheets and Gmail

Score and Route Website Leads with Google Sheets and Gmail

ByTinyOps Studio @tinyopsstudio on n8n.io

This workflow receives website lead submissions via webhook, normalizes the payload, validates contact details, scores intent to assign an SLA bucket, logs each lead to Google Sheets, and routes hot and review leads to the right inbox using Gmail. Receives a POST request on a…

Webhook trigger★★★★☆ complexity15 nodesGoogle SheetsGmail
Marketing & Ads Trigger: Webhook Nodes: 15 Complexity: ★★★★☆ Added:

This workflow corresponds to n8n.io template #16067 — 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
{
  "name": "Website Lead Rescue Desk: Score, SLA, Sheet, and Gmail Routing",
  "tags": [
    {
      "name": "lead-intake"
    },
    {
      "name": "sales-ops"
    },
    {
      "name": "sla-routing"
    }
  ],
  "nodes": [
    {
      "id": "workflow-note",
      "name": "Workflow Notes",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -1120,
        -360
      ],
      "parameters": {
        "width": 520,
        "height": 760,
        "content": "## Website Lead Rescue Desk\n\nUse this when website leads go stale because form payloads are inconsistent, urgency is unclear, and the right person is not notified quickly.\n\nFlow:\n1. Receive a website/form lead payload.\n2. Normalize contact, company, source, budget, page, and message fields.\n3. Reject incomplete submissions with a structured webhook response.\n4. Score intent from pricing/demo/urgent/integration language plus contact quality.\n5. Set an SLA bucket and dedupe key.\n6. Append an audit row to Google Sheets.\n7. Route hot leads to a sales Gmail alert, medium leads to a review queue, and low-priority leads to a normal response.\n\nReplace placeholder Google Sheets and Gmail credentials before production use. No private credentials or customer data are included."
      },
      "typeVersion": 1
    },
    {
      "id": "lead-webhook",
      "name": "Lead Webhook",
      "type": "n8n-nodes-base.webhook",
      "position": [
        -520,
        0
      ],
      "parameters": {
        "path": "lead-rescue-desk",
        "options": {},
        "httpMethod": "POST",
        "responseMode": "responseNode"
      },
      "typeVersion": 2
    },
    {
      "id": "normalize-payload",
      "name": "Normalize Payload",
      "type": "n8n-nodes-base.set",
      "position": [
        -320,
        0
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "received-at",
              "name": "received_at",
              "type": "string",
              "value": "={{ $now.toISO() }}"
            },
            {
              "id": "lead-name",
              "name": "name",
              "type": "string",
              "value": "={{ ($json.body?.name || $json.name || $json.body?.full_name || $json.full_name || '').trim() }}"
            },
            {
              "id": "lead-email",
              "name": "email",
              "type": "string",
              "value": "={{ ($json.body?.email || $json.email || '').toLowerCase().trim() }}"
            },
            {
              "id": "lead-phone",
              "name": "phone",
              "type": "string",
              "value": "={{ ($json.body?.phone || $json.phone || '').trim() }}"
            },
            {
              "id": "lead-company",
              "name": "company",
              "type": "string",
              "value": "={{ ($json.body?.company || $json.company || $json.body?.organization || $json.organization || '').trim() }}"
            },
            {
              "id": "lead-message",
              "name": "message",
              "type": "string",
              "value": "={{ ($json.body?.message || $json.message || $json.body?.details || $json.details || '').trim() }}"
            },
            {
              "id": "lead-budget",
              "name": "budget",
              "type": "string",
              "value": "={{ ($json.body?.budget || $json.budget || '').trim() }}"
            },
            {
              "id": "lead-timeline",
              "name": "requested_timeline",
              "type": "string",
              "value": "={{ ($json.body?.timeline || $json.timeline || $json.body?.requested_timeline || $json.requested_timeline || '').trim() }}"
            },
            {
              "id": "lead-page",
              "name": "page_url",
              "type": "string",
              "value": "={{ ($json.body?.page_url || $json.page_url || $json.body?.url || $json.url || '').trim() }}"
            },
            {
              "id": "lead-source",
              "name": "source",
              "type": "string",
              "value": "={{ ($json.body?.source || $json.source || $json.body?.utm_source || $json.utm_source || 'website').trim() }}"
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "has-contact",
      "name": "Has Contact?",
      "type": "n8n-nodes-base.if",
      "position": [
        -120,
        0
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "boolean": [
            {
              "value1": "={{ Boolean(($json.email || '').includes('@') || ($json.phone || '').length >= 7) }}",
              "value2": true
            }
          ]
        }
      },
      "typeVersion": 2
    },
    {
      "id": "missing-contact-response",
      "name": "Build Missing Contact Response",
      "type": "n8n-nodes-base.set",
      "position": [
        80,
        220
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "missing-ok",
              "name": "ok",
              "type": "boolean",
              "value": "={{ false }}"
            },
            {
              "id": "missing-status",
              "name": "status",
              "type": "string",
              "value": "needs_contact"
            },
            {
              "id": "missing-message",
              "name": "response_message",
              "type": "string",
              "value": "Please include a valid email address or phone number so the team can follow up."
            }
          ]
        },
        "includeOtherFields": true
      },
      "typeVersion": 3.4
    },
    {
      "id": "respond-missing-contact",
      "name": "Respond Missing Contact",
      "type": "n8n-nodes-base.respondToWebhook",
      "position": [
        300,
        220
      ],
      "parameters": {
        "options": {
          "responseCode": 400
        },
        "respondWith": "json",
        "responseBody": "={{ { ok: $json.ok, status: $json.status, message: $json.response_message } }}"
      },
      "typeVersion": 1.1
    },
    {
      "id": "score-lead",
      "name": "Score Lead and SLA",
      "type": "n8n-nodes-base.set",
      "position": [
        80,
        -80
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "intent-matches",
              "name": "intent_matches",
              "type": "number",
              "value": "={{ (($json.message || '') + ' ' + ($json.requested_timeline || '') + ' ' + ($json.budget || '')).match(/pricing|quote|proposal|demo|urgent|asap|this week|call|integration|crm|revenue|sales|budget|deadline/gi)?.length || 0 }}"
            },
            {
              "id": "lead-score",
              "name": "lead_score",
              "type": "number",
              "value": "={{ Math.min(100, ((($json.message || '') + ' ' + ($json.requested_timeline || '') + ' ' + ($json.budget || '')).match(/pricing|quote|proposal|demo|urgent|asap|this week|call|integration|crm|revenue|sales|budget|deadline/gi)?.length || 0) * 15 + (($json.email || '').includes('@') ? 20 : 0) + (($json.company || '').length ? 10 : 0) + (($json.budget || '').length ? 15 : 0) + (($json.page_url || '').length ? 5 : 0)) }}"
            },
            {
              "id": "sla",
              "name": "sla_bucket",
              "type": "string",
              "value": "={{ Math.min(100, ((($json.message || '') + ' ' + ($json.requested_timeline || '') + ' ' + ($json.budget || '')).match(/pricing|quote|proposal|demo|urgent|asap|this week|call|integration|crm|revenue|sales|budget|deadline/gi)?.length || 0) * 15 + (($json.email || '').includes('@') ? 20 : 0) + (($json.company || '').length ? 10 : 0) + (($json.budget || '').length ? 15 : 0) + (($json.page_url || '').length ? 5 : 0)) >= 70 ? '15-minute sales alert' : Math.min(100, ((($json.message || '') + ' ' + ($json.requested_timeline || '') + ' ' + ($json.budget || '')).match(/pricing|quote|proposal|demo|urgent|asap|this week|call|integration|crm|revenue|sales|budget|deadline/gi)?.length || 0) * 15 + (($json.email || '').includes('@') ? 20 : 0) + (($json.company || '').length ? 10 : 0) + (($json.budget || '').length ? 15 : 0) + (($json.page_url || '').length ? 5 : 0)) >= 40 ? 'same-day review' : 'next business day' }}"
            },
            {
              "id": "priority",
              "name": "priority",
              "type": "string",
              "value": "={{ Math.min(100, ((($json.message || '') + ' ' + ($json.requested_timeline || '') + ' ' + ($json.budget || '')).match(/pricing|quote|proposal|demo|urgent|asap|this week|call|integration|crm|revenue|sales|budget|deadline/gi)?.length || 0) * 15 + (($json.email || '').includes('@') ? 20 : 0) + (($json.company || '').length ? 10 : 0) + (($json.budget || '').length ? 15 : 0) + (($json.page_url || '').length ? 5 : 0)) >= 70 ? 'hot' : Math.min(100, ((($json.message || '') + ' ' + ($json.requested_timeline || '') + ' ' + ($json.budget || '')).match(/pricing|quote|proposal|demo|urgent|asap|this week|call|integration|crm|revenue|sales|budget|deadline/gi)?.length || 0) * 15 + (($json.email || '').includes('@') ? 20 : 0) + (($json.company || '').length ? 10 : 0) + (($json.budget || '').length ? 15 : 0) + (($json.page_url || '').length ? 5 : 0)) >= 40 ? 'review' : 'normal' }}"
            }
          ]
        },
        "includeOtherFields": true
      },
      "typeVersion": 3.4
    },
    {
      "id": "dedupe-key",
      "name": "Build Dedupe Key",
      "type": "n8n-nodes-base.set",
      "position": [
        300,
        -80
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "dedupe",
              "name": "dedupe_key",
              "type": "string",
              "value": "={{ [($json.email || $json.phone || 'unknown').toLowerCase(), ($json.company || 'no-company').toLowerCase(), $now.toFormat('yyyy-LL-dd')].join('|') }}"
            },
            {
              "id": "owner",
              "name": "routing_owner",
              "type": "string",
              "value": "={{ $json.priority === 'hot' ? 'sales' : $json.priority === 'review' ? 'ops-review' : 'nurture' }}"
            }
          ]
        },
        "includeOtherFields": true
      },
      "typeVersion": 3.4
    },
    {
      "id": "append-audit-row",
      "name": "Append Lead Audit Row",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        520,
        -80
      ],
      "parameters": {
        "columns": {
          "value": {
            "name": "={{ $json.name }}",
            "email": "={{ $json.email }}",
            "phone": "={{ $json.phone }}",
            "budget": "={{ $json.budget }}",
            "source": "={{ $json.source }}",
            "company": "={{ $json.company }}",
            "message": "={{ $json.message }}",
            "page_url": "={{ $json.page_url }}",
            "priority": "={{ $json.priority }}",
            "dedupe_key": "={{ $json.dedupe_key }}",
            "lead_score": "={{ $json.lead_score }}",
            "sla_bucket": "={{ $json.sla_bucket }}",
            "received_at": "={{ $json.received_at }}",
            "routing_owner": "={{ $json.routing_owner }}",
            "intent_matches": "={{ $json.intent_matches }}",
            "requested_timeline": "={{ $json.requested_timeline }}"
          },
          "mappingMode": "defineBelow"
        },
        "options": {},
        "operation": "append",
        "sheetName": "Lead audit",
        "documentId": "YOUR_SPREADSHEET_ID"
      },
      "typeVersion": 4.5
    },
    {
      "id": "hot-lead-check",
      "name": "Hot Lead?",
      "type": "n8n-nodes-base.if",
      "position": [
        740,
        -80
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "number": [
            {
              "value1": "={{ $json.lead_score }}",
              "value2": 70,
              "operation": "largerEqual"
            }
          ]
        }
      },
      "typeVersion": 2
    },
    {
      "id": "build-hot-alert",
      "name": "Build Hot Lead Alert",
      "type": "n8n-nodes-base.set",
      "position": [
        960,
        -240
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "alert-subject",
              "name": "alert_subject",
              "type": "string",
              "value": "={{ '[HOT lead ' + $json.lead_score + '] ' + ($json.company || $json.name || $json.email || 'Website lead') }}"
            },
            {
              "id": "alert-body",
              "name": "alert_body",
              "type": "string",
              "value": "={{ 'Lead score: ' + $json.lead_score + '\\nSLA: ' + $json.sla_bucket + '\\nOwner: ' + $json.routing_owner + '\\nName: ' + $json.name + '\\nEmail: ' + $json.email + '\\nPhone: ' + $json.phone + '\\nCompany: ' + $json.company + '\\nBudget: ' + $json.budget + '\\nTimeline: ' + $json.requested_timeline + '\\nSource: ' + $json.source + '\\nPage: ' + $json.page_url + '\\n\\nMessage:\\n' + $json.message }}"
            }
          ]
        },
        "includeOtherFields": true
      },
      "typeVersion": 3.4
    },
    {
      "id": "send-hot-alert",
      "name": "Send Hot Lead Gmail Alert",
      "type": "n8n-nodes-base.gmail",
      "position": [
        1180,
        -240
      ],
      "parameters": {
        "sendTo": "sales@example.com",
        "message": "={{ $json.alert_body }}",
        "subject": "={{ $json.alert_subject }}"
      },
      "typeVersion": 2.1
    },
    {
      "id": "review-lead-check",
      "name": "Needs Human Review?",
      "type": "n8n-nodes-base.if",
      "position": [
        960,
        20
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "number": [
            {
              "value1": "={{ $json.lead_score }}",
              "value2": 40,
              "operation": "largerEqual"
            }
          ]
        }
      },
      "typeVersion": 2
    },
    {
      "id": "send-review-alert",
      "name": "Send Review Queue Gmail Alert",
      "type": "n8n-nodes-base.gmail",
      "position": [
        1180,
        20
      ],
      "parameters": {
        "sendTo": "ops-review@example.com",
        "message": "={{ 'Review this same-day lead.\\nScore: ' + $json.lead_score + '\\nSLA: ' + $json.sla_bucket + '\\nName: ' + $json.name + '\\nEmail: ' + $json.email + '\\nCompany: ' + $json.company + '\\nMessage: ' + $json.message }}",
        "subject": "={{ '[Review lead ' + $json.lead_score + '] ' + ($json.company || $json.name || $json.email || 'Website lead') }}"
      },
      "typeVersion": 2.1
    },
    {
      "id": "respond-success",
      "name": "Respond Success",
      "type": "n8n-nodes-base.respondToWebhook",
      "position": [
        1420,
        -80
      ],
      "parameters": {
        "options": {},
        "respondWith": "json",
        "responseBody": "={{ { ok: true, priority: $json.priority, lead_score: $json.lead_score, sla: $json.sla_bucket, routing_owner: $json.routing_owner } }}"
      },
      "typeVersion": 1.1
    }
  ],
  "settings": {
    "executionOrder": "v1"
  },
  "updatedAt": "2026-06-02T00:00:00.000Z",
  "versionId": "tinyops-lead-rescue-desk-v2",
  "staticData": null,
  "connections": {
    "Hot Lead?": {
      "main": [
        [
          {
            "node": "Build Hot Lead Alert",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Needs Human Review?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Has Contact?": {
      "main": [
        [
          {
            "node": "Score Lead and SLA",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Build Missing Contact Response",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Lead Webhook": {
      "main": [
        [
          {
            "node": "Normalize Payload",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Build Dedupe Key": {
      "main": [
        [
          {
            "node": "Append Lead Audit Row",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Normalize Payload": {
      "main": [
        [
          {
            "node": "Has Contact?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Score Lead and SLA": {
      "main": [
        [
          {
            "node": "Build Dedupe Key",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Needs Human Review?": {
      "main": [
        [
          {
            "node": "Send Review Queue Gmail Alert",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Respond Success",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Build Hot Lead Alert": {
      "main": [
        [
          {
            "node": "Send Hot Lead Gmail Alert",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Append Lead Audit Row": {
      "main": [
        [
          {
            "node": "Hot Lead?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Send Hot Lead Gmail Alert": {
      "main": [
        [
          {
            "node": "Respond Success",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Send Review Queue Gmail Alert": {
      "main": [
        [
          {
            "node": "Respond Success",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Build Missing Contact Response": {
      "main": [
        [
          {
            "node": "Respond Missing Contact",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "triggerCount": 1
}
Pro

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

About this workflow

This workflow receives website lead submissions via webhook, normalizes the payload, validates contact details, scores intent to assign an SLA bucket, logs each lead to Google Sheets, and routes hot and review leads to the right inbox using Gmail. Receives a POST request on a…

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

More Marketing & Ads workflows → · Browse all categories →

Related workflows

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

Marketing & Ads

Automatically qualify, score, and route inbound B2B leads using GPT-4o-mini — no manual review needed.

HTTP Request, Slack, Gmail +1
Marketing & Ads

I created this workflow with great care to help you simplify your daily reporting routine. If you manage Meta Ads campaigns, you know how time-consuming it can be to open Ads Manager, filter data, and

Google Sheets, Facebook Graph Api, Gmail +1
Marketing & Ads

This workflow automates the import of leads into the Company table of a CRM built with Airtable.

Data Table, Gmail, Airtable +1
Marketing & Ads

Find companies similar to your best clients using PredictLeads, enrich each with news, hiring, and tech signals, then score them 0–100 for outreach priority.

Google Sheets, @Predictleads/N8N Nodes Predictleads, Slack +2
Marketing & Ads

This workflow runs a FindMyClient search for a keyword, polls for completion, and extracts any returned email addresses. If emails are found, it splits them into individual items for optional saving t

Gmail, Google Sheets, HTTP Request +1