{
  "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
          }
        ]
      ]
    }
  }
}