AutomationFlowsMarketing & Ads › Google Maps Leads with AI Enrichment and Google Sheets

Google Maps Leads with AI Enrichment and Google Sheets

Original n8n title: Generate Google Maps Leads with AI Enrichment, Social Discovery and Google Sheets

ByShreya Bhingarkar @shreya-bhingarkar on n8n.io

This workflow finds local businesses from Google Maps and automatically enriches them with emails, social profiles, AI summaries, and personalized outreach messages — all saved to Google Sheets. Searches Google Maps using your custom queries Scrapes each business website for…

Event trigger★★★★★ complexity43 nodesHTTP RequestGoogle Sheets
Marketing & Ads Trigger: Event Nodes: 43 Complexity: ★★★★★ Added:

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

This workflow follows the Google Sheets → HTTP Request 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
{
  "id": "87VrJWai99TrGzDT",
  "meta": {
    "templateCredsSetupCompleted": true
  },
  "name": "Automate Google Maps lead generation with AI enrichment and social discovery",
  "tags": [],
  "nodes": [
    {
      "id": "a498ba50-4265-4fe0-8bba-c4ed4d53e57d",
      "name": "Sticky Note1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        4096,
        2576
      ],
      "parameters": {
        "width": 704,
        "height": 512,
        "content": "## AI Lead Generation & Social Discovery Automation\n\nInstead of spending hours researching businesses manually, this workflow \ndoes it all automatically. Give it a search query and it finds businesses \nfrom Google Maps, grabs their emails and social profiles, validates the \ncontacts, runs each one through AI, writes a personalized outreach message, \nscores the lead, and saves everything to Google Sheets.\n\n## What you need\n\n- Serper API key\n- Google Sheets OAuth\n- Email validation API\n- Ollama or any LLM endpoint\n\n## How to run\n\n- Connect your credentials in n8n\n- Add your Google Sheet ID to the Sheets node\n- Add search queries like \"dentists in Pune\" or \"gyms in Delhi\"\n- Hit run and leads start filling your sheet automatically"
      },
      "typeVersion": 1
    },
    {
      "id": "a24df743-e934-498b-b057-38725f0f20c6",
      "name": "Start Lead Generation",
      "type": "n8n-nodes-base.manualTrigger",
      "position": [
        3968,
        3696
      ],
      "parameters": {},
      "typeVersion": 1
    },
    {
      "id": "902a7fb0-320c-4aec-934a-e20113c61d75",
      "name": "Split Search Queries",
      "type": "n8n-nodes-base.splitOut",
      "position": [
        4192,
        3696
      ],
      "parameters": {
        "options": {
          "destinationFieldName": "query"
        },
        "fieldToSplitOut": "queries"
      },
      "typeVersion": 1
    },
    {
      "id": "18d7d04b-aa4a-40b4-984a-0f7db85401da",
      "name": "Process Each Query",
      "type": "n8n-nodes-base.splitInBatches",
      "position": [
        4416,
        3696
      ],
      "parameters": {
        "options": {}
      },
      "typeVersion": 3
    },
    {
      "id": "6ff40d64-ca53-4db6-8f50-e96fca8b6689",
      "name": "Rate Limit Protection",
      "type": "n8n-nodes-base.wait",
      "position": [
        4640,
        3744
      ],
      "parameters": {
        "amount": 3
      },
      "typeVersion": 1.1
    },
    {
      "id": "11340894-ebf8-4ba8-92f2-396cc0645d5c",
      "name": "Fetch Maps Results (Page 1)",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        4640,
        3552
      ],
      "parameters": {
        "url": "=https://google.serper.dev/maps?q={{ $json.query }}",
        "options": {},
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth"
      },
      "executeOnce": false,
      "typeVersion": 4.2,
      "alwaysOutputData": false
    },
    {
      "id": "c6b2ac66-8f27-49cf-9a30-5662cd74fd59",
      "name": "Fetch Maps Results (Pages 2\u201312)",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        5088,
        3552
      ],
      "parameters": {
        "url": "=https://google.serper.dev/maps",
        "method": "POST",
        "options": {},
        "jsonBody": "=[\n  {\"q\": \"{{ $json.searchParameters.q }}\", \"page\": 2, \"ll\": \"{{ $json.ll }}\"},\n  {\"q\": \"{{ $json.searchParameters.q }}\", \"page\": 3, \"ll\": \"{{ $json.ll }}\"},\n  {\"q\": \"{{ $json.searchParameters.q }}\", \"page\": 4, \"ll\": \"{{ $json.ll }}\"},\n {\"q\": \"{{ $json.searchParameters.q }}\", \"page\": 5, \"ll\": \"{{ $json.ll }}\"},\n {\"q\": \"{{ $json.searchParameters.q }}\", \"page\": 6, \"ll\": \"{{ $json.ll }}\"},\n {\"q\": \"{{ $json.searchParameters.q }}\", \"page\": 7, \"ll\": \"{{ $json.ll }}\"},\n {\"q\": \"{{ $json.searchParameters.q }}\", \"page\": 8, \"ll\": \"{{ $json.ll }}\"},\n {\"q\": \"{{ $json.searchParameters.q }}\", \"page\": 9, \"ll\": \"{{ $json.ll }}\"},\n {\"q\": \"{{ $json.searchParameters.q }}\", \"page\": 10, \"ll\": \"{{ $json.ll }}\"},\n {\"q\": \"{{ $json.searchParameters.q }}\", \"page\": 11, \"ll\": \"{{ $json.ll }}\"},\n {\"q\": \"{{ $json.searchParameters.q }}\", \"page\": 12, \"ll\": \"{{ $json.ll }}\"}\n]",
        "sendBody": true,
        "specifyBody": "json",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth"
      },
      "executeOnce": false,
      "typeVersion": 4.2,
      "alwaysOutputData": false
    },
    {
      "id": "064b2527-ef68-4e60-a2dd-1cc4a71394a4",
      "name": "Extract Businesses From Maps",
      "type": "n8n-nodes-base.code",
      "position": [
        5312,
        3552
      ],
      "parameters": {
        "jsCode": "const results = [...$('Fetch Maps Results (Page 1)').all(), ...$input.all()];\nconst companies = [];\n\nresults.forEach(item => {\n  if (item.json.places) {\n    item.json.places.forEach(place => {\n      if (place.phoneNumber) {\n        const company = {\n          name: place.title || '',\n          phone: place.phoneNumber || '',\n          website: place.website,\n          rating: place.rating || null,\n          type: place.type || ''\n        };\n        \n        companies.push(company);\n      }\n    });\n  }\n});\n\nreturn companies.map(company => ({json: company}));"
      },
      "typeVersion": 2
    },
    {
      "id": "7f5f717e-ab1a-4f06-92f2-aac13ca72e33",
      "name": "Process Businesses in Batches",
      "type": "n8n-nodes-base.splitInBatches",
      "position": [
        5536,
        3552
      ],
      "parameters": {
        "options": {},
        "batchSize": 5
      },
      "typeVersion": 3
    },
    {
      "id": "c57c2dd2-b415-4088-9eb8-07b25d10736b",
      "name": "Scrape Business Website",
      "type": "n8n-nodes-base.httpRequest",
      "onError": "continueRegularOutput",
      "position": [
        5760,
        3504
      ],
      "parameters": {
        "url": "={{ $json.website }}",
        "options": {
          "allowUnauthorizedCerts": true
        },
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "User-Agent",
              "value": "Mozilla/5.0"
            },
            {
              "name": "Accept",
              "value": "text/html"
            },
            {
              "name": "Accept-Language",
              "value": "en-US,en;q=0.9"
            }
          ]
        }
      },
      "typeVersion": 4.2,
      "alwaysOutputData": false
    },
    {
      "id": "c1a67e75-d5e1-4e86-944b-6dc0e24647cb",
      "name": "Extract Socials & Email",
      "type": "n8n-nodes-base.code",
      "position": [
        5984,
        3504
      ],
      "parameters": {
        "mode": "runOnceForEachItem",
        "jsCode": "// scan entire HTML + response text\nconst html = JSON.stringify($json).toLowerCase();\n\nfunction extract(patterns) {\n  for (const pattern of patterns) {\n    const matches = html.match(pattern);\n    if (matches && matches.length) {\n      return clean(matches[0]);\n    }\n  }\n  return \"-\";\n}\n\n// remove tracking & redirect junk\nfunction clean(url) {\n  return url\n    .replace(/\\\\u002f/g, \"/\")\n    .split(\"?\")[0]\n    .replace(/amp;/g, \"\");\n}\n\n// ---------- SOCIAL DETECTION ----------\n\n// Instagram\nconst instagram = extract([\n  /https?:\\/\\/[^\"'<> ]*instagram\\.com[^\"'<> ]*/gi,\n  /instagr\\.am\\/[^\"'<> ]*/gi,\n  /instagram:[^\"'<> ]+/gi\n]);\n\n// Facebook\nconst facebook = extract([\n  /https?:\\/\\/[^\"'<> ]*facebook\\.com[^\"'<> ]*/gi,\n  /fb\\.com\\/[^\"'<> ]*/gi\n]);\n\n// LinkedIn\nconst linkedin = extract([\n  /https?:\\/\\/[^\"'<> ]*linkedin\\.com[^\"'<> ]*/gi\n]);\n\n// Twitter / X\nconst twitter = extract([\n  /https?:\\/\\/[^\"'<> ]*(twitter|x)\\.com[^\"'<> ]*/gi\n]);\n\n// YouTube\nconst youtube = extract([\n  /https?:\\/\\/[^\"'<> ]*youtube\\.com[^\"'<> ]*/gi\n]);\n\n// TikTok\nconst tiktok = extract([\n  /https?:\\/\\/[^\"'<> ]*tiktok\\.com[^\"'<> ]*/gi\n]);\n\n\n// ---------- EMAIL EXTRACTION ----------\nfunction extractEmails(html) {\n\n  const text = html\n    .replace(/\\[at\\]/gi, \"@\")\n    .replace(/\\(at\\)/gi, \"@\")\n    .replace(/\\s+at\\s+/gi, \"@\")\n    .replace(/\\[dot\\]/gi, \".\")\n    .replace(/\\(dot\\)/gi, \".\")\n    .replace(/\\s+dot\\s+/gi, \".\");\n\n  const emailRegex = /mailto:([a-z0-9._%+-]+@[a-z0-9.-]+\\.[a-z]{2,})|([a-z0-9._%+-]+@[a-z0-9.-]+\\.[a-z]{2,})/gi;\n\n  const matches = [...text.matchAll(emailRegex)].map(m => m[1] || m[2]);\n\n  const badPatterns = [\n    \"example.com\",\n    \"domain.com\",\n    \"email.com\",\n    \"test.com\",\n    \"user@\",\n    \"admin@domain\",\n    \"yourname@\",\n    \"noreply@\",\n    \"donotreply@\"\n  ];\n\n  const cleaned = matches\n    .map(e => e.toLowerCase().trim())\n    .filter(e =>\n      e &&\n      !badPatterns.some(b => e.includes(b))\n    );\n\n  return [...new Set(cleaned)];\n}\n\n// run extraction\nconst emails = extractEmails(html);\n\n// choose best email\nconst email = emails.length ? emails[0] : \"\";\n\n\nreturn {\n  ...$('Extract Businesses From Maps').item.json,\n  email,\n  instagram,\n  facebook,\n  linkedin,\n  twitter,\n  youtube,\n  tiktok\n};\n"
      },
      "typeVersion": 2
    },
    {
      "id": "5bffb9ef-99c4-4adf-a72f-6f788ffa7345",
      "name": "Build Social Search Query",
      "type": "n8n-nodes-base.code",
      "position": [
        6208,
        3568
      ],
      "parameters": {
        "mode": "runOnceForEachItem",
        "jsCode": "// Business name\nconst name = ($json.name || \"\").replace(/[^\\w\\s]/gi, '').trim();\n\n\n// Optional: add city if available\nconst city = ($json.city || \"\").trim();\n\n// Remove accents (\u00e9 \u2192 e)\nconst normalize = str =>\n  str.normalize(\"NFD\").replace(/[\\u0300-\\u036f]/g, \"\");\n\n// Clean symbols & punctuation\nconst clean = normalize(name)\n  .replace(/&/g, \" and \")\n  .replace(/\\+/g, \" and \")\n  .replace(/@/g, \"\")\n  .replace(/'/g, \"\")\n  .replace(/\\./g, \"\")\n  .replace(/-/g, \" \")\n  .replace(/\\//g, \" \")\n  .replace(/\\s+/g, \" \")\n  .trim();\n\n// Remove noise business words (optional but improves accuracy)\nconst noiseWords = [\n  \"cafe\",\"bakery\",\"restaurant\",\"shop\",\"store\",\n  \"kitchen\",\"bar\",\"grill\",\"coffee\",\"house\"\n];\n\nconst simplified = clean\n  .split(\" \")\n  .filter(w => !noiseWords.includes(w.toLowerCase()))\n  .join(\" \");\n\n// Build search variations\nconst variations = [\n  `\"${name}\"`,\n  `\"${clean}\"`,\n  `\"${simplified}\"`,\n  clean,\n  simplified\n];\n\n// Add location variations (if available)\nif (city) {\n  variations.push(`\"${name}\" ${city}`);\n  variations.push(`${clean} ${city}`);\n}\n\n// Remove duplicates & empty strings\nconst uniqueQueries = [...new Set(variations.filter(Boolean))];\n\n// Force search only social platforms\nconst socialSites = `\n(site:instagram.com OR\n site:facebook.com OR\n site:linkedin.com OR\n site:tiktok.com OR\n site:youtube.com OR\n site:twitter.com OR\n site:x.com)\n`;\n\n// Build final search query\nconst finalQuery = `${uniqueQueries.join(\" OR \")} ${socialSites}`;\n\nreturn {\n  ...$json,\n  smartQuery: finalQuery\n};\n"
      },
      "typeVersion": 2
    },
    {
      "id": "e126ee9c-24fe-430b-80f8-b9d747caf613",
      "name": "Search Social Profiles (Serper)",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        6432,
        3568
      ],
      "parameters": {
        "url": "=https://google.serper.dev/search",
        "method": "POST",
        "options": {},
        "jsonBody": "={\n  \"q\": \"{{ $json.name }}\",\n  \"num\": 10,\n  \"gl\": \"us\",\n  \"sites\": [\n    \"instagram.com\",\n    \"facebook.com\",\n    \"linkedin.com\",\n    \"tiktok.com\",\n    \"youtube.com\",\n    \"twitter.com\",\n    \"x.com\"\n  ]\n}\n",
        "sendBody": true,
        "specifyBody": "json",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth"
      },
      "typeVersion": 4.3
    },
    {
      "id": "3bee6f31-5e02-4b3d-b2b9-8c87598e46f1",
      "name": "Combine Website + Serper Results",
      "type": "n8n-nodes-base.merge",
      "position": [
        6880,
        3504
      ],
      "parameters": {
        "mode": "combine",
        "options": {},
        "combineBy": "combineByPosition"
      },
      "typeVersion": 3.2
    },
    {
      "id": "92f1e3c5-374d-413a-a25f-72344f013438",
      "name": "Resolve & Score Social Profiles",
      "type": "n8n-nodes-base.code",
      "position": [
        7104,
        3504
      ],
      "parameters": {
        "mode": "runOnceForEachItem",
        "jsCode": "function clean(url) {\n  if (!url || url === \"-\") return \"\";\n\n  const badPatterns = [\n    \"/p/\",              // instagram posts\n    \"/reel/\",\n    \"watch?v=\",         // youtube videos\n    \"youtu.be/\",\n    \"discover\",         // tiktok discover pages\n    \"/video/\",\n    \"/videos/\",\n    \"share\",\n    \"sharer\",\n    \"intent\",\n    \"status\"\n  ];\n\n  const lower = url.toLowerCase();\n\n  for (const bad of badPatterns) {\n    if (lower.includes(bad)) return \"\";\n  }\n\n  return url.trim().replace(/\\/$/, \"\");\n}\n\n\nfunction username(url){\n  if(!url) return \"\";\n  return url.split(\"/\").pop().toLowerCase();\n}\n\nfunction merge(primary, fallback) {\n\n  const p = clean(primary);\n  const f = clean(fallback);\n\n  // CASE: nothing found\n  if (!p && !f) {\n    return { url: \"-\", confidence: \"LOW\" };\n  }\n\n  // CASE: website only\n  if (p && !f) {\n    return { url: p, confidence: \"HIGH\" };\n  }\n\n  // CASE: serper only\n  if (!p && f) {\n    return { url: f, confidence: \"MEDIUM\" };\n  }\n\n  // CASE: both exist & same handle\n  if (username(p) === username(f)) {\n    return { url: p, confidence: \"HIGH\" };\n  }\n\n  // CASE: both exist but different\n  return {\n    url: `${p} | ${f}`,\n    confidence: \"MEDIUM\"\n  };\n}\n\nconst instagram = merge($json.instagram, $json.serperInstagram);\nconst facebook  = merge($json.facebook, $json.serperFacebook);\nconst linkedin  = merge($json.linkedin, $json.serperLinkedin);\nconst twitter   = merge($json.twitter, $json.serperTwitter);\nconst youtube   = merge($json.youtube, $json.serperYoutube);\nconst tiktok    = merge($json.tiktok, $json.serperTiktok);\n\nreturn {\n  ...$json,\n\n  instagram: instagram.url,\n  instagramConfidence: instagram.confidence,\n\n  facebook: facebook.url,\n  facebookConfidence: facebook.confidence,\n\n  linkedin: linkedin.url,\n  linkedinConfidence: linkedin.confidence,\n\n  twitter: twitter.url,\n  twitterConfidence: twitter.confidence,\n\n  youtube: youtube.url,\n  youtubeConfidence: youtube.confidence,\n\n  tiktok: tiktok.url,\n  tiktokConfidence: tiktok.confidence\n};\n"
      },
      "typeVersion": 2
    },
    {
      "id": "0e5af8e1-4520-42a6-918b-56496fc45042",
      "name": "Normalize Social Data",
      "type": "n8n-nodes-base.code",
      "position": [
        7328,
        3616
      ],
      "parameters": {
        "mode": "runOnceForEachItem",
        "jsCode": "function safe(value) {\n  if (value === undefined || value === null) return \"-\";\n  if (typeof value === \"string\" && value.trim() === \"\") return \"-\";\n  return value;\n}\n\nreturn {\n  json: {\n\n    // BUSINESS INFO\n    name: safe($json.name),\n    website: safe($json.website),\n    phone: safe($json.phone),\n    rating: safe($json.rating),\n    type: safe($json.type),\n\n    // EMAIL (important for validation)\n    email: $json.email || \"\",\n\n    // SOCIALS\n    instagram: safe($json.instagram),\n    facebook: safe($json.facebook),\n    linkedin: safe($json.linkedin),\n    twitter: safe($json.twitter),\n    youtube: safe($json.youtube),\n    tiktok: safe($json.tiktok),\n\n    // CONFIDENCE\n    instagramConfidence: safe($json.instagramConfidence) || \"LOW\",\n    facebookConfidence: safe($json.facebookConfidence) || \"LOW\",\n    linkedinConfidence: safe($json.linkedinConfidence) || \"LOW\",\n    twitterConfidence: safe($json.twitterConfidence) || \"LOW\",\n    youtubeConfidence: safe($json.youtubeConfidence) || \"LOW\",\n    tiktokConfidence: safe($json.tiktokConfidence) || \"LOW\"\n\n  }\n};\n"
      },
      "typeVersion": 2
    },
    {
      "id": "f027a103-359c-411d-98cc-d95c40a340c2",
      "name": "Check Email Exists",
      "type": "n8n-nodes-base.if",
      "position": [
        5760,
        3184
      ],
      "parameters": {
        "options": {
          "ignoreCase": true
        },
        "conditions": {
          "options": {
            "version": 3,
            "leftValue": "",
            "caseSensitive": false,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "4c59052f-cbb3-4638-a74d-0337f1e063b2",
              "operator": {
                "type": "string",
                "operation": "notEmpty",
                "singleValue": true
              },
              "leftValue": "={{ $('Normalize Social Data').item.json.email }}",
              "rightValue": ""
            }
          ]
        }
      },
      "typeVersion": 2.3
    },
    {
      "id": "33f27175-50c5-4de4-bae2-105d959137e1",
      "name": "Validate Email Address",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        5984,
        3088
      ],
      "parameters": {
        "url": "https://rapid-email-verifier.fly.dev/api/validate",
        "options": {},
        "sendQuery": true,
        "queryParameters": {
          "parameters": [
            {
              "name": "email",
              "value": "={{ $json.email }}"
            }
          ]
        }
      },
      "typeVersion": 4.2
    },
    {
      "id": "44f062c2-40c2-41fb-aea9-70d67deca377",
      "name": "Prepare Valid Leads",
      "type": "n8n-nodes-base.code",
      "position": [
        6208,
        3088
      ],
      "parameters": {
        "mode": "runOnceForEachItem",
        "jsCode": "const original = $('Check Email Exists').item.json;\nconst result = $json.result || $json.status || \"\";\n\nlet email = original.email || \"\";\nlet emailStatus = \"VALID\";\n\nif (result === \"invalid\" || result === \"undeliverable\") {\n  email = \"\";\n  emailStatus = \"NOT_FOUND_OR_INVALID\";\n}\nelse if (result === \"risky\" || result === \"catch-all\" || result === \"unknown\") {\n  emailStatus = \"RISKY\";\n}\n\nreturn {\n  json: {\n\n    // BUSINESS INFO\n    name: original.name || \"-\",\n    website: original.website || \"-\",\n    phone: original.phone || \"-\",\n    rating: original.rating || \"-\",\n    type: original.type || \"-\",\n\n    // EMAIL\n    email,\n    emailStatus,\n\n    // SOCIALS\n    instagram: original.instagram || \"-\",\n    facebook: original.facebook || \"-\",\n    linkedin: original.linkedin || \"-\",\n    twitter: original.twitter || \"-\",\n    youtube: original.youtube || \"-\",\n    tiktok: original.tiktok || \"-\",\n\n    // CONFIDENCE\n    instagramConfidence: original.instagramConfidence || \"LOW\",\n    facebookConfidence: original.facebookConfidence || \"LOW\",\n    linkedinConfidence: original.linkedinConfidence || \"LOW\",\n    twitterConfidence: original.twitterConfidence || \"LOW\",\n    youtubeConfidence: original.youtubeConfidence || \"LOW\",\n    tiktokConfidence: original.tiktokConfidence || \"LOW\"\n  }\n};\n"
      },
      "typeVersion": 2
    },
    {
      "id": "13adae03-f43a-46dc-a1f9-acf459a9c83a",
      "name": "Prepare Leads Without Email",
      "type": "n8n-nodes-base.code",
      "position": [
        6208,
        3280
      ],
      "parameters": {
        "mode": "runOnceForEachItem",
        "jsCode": "const data = $json;\n\nreturn {\n  json: {\n\n    // BUSINESS INFO\n    name: data.name || \"-\",\n    website: data.website || \"-\",\n    phone: data.phone || \"-\",\n    rating: data.rating || \"-\",\n    type: data.type || \"-\",\n\n    // EMAIL\n    email: \"\",\n    emailStatus: \"NOT_FOUND\",\n\n    // SOCIALS\n    instagram: data.instagram || \"-\",\n    facebook: data.facebook || \"-\",\n    linkedin: data.linkedin || \"-\",\n    twitter: data.twitter || \"-\",\n    youtube: data.youtube || \"-\",\n    tiktok: data.tiktok || \"-\",\n\n    // CONFIDENCE\n    instagramConfidence: data.instagramConfidence || \"LOW\",\n    facebookConfidence: data.facebookConfidence || \"LOW\",\n    linkedinConfidence: data.linkedinConfidence || \"LOW\",\n    twitterConfidence: data.twitterConfidence || \"LOW\",\n    youtubeConfidence: data.youtubeConfidence || \"LOW\",\n    tiktokConfidence: data.tiktokConfidence || \"LOW\"\n\n  }\n};\n"
      },
      "typeVersion": 2
    },
    {
      "id": "c35b21a1-368a-4995-83c4-6b053dfcae17",
      "name": "Merge Lead Results",
      "type": "n8n-nodes-base.merge",
      "position": [
        6432,
        3184
      ],
      "parameters": {},
      "typeVersion": 3.2
    },
    {
      "id": "0aca8824-eb80-4016-921b-1b4cc182a514",
      "name": "Clean Lead Data",
      "type": "n8n-nodes-base.code",
      "position": [
        6656,
        3184
      ],
      "parameters": {
        "mode": "runOnceForEachItem",
        "jsCode": "function safe(value) {\n  if (value === undefined || value === null) return \"-\";\n  if (typeof value === \"string\" && value.trim() === \"\") return \"-\";\n  return value;\n}\n\n\nfunction formatPhone(phone) {\n  if (!phone || phone === \"-\") return \"-\";\n\n  // remove everything except digits and +\n  phone = phone.toString().replace(/[^\\d+]/g, \"\");\n\n  // remove leading +\n  phone = phone.replace(/^\\+/, \"\");\n\n  // if too short \u2192 return original\n  if (phone.length < 7) return phone;\n\n  // detect country code (1\u20133 digits)\n  let countryCode = \"\";\n  let rest = \"\";\n\n  if (phone.length > 10) {\n    countryCode = phone.slice(0, phone.length - 10);\n    rest = phone.slice(-10);\n  } else {\n    rest = phone;\n  }\n\n  // US-style formatting\n  if (!countryCode && rest.length === 10) {\n    const formatted = `(${rest.slice(0,3)}) ${rest.slice(3,6)}-${rest.slice(6)}`;\n    return `'${formatted}`;   // force text for Sheets\n  }\n\n  // group remaining digits nicely\n  function groupDigits(num) {\n    return num.replace(/(\\d{2,4})(?=\\d)/g, \"$1 \");\n  }\n\n  const formattedIntl = `(+${countryCode}) ${groupDigits(rest)}`;\n\n  return `'${formattedIntl}`;   // force text for Sheets\n}\n\nreturn {\n  json: {\n\n    // BUSINESS INFO\n    website: safe($json.website),\n    name: safe($json.name),\n\n    //  PHONE SAFE FORMAT\n    phone: formatPhone($json.phone),\n\n    rating: safe($json.rating),\n    type: safe($json.type),\n\n    // EMAIL\n    email: safe($json.email),\n    emailStatus: safe($json.emailStatus),\n\n    // SOCIALS\n    instagram: safe($json.instagram),\n    facebook: safe($json.facebook),\n    linkedin: safe($json.linkedin),\n    twitter: safe($json.twitter),\n    youtube: safe($json.youtube),\n    tiktok: safe($json.tiktok),\n\n    // CONFIDENCE\n    instagramConfidence: safe($json.instagramConfidence),\n    facebookConfidence: safe($json.facebookConfidence),\n    linkedinConfidence: safe($json.linkedinConfidence),\n    twitterConfidence: safe($json.twitterConfidence),\n    youtubeConfidence: safe($json.youtubeConfidence),\n    tiktokConfidence: safe($json.tiktokConfidence)\n  }\n};\n"
      },
      "typeVersion": 2
    },
    {
      "id": "8dfe38e9-e49b-47aa-9458-977624a42c66",
      "name": "Remove Duplicate Leads",
      "type": "n8n-nodes-base.removeDuplicates",
      "position": [
        7104,
        3184
      ],
      "parameters": {
        "compare": "selectedFields",
        "options": {},
        "fieldsToCompare": "website, name"
      },
      "typeVersion": 2
    },
    {
      "id": "8d64b028-d665-47aa-8c81-e6875057c7f8",
      "name": "Process Leads for AI Enrichment",
      "type": "n8n-nodes-base.splitInBatches",
      "position": [
        7328,
        3184
      ],
      "parameters": {
        "options": {}
      },
      "typeVersion": 3
    },
    {
      "id": "eca95d2d-b2f1-4db3-9fc5-da4d3ab419cd",
      "name": "AI Rate Limit Buffer",
      "type": "n8n-nodes-base.wait",
      "position": [
        7552,
        3280
      ],
      "parameters": {
        "amount": 2
      },
      "typeVersion": 1.1
    },
    {
      "id": "285efe24-63a7-4171-bcae-c56cd996e076",
      "name": "Analyze Business (AI)",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        7776,
        3360
      ],
      "parameters": {
        "url": "YOUR_OLLAMA_URL/api/generate",
        "method": "POST",
        "options": {
          "timeout": 60000
        },
        "jsonBody": "={\n  \"model\": \"llama3\",\n  \"prompt\": \"Analyze this local business.\\n\\nBusiness Name: {{$json.name}}\\nBusiness Type: {{$json.type}}\\nWebsite: {{$json.website}}\\nRating: {{$json.rating}}\\n\\nIf website or details are missing, intelligently infer from the business name and type.\\n\\nReturn ONLY valid JSON:\\n{\\n  \\\"services\\\": [\\\"\\\", \\\"\\\"],\\n  \\\"summary\\\": \\\"\\\"\\n}\\n\\nRules:\\n- services must list main offerings\\n- summary must be ONE natural sentence\\n- summary must be specific to the business\\n- DO NOT repeat the same phrasing\\n- DO NOT leave fields empty\\n- infer logically if data missing\\n- DO NOT include markdown\\n- RETURN ONLY JSON\",\n  \"stream\": false\n}\n",
        "sendBody": true,
        "sendHeaders": true,
        "specifyBody": "json",
        "headerParameters": {
          "parameters": [
            {
              "name": "Content-Type",
              "value": "application/json"
            }
          ]
        }
      },
      "typeVersion": 4.3
    },
    {
      "id": "0f9d44a1-c641-407f-927d-07a38d44dd95",
      "name": "Extract AI Insights",
      "type": "n8n-nodes-base.code",
      "position": [
        7552,
        3088
      ],
      "parameters": {
        "mode": "runOnceForEachItem",
        "jsCode": "let raw = $json.response || \"\";\n\n// extract JSON block\nconst match = raw.match(/\\{[\\s\\S]*\\}/);\n\nlet parsed = null;\n\ntry {\n  parsed = match ? JSON.parse(match[0]) : null;\n} catch (e) {\n  parsed = null;\n}\n\n// fallback intelligent generation\nconst services =\n  parsed?.services?.length\n    ? parsed.services.join(\", \")\n    : ($json.type\n        ? `${$json.type}`\n        : \"Local business services\");\n\nconst summary =\n  parsed?.summary\n    ? parsed.summary\n    : `${$json.name} is a local ${$json.type || \"business\"} offering quality products and services to its community.`;\n\nreturn {\n  ...$json,\n  services,\n  summary\n};\n"
      },
      "typeVersion": 2
    },
    {
      "id": "7b60da04-555b-4a0a-a4c3-d403cb2790e7",
      "name": "AI Processing Buffer",
      "type": "n8n-nodes-base.wait",
      "position": [
        7776,
        3088
      ],
      "parameters": {
        "amount": 2
      },
      "typeVersion": 1.1
    },
    {
      "id": "f8a55c25-8130-4be0-9bb7-1e1e96b3e9d5",
      "name": "Message Rate Limit Buffer",
      "type": "n8n-nodes-base.wait",
      "position": [
        8224,
        3184
      ],
      "parameters": {
        "amount": 2
      },
      "typeVersion": 1.1
    },
    {
      "id": "3f824c01-673c-42c8-86b8-c23b7cdd6f27",
      "name": "Process Outreach Message",
      "type": "n8n-nodes-base.splitInBatches",
      "position": [
        8000,
        3088
      ],
      "parameters": {
        "options": {}
      },
      "typeVersion": 3
    },
    {
      "id": "b75c29df-2a74-4ab1-8486-4525dd809616",
      "name": "Create Outreach Message",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        8448,
        3264
      ],
      "parameters": {
        "url": "YOUR_OLLAMA_URL/api/generate",
        "method": "POST",
        "options": {},
        "jsonBody": "={\n  \"model\": \"llama3\",\n  \"prompt\": \"Write a short, friendly outreach message to the business below.\\n\\nBusiness Name: {{ $('Clean Lead Data').item.json.name }}\\nBusiness Type: {{ $('Clean Lead Data').item.json.type }}\\nWebsite: {{ $('Clean Lead Data').item.json.website }}\\n\\nYou are an AI automation specialist reaching out to help them improve operations and increase bookings.\\n\\nThe message should:\\n- be 2\u20133 sentences\\n- sound natural and human\\n- NOT sound salesy or spammy\\n- suggest improving bookings, customer engagement, or reducing manual work\\n- NOT pretend to be the business\\n- NOT mention AI unless natural\\n\\nReturn ONLY the message text.\",\n  \"stream\": false\n}\n",
        "sendBody": true,
        "specifyBody": "json"
      },
      "typeVersion": 4.3
    },
    {
      "id": "03ddeb4d-9d21-401e-8978-e0371e66bd35",
      "name": "Clean Outreach Message",
      "type": "n8n-nodes-base.code",
      "position": [
        8224,
        2992
      ],
      "parameters": {
        "mode": "runOnceForEachItem",
        "jsCode": "let msg = $json.response || \"\";\n\n// remove intro lines AI sometimes adds\nmsg = msg.replace(/Here is.*?:/i, \"\").trim();\n\n// remove quotes\nmsg = msg.replace(/^[\"']|[\"']$/g, \"\");\n\n// remove extra newlines\nmsg = msg.replace(/\\n+/g, \" \");\n\nreturn {\n  json: {\n    ...$json,\n    message: msg\n  }\n};\n"
      },
      "typeVersion": 2
    },
    {
      "id": "891b1d6d-3178-41c9-99b0-9d29f35f70ce",
      "name": "Calculate Lead Score",
      "type": "n8n-nodes-base.code",
      "position": [
        6880,
        3184
      ],
      "parameters": {
        "mode": "runOnceForEachItem",
        "jsCode": "let score = 0;\n\nconst website = $json.website || \"-\";\nconst rating = Number($json.rating) || 0;\nconst emailStatus = $json.emailStatus || \"NOT_FOUND\";\n\nconst socials = [\n  $json.instagram,\n  $json.facebook,\n  $json.linkedin,\n  $json.twitter,\n  $json.tiktok,\n  $json.youtube\n];\n\n// count socials safely\nconst activeSocials = socials.filter(\n  s => s && s !== \"-\" && s !== \"\"\n).length;\n\n\n//  WEBSITE (credibility)\nif (website !== '-') score += 2;\nelse score += 1; // still a valid lead\n\n\n// INSTAGRAM (best outreach channel)\nif ($json.instagram && $json.instagram !== \"-\") score += 2;\n\n\n// MULTIPLE SOCIALS (brand presence)\nif (activeSocials >= 3) score += 3;\nelse if (activeSocials >= 1) score += 2;\n\n\n// RATING QUALITY\nif (rating >= 4.6) score += 2;\nelse if (rating >= 4.2) score += 1;\n\n\n// EMAIL VALIDITY (direct contact)\nif (emailStatus === \"VALID\") score += 1;\n\n\n// GROWTH OPPORTUNITY BONUS\n// businesses without socials are easy wins\nif (activeSocials === 0) score += 1;\n\nif ($json.instagram && website === '-') score += 2;\n\n\n\n// normalize to max 10\nscore = Math.min(Math.round(score), 10);\n\nreturn {\n  ...$json,\n  leadScore: `${score}/10`\n};\n"
      },
      "typeVersion": 2
    },
    {
      "id": "5f656814-7906-4382-9b85-6fe8b49121d8",
      "name": "Save Leads to Google Sheets",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        8448,
        2992
      ],
      "parameters": {
        "columns": {
          "value": {
            "Name": "={{ $('Clean Lead Data').item.json.name }}",
            "Type": "={{ $('Clean Lead Data').item.json.type }}",
            "Email": "={{ $('Clean Lead Data').item.json.email }}",
            "Phone": "={{ $('Clean Lead Data').item.json.phone }}",
            "Rating": "={{ $('Clean Lead Data').item.json.rating }}",
            "Tiktok": "={{ $('Clean Lead Data').item.json.tiktok }}",
            "Twitter": "={{ $('Clean Lead Data').item.json.twitter }}",
            "Website": "={{ $('Clean Lead Data').item.json.website }}",
            "Youtube": "={{ $('Clean Lead Data').item.json.youtube }}",
            "Facebook": "={{ $('Clean Lead Data').item.json.facebook }}",
            "Linkedin": "={{ $('Clean Lead Data').item.json.linkedin }}",
            "Instagram": "={{ $('Clean Lead Data').item.json.instagram }}",
            "LeadScore": "={{ $('Calculate Lead Score').item.json.leadScore }}",
            "Summary (AI)": "={{ $('Extract AI Insights').item.json.summary }}",
            "Email\n Status": "={{ $('Clean Lead Data').item.json.emailStatus }}",
            "Services (AI)": "={{ $('Extract AI Insights').item.json.services }}",
            "Draft message (AI)": "={{ $('Clean Outreach Message').item.json.message }}",
            "Tiktok\n Confidence": "={{ $('Clean Lead Data').item.json.tiktokConfidence }}",
            "Twitter\n Confidence": "={{ $('Clean Lead Data').item.json.twitterConfidence }}",
            "Youtube\n Confidence": "={{ $('Clean Lead Data').item.json.youtubeConfidence }}",
            "Facebook\n Confidence": "={{ $('Clean Lead Data').item.json.facebookConfidence }}",
            "Linkedln\n Confidence": "={{ $('Clean Lead Data').item.json.linkedinConfidence }}",
            "Instagram\n Confidence": "={{ $('Clean Lead Data').item.json.instagramConfidence }}"
          },
          "schema": [
            {
              "id": "Name",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "Name",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Website",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "Website",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Email",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "Email",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Email\n Status",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "Email\n Status",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Phone",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "Phone",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Rating",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "Rating",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Type",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "Type",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Instagram",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "Instagram",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Instagram\n Confidence",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "Instagram\n Confidence",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Facebook",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "Facebook",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Facebook\n Confidence",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "Facebook\n Confidence",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Linkedin",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "Linkedin",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Linkedln\n Confidence",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "Linkedln\n Confidence",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Twitter",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "Twitter",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Twitter\n Confidence",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "Twitter\n Confidence",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Tiktok",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "Tiktok",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Tiktok\n Confidence",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "Tiktok\n Confidence",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Youtube",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "Youtube",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Youtube\n Confidence",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "Youtube\n Confidence",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Services (AI)",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "Services (AI)",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Summary (AI)",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "Summary (AI)",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "LeadScore",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "LeadScore",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Draft message (AI)",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "Draft message (AI)",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            }
          ],
          "mappingMode": "defineBelow",
          "matchingColumns": [
            "Name"
          ],
          "attemptToConvertTypes": false,
          "convertFieldsToString": false
        },
        "options": {},
        "operation": "appendOrUpdate",
        "sheetName": {
          "__rl": true,
          "mode": "id",
          "value": "YOUR_GOOGLE_SHEET_ID"
        },
        "documentId": {
          "__rl": true,
          "mode": "id",
          "value": "YOUR_GOOGLE_SHEET_ID"
        }
      },
      "typeVersion": 4.5
    },
    {
      "id": "86d72dd9-c732-49fd-9c2c-05b4eb0f9673",
      "name": "Parse Social Results",
      "type": "n8n-nodes-base.code",
      "position": [
        6656,
        3568
      ],
      "parameters": {
        "mode": "runOnceForEachItem",
        "jsCode": "const text = JSON.stringify($json);\n\n// find first match helper\nfunction find(pattern) {\n  const match = text.match(pattern);\n  return match ? match[0] : \"-\";\n}\n\nreturn {\n  ...$json,\n\n  serperInstagram: find(/https?:\\/\\/(www\\.)?instagram\\.com\\/(?!reel|p\\/)[^\"& ]+/i),\n\n  serperFacebook: find(/https?:\\/\\/(www\\.)?facebook\\.com\\/(?!share|sharer|videos)[^\"& ]+/i),\n\n  serperLinkedin: find(/https?:\\/\\/(www\\.)?linkedin\\.com\\/company\\/[^\"& ]+/i),\n\n  serperTwitter: find(/https?:\\/\\/(twitter|x)\\.com\\/[^\"& ]+/i),\n\n  serperYoutube: find(/https?:\\/\\/(www\\.)?youtube\\.com\\/(@|channel|c\\/)[^\"& ]+/i),\n\n  serperTiktok: find(/https?:\\/\\/(www\\.)?tiktok\\.com\\/@[^\"& ]+/i)\n};\n"
      },
      "typeVersion": 2
    },
    {
      "id": "cd98522f-0ff0-4960-aa54-7a7bbc773f2b",
      "name": "Delay Between Requests",
      "type": "n8n-nodes-base.wait",
      "position": [
        4864,
        3552
      ],
      "parameters": {},
      "typeVersion": 1.1
    },
    {
      "id": "46cefa60-90ba-4903-b747-13093cc56cbf",
      "name": "Sticky Note",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        4784,
        3328
      ],
      "parameters": {
        "color": 7,
        "width": 256,
        "content": "## Maps section:\nSearches Google Maps using your queries and pulls business name, \nwebsite, phone number, rating and category for each result.\n"
      },
      "typeVersion": 1
    },
    {
      "id": "cb596e41-46da-4a1a-a808-63bb875537ba",
      "name": "Sticky Note2",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        5728,
        3840
      ],
      "parameters": {
        "color": 7,
        "content": "## Scraping section:\nVisits each business website and looks for emails and social media \nlinks. Handles broken pages and redirects automatically."
      },
      "typeVersion": 1
    },
    {
      "id": "e2535f29-7adf-4642-b594-151ec0301eda",
      "name": "Sticky Note3",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        6896,
        3840
      ],
      "parameters": {
        "color": 7,
        "content": "## Social section:\nIf social profiles are not found on the website, it runs a \nseparate search to find Instagram, Facebook, LinkedIn, Twitter, \nTikTok and YouTube with a confidence score for each."
      },
      "typeVersion": 1
    },
    {
      "id": "87c135e6-22c9-496b-8272-1ce9e1426df2",
      "name": "Sticky Note4",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        5968,
        2864
      ],
      "parameters": {
        "color": 7,
        "content": "## Email validation section:\nChecks every email found and removes ones that are invalid \nor likely to bounce before saving to your sheet."
      },
      "typeVersion": 1
    },
    {
      "id": "af6033e0-05c6-42e2-861c-42cf014a40a6",
      "name": "Sticky Note5",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        8624,
        3376
      ],
      "parameters": {
        "color": 7,
        "width": 304,
        "height": 176,
        "content": "## AI section:\nSends each business to an AI model which reads the data and \nwrites a short summary, lists their services, and creates a \npersonalized outreach message based on what the business actually does."
      },
      "typeVersion": 1
    },
    {
      "id": "48cb72c9-c11b-485f-a93b-81f8d1f7b47d",
      "name": "Sticky Note6",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        8240,
        2752
      ],
      "parameters": {
        "color": 7,
        "content": "## Export section:\nScores each lead from 0 to 10 based on their digital presence, \nremoves duplicates, and saves everything clean to Google Sheets \nready for outreach."
      },
      "typeVersion": 1
    },
    {
      "id": "d4891001-b869-4e93-bf9a-56a58a0b4ed3",
      "name": "Sticky Note7",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        7808,
        3600
      ],
      "parameters": {
        "color": 7,
        "width": 560,
        "height": 432,
        "content": "## Ollama Setup\n\nIf you are using Ollama as your AI model, replace the endpoint URL \nin the AI nodes with your own based on your setup:\n\nMac (Docker): http://docker.for.mac.host.internal:11434/api/generate\nWindows/Linux (Docker): http://host.docker.internal:11434/api/generate\nLocal (no Docker): http://localhost:11434/api/generate\nRemote server: http://YOUR_SERVER_IP:11434/api/generate\nn8n Cloud users: Ollama must be publicly accessible via a URL or tunnel\n\n## Not using Ollama?\n\nReplace the endpoint with any OpenAI compatible API:\nOpenAI: https://api.openai.com/v1/chat/completions\nGroq: https://api.groq.com/openai/v1/chat/completions\nTogether AI: https://api.together.xyz/v1/chat/completions\nMistral: https://api.mistral.ai/v1/chat/completions\nAnthropic: https://api.anthropic.com/v1/messages\n\nMake sure to update the Authorization header with your API key."
      },
      "typeVersion": 1
    }
  ],
  "active": false,
  "settings": {
    "binaryMode": "separate",
    "availableInMCP": false,
    "executionOrder": "v1"
  },
  "versionId": "96355623-306a-48c1-ba3a-5d12d7fe1f52",
  "connections": {
    "Clean Lead Data": {
      "main": [
        [
          {
            "node": "Calculate Lead Score",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Check Email Exists": {
      "main": [
        [
          {
            "node": "Validate Email Address",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Prepare Leads Without Email",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Merge Lead Results": {
      "main": [
        [
          {
            "node": "Clean Lead Data",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Process Each Query": {
      "main": [
        [
          {
            "node": "Fetch Maps Results (Page 1)",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Rate Limit Protection",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Extract AI Insights": {
      "main": [
        [
          {
            "node": "AI Processing Buffer",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Prepare Valid Leads": {
      "main": [
        [
          {
            "node": "Merge Lead Results",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "AI Processing Buffer": {
      "main": [
        [
          {
            "node": "Process Outreach Message",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "AI Rate Limit Buffer": {
      "main": [
        [
          {
            "node": "Analyze Business (AI)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Calculate Lead Score": {
      "main": [
        [
          {
            "node": "Remove Duplicate Leads",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Parse Social Results": {
      "main": [
        [
          {
            "node": "Combine Website + Serper Results",
            "type": "main",
            "index": 1
          }
        ]
      ]
    },
    "Split Search Queries": {
      "main": [
        [
          {
            "node": "Process Each Query",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Analyze Business (AI)": {
      "main": [
        [
          {
            "node": "Process Leads for AI Enrichment",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Normalize Social Data": {
      "main": [
        [
          {
            "node": "Process Businesses in Batches",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Rate Limit Protection": {
      "main": [
        [
          {
            "node": "Process Each Query",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Start Lead Generation": {
      "main": [
        [
          {
            "node": "Split Search Queries",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Clean Outreach Message": {
      "main": [
        [
          {
            "node": "Save Leads to Google Sheets",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Delay Between Requests": {
      "main": [
        [
          {
            "node": "Fetch Maps Results (Pages 2\u201312)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Remove Duplicate Leads": {
      "main": [
        [
          {
            "node": "Process Leads for AI Enrichment",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Validate Email Address": {
      "main": [
        [
          {
            "node": "Prepare Valid Leads",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Create Outreach Message": {
      "main": [
        [
          {
            "node": "Process Outreach Message",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Extract Socials & Email": {
      "main": [
        [
          {
            "node": "Combine Website + Serper Results",
            "type": "main",
            "index": 0
          },
          {
            "node": "Build Social Search Query",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Scrape Business Website": {
      "main": [
        [
          {
            "node": "Extract Socials & Email",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Process Outreach Message": {
      "main": [
        [
          {
            "node": "Clean Outreach Message",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Message Rate Limit Buffer",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Build Social Search Query": {
      "main": [
        [
          {
            "node": "Search Social Profiles (Serper)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Message Rate Limit Buffer": {
      "main": [
        [
          {
            "node": "Create Outreach Message",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Fetch Maps Results (Page 1)": {
      "main": [
        [
          {
            "node": "Delay Between Requests",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Prepare Leads Without Email": {
      "main": [
        [
          {
            "node": "Merge Lead Results",
            "type": "main",
            "index": 1
          }
        ]
      ]
    },
    "Extract Businesses From Maps": {
      "main": [
        [
          {
            "node": "Process Businesses in Batches",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Process Businesses in Batches": {
      "main": [
        [
          {
            "node": "Check Email Exists",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Scrape Business Website",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Process Leads for AI Enrichment": {
      "main": [
        [
          {
            "node": "Extract AI Insights",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "AI Rate Limit Buffer",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Resolve & Score Social Profiles": {
      "main": [
        [
          {
            "node": "Normalize Social Data",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Search Social Profiles (Serper)": {
      "main": [
        [
          {
            "node": "Parse Social Results",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Combine Website + Serper Results": {
      "main": [
        [
          {
            "node": "Resolve & Score Social Profiles",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Fetch Maps Results (Pages 2\u201312)": {
      "main": [
        [
          {
            "node": "Extract Businesses From Maps",
            "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 finds local businesses from Google Maps and automatically enriches them with emails, social profiles, AI summaries, and personalized outreach messages — all saved to Google Sheets. Searches Google Maps using your custom queries Scrapes each business website for…

Source: https://n8n.io/workflows/13513/ — 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

This n8n workflow automates the process of finding ecommerce seller leads, enriching them with product and business details, discovering company websites, and extracting contact information such as em

Google Sheets, N8N Nodes Mrscraper, HTTP Request
Marketing & Ads

This template is for B2B sales teams, SDRs, growth marketers, and founders who maintain a spreadsheet of prospects and need verified contact details -- emails and mobile numbers -- without manual rese

Google Sheets, HTTP Request
Marketing & Ads

This workflow leverages n8n to perform automated Google Maps API queries and manage data efficiently in Google Sheets. It's designed to extract specific location data based on a given list of ZIP code

Execute Workflow Trigger, Stop And Error, HTTP Request +1
Marketing & Ads

This repository contains an SLA-based lead routing workflow built in n8n, designed to ensure fast lead response, fair sales distribution, and controlled escalation without relying on a full CRM system

Form Trigger, Google Sheets, Slack +1
Marketing & Ads

This n8n workflow is a sophisticated B2B Lead Generation Scraper. It automates the entire journey from discovering businesses on Google Maps to extracting, scoring, and saving high-quality contact ema

HTTP Request, Google Sheets