{
  "id": "VrCPRUVP_2NQHAX8urfvx",
  "name": "Enriched Google Maps Email Scraper",
  "tags": [],
  "nodes": [
    {
      "id": "e1028c97-2a58-4b4a-a459-0e573ed3ea94",
      "name": "Sticky Note",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -160,
        -96
      ],
      "parameters": {
        "width": 480,
        "height": 896,
        "content": "## Google Maps Test\n\n### How it works\n\n1. The workflow is triggered manually.\n2. It pulls search data from Airtable to initiate the query.\n3. Google Maps API is queried for information based on search data.\n4. Extracted data is checked, filtered, and emails are validated.\n5. Results are processed and updated back into Airtable.\n\n### Setup steps\n\n- [ ] Connect Google Maps API credentials.\n- [ ] Connect Airtable credentials for access.\n- [ ] Ensure the necessary queries and fields are set within Airtable for pulling and updating records.\n\n### Customization\n\nAdjust the API query parameters and the email validation criteria based on business needs."
      },
      "typeVersion": 1
    },
    {
      "id": "30360423-8725-447c-a871-c2889b6ba273",
      "name": "Sticky Note1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        400,
        672
      ],
      "parameters": {
        "color": 7,
        "height": 304,
        "content": "## Workflow trigger\n\nManual trigger to start the workflow process."
      },
      "typeVersion": 1
    },
    {
      "id": "a8a6ed46-5209-4d99-a127-17535ffecaf4",
      "name": "Sticky Note2",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        672,
        672
      ],
      "parameters": {
        "color": 7,
        "width": 464,
        "height": 304,
        "content": "## Airtable data import\n\nPull search records from Airtable to use as a query starter."
      },
      "typeVersion": 1
    },
    {
      "id": "a6158609-9c0a-4b94-8d6c-37d9615e8365",
      "name": "Sticky Note3",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        720,
        1008
      ],
      "parameters": {
        "color": 7,
        "width": 528,
        "height": 320,
        "content": "## Prepare search queries\n\nDuplicate and modify search queries with necessary parameters before processing."
      },
      "typeVersion": 1
    },
    {
      "id": "9374025a-c948-4d45-a02d-0b6ffa16c6ee",
      "name": "Sticky Note4",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1168,
        656
      ],
      "parameters": {
        "color": 7,
        "height": 304,
        "content": "## Query Google Maps\n\nUse the Google Maps Search API to fetch information."
      },
      "typeVersion": 1
    },
    {
      "id": "609b13ce-55fa-4d9b-9c66-d9b0c446936e",
      "name": "Sticky Note5",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1440,
        672
      ],
      "parameters": {
        "color": 7,
        "width": 560,
        "height": 272,
        "content": "## Data cleaning and deduplication\n\nProcess, clean, and remove duplicate entries from API results."
      },
      "typeVersion": 1
    },
    {
      "id": "aa2737c4-6ddb-4dc6-9e46-53a97a2e6440",
      "name": "Sticky Note6",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1904,
        112
      ],
      "parameters": {
        "color": 7,
        "height": 304,
        "content": "## Subpages setup\n\nConfigure additional pages for the websites."
      },
      "typeVersion": 1
    },
    {
      "id": "3377c338-8a42-4720-aa1f-58adb13fa0c6",
      "name": "Sticky Note7",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        2176,
        80
      ],
      "parameters": {
        "color": 7,
        "width": 1008,
        "height": 480,
        "content": "## Email scraping and validation\n\nScrape emails from results and filter out bad ones via custom code."
      },
      "typeVersion": 1
    },
    {
      "id": "4b1bf25b-d601-485d-a073-a6225f3985c9",
      "name": "Sticky Note8",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        3216,
        -96
      ],
      "parameters": {
        "color": 7,
        "width": 1456,
        "height": 864,
        "content": "## Websites processing\n\nLoop through websites for more detailed data extraction, including priority setting."
      },
      "typeVersion": 1
    },
    {
      "id": "6739cddc-a1ac-42d2-b497-5924b40240c7",
      "name": "Sticky Note9",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        5344,
        592
      ],
      "parameters": {
        "color": 7,
        "width": 640,
        "height": 320,
        "content": "## Merge and update results\n\nMerge emails with Google Maps data, remove unnecessary fields, and update Airtable."
      },
      "typeVersion": 1
    },
    {
      "id": "f0a16d4f-c905-4df2-9376-eee802340fd7",
      "name": "Manual Workflow Trigger",
      "type": "n8n-nodes-base.manualTrigger",
      "position": [
        448,
        800
      ],
      "parameters": {},
      "typeVersion": 1
    },
    {
      "id": "6aabb466-4a47-4b2f-93dd-fd4d2f22aa83",
      "name": "Wait 4 Seconds",
      "type": "n8n-nodes-base.wait",
      "position": [
        4528,
        560
      ],
      "parameters": {
        "amount": 4
      },
      "typeVersion": 1.1
    },
    {
      "id": "adb0c30e-51ca-41e6-b286-c15b18fc6694",
      "name": "Extract Emails",
      "type": "n8n-nodes-base.code",
      "onError": "continueRegularOutput",
      "position": [
        2224,
        384
      ],
      "parameters": {
        "mode": "runOnceForEachItem",
        "jsCode": "// Get the input item - this contains all data passed to this Code node\nconst inputData = $input.item.json;\n\n// Get the website URL - try multiple possible field names\nconst website = inputData.url || inputData.website || inputData.requestUrl || '';\n\n// Get the HTML content\nconst htmlContent = inputData.body || inputData.html || inputData.data || '';\nconst html = typeof htmlContent === 'string' ? htmlContent : JSON.stringify(htmlContent);\n\n// Regular expression to find email addresses\nconst emailRegex = /[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}/g;\n\n// Find all emails in the HTML\nconst emails = html.match(emailRegex);\n\n// Get unique emails (remove duplicates)\nconst uniqueEmails = emails ? [...new Set(emails)] : [];\n\n// Preserve all original business data from upstream nodes\n// Remove only the large HTML fields we don't need in output\nconst { body, html: htmlField, data, ...preservedData } = inputData;\n\n// Return preserved business data plus extracted emails\nreturn {\n  json: {\n    primary_email: uniqueEmails[0] || null,\n    emails: uniqueEmails\n  }\n};"
      },
      "typeVersion": 2,
      "alwaysOutputData": true
    },
    {
      "id": "db339a00-81f7-4fab-8a64-c9253f051a5a",
      "name": "Remove Invalid Emails",
      "type": "n8n-nodes-base.code",
      "position": [
        2400,
        384
      ],
      "parameters": {
        "jsCode": "const item = $input.item.json;\n\n// Domains that are commonly used for system/tracking purposes\nconst blockedDomains = [\n  'sentry.wixpress.com',\n  'sentry-next.wixpress.com',\n  'sentry.io',\n  'example.com',\n  'domain.com',\n  'test.com',\n  'localhost',\n  'email.com'\n];\n\n// Common placeholder local parts (the part before @)\nconst placeholderLocalParts = [\n  'user',\n  'test',\n  'admin',\n  'email',\n  'your',\n  'name',\n  'john',\n  'jane',\n  'example',\n  'noreply',\n  'no-reply',\n  'donotreply'\n];\n\n// Patterns that indicate system-generated or invalid emails\nconst suspiciousPatterns = [\n  /^[a-f0-9]{20,}@/i,\n  /^[0-9]+@/,\n  /.+@.+\\.(png|jpg|gif|svg|css|js)$/i,\n];\n\nfunction cleanEmail(email) {\n  if (!email || typeof email !== 'string') return null;\n  \n  let cleaned = email.trim();\n  \n  const prefixWords = [\n    'email',\n    'e-mail',\n    'mail',\n    'contact',\n    'mailto',\n    'send',\n    'write',\n    'reach'\n  ];\n  \n  for (const prefix of prefixWords) {\n    const prefixRegex = new RegExp(`^${prefix}([a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\\\.[a-zA-Z]{2,})$`, 'i');\n    const match = cleaned.match(prefixRegex);\n    if (match) {\n      cleaned = match[1];\n      break;\n    }\n  }\n  \n  const emailMatch = cleaned.match(/([a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,})/);\n  if (!emailMatch) return null;\n  \n  return emailMatch[1].toLowerCase().trim();\n}\n\nfunction isValidContactEmail(email) {\n  if (!email) return false;\n  \n  const cleaned = cleanEmail(email);\n  if (!cleaned) return false;\n  \n  const [localPart, domain] = cleaned.split('@');\n  if (!localPart || !domain) return false;\n  \n  if (blockedDomains.some(blocked => domain.includes(blocked))) {\n    return false;\n  }\n  \n  if (placeholderLocalParts.some(placeholder => {\n    return localPart === placeholder || \n           (placeholder === 'john' && localPart === 'john' && domain === 'doe.com');\n  })) {\n    return false;\n  }\n  \n  if (suspiciousPatterns.some(pattern => pattern.test(cleaned))) {\n    return false;\n  }\n  \n  if (localPart.length < 2 || localPart.length > 64) {\n    return false;\n  }\n  \n  return true;\n}\n\n// Clean the emails array\nconst rawEmails = item.emails || [];\nconst cleanedEmails = rawEmails\n  .map(cleanEmail)\n  .filter(isValidContactEmail);\nconst uniqueEmails = [...new Set(cleanedEmails)];\n\n// Clean the primary_email\nconst cleanedPrimaryEmail = cleanEmail(item.primary_email);\nconst validPrimaryEmail = isValidContactEmail(cleanedPrimaryEmail) ? cleanedPrimaryEmail : null;\n\n// If primary_email was invalid but we have other valid emails, use the first one\nconst finalPrimaryEmail = validPrimaryEmail || (uniqueEmails.length > 0 ? uniqueEmails[0] : null);\n\nreturn {\n  json: {\n    ...item,\n    primary_email: finalPrimaryEmail,\n    emails: uniqueEmails.length > 0 ? uniqueEmails : null\n  }\n};"
      },
      "typeVersion": 2
    },
    {
      "id": "5f303708-8581-42b9-a612-dbddfdabe549",
      "name": "Check for Email Presence",
      "type": "n8n-nodes-base.if",
      "position": [
        2608,
        384
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 2,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "045baee4-cc8b-45b8-9ca3-eacc3fca1b4c",
              "operator": {
                "type": "string",
                "operation": "exists",
                "singleValue": true
              },
              "leftValue": "={{ $json.primary_email }}",
              "rightValue": ""
            }
          ]
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "74614dd2-0d66-47e8-b672-cfc464772edf",
      "name": "Batch Website Processing",
      "type": "n8n-nodes-base.splitInBatches",
      "position": [
        3264,
        16
      ],
      "parameters": {
        "options": {}
      },
      "typeVersion": 3
    },
    {
      "id": "075ca3e5-a6ab-4443-8a18-7923abae1fe1",
      "name": "Define Processing Priorities",
      "type": "n8n-nodes-base.code",
      "position": [
        3424,
        112
      ],
      "parameters": {
        "jsCode": "const items = [];\n\nfor (const item of $input.all()) {\n  const businessId = item.json.website; // Use base URL as identifier\n  \n  const urlFields = [\n    { field: 'website', priority: 0 },\n    { field: 'website - contact', priority: 1 },\n    { field: 'website - contact-us', priority: 2 },\n    { field: 'website - about', priority: 3 },\n    { field: 'website - about-us', priority: 4 },\n    { field: 'website - contactus', priority: 5 },\n    { field: 'website - aboutus', priority: 6 }\n  ];\n  \n  for (const { field, priority } of urlFields) {\n    const url = item.json[field];\n    if (url && url.length > 0) {\n      items.push({\n        json: {\n          businessId: businessId,\n          urlToTry: url,\n          pageType: field,\n          priority: priority,\n          originalData: item.json\n        }\n      });\n    }\n  }\n}\n\nreturn items;"
      },
      "typeVersion": 2
    },
    {
      "id": "1bcd0b79-6992-4697-bb65-b0028eb2b658",
      "name": "Fetch Main Domain",
      "type": "n8n-nodes-base.httpRequest",
      "onError": "continueErrorOutput",
      "position": [
        4048,
        224
      ],
      "parameters": {
        "url": "={{ $json.urlToTry }}",
        "options": {
          "redirect": {
            "redirect": {
              "maxRedirects": 15
            }
          }
        }
      },
      "typeVersion": 4.3,
      "alwaysOutputData": false
    },
    {
      "id": "a60d242f-2d74-48cf-996c-d98745213521",
      "name": "Fetch Sub Pages",
      "type": "n8n-nodes-base.httpRequest",
      "onError": "continueRegularOutput",
      "position": [
        4144,
        608
      ],
      "parameters": {
        "url": "={{ $json.urlToTry }}",
        "options": {
          "redirect": {
            "redirect": {
              "maxRedirects": 15
            }
          }
        }
      },
      "typeVersion": 4.3,
      "alwaysOutputData": true
    },
    {
      "id": "24691df6-e112-4946-95e9-27a33928cf04",
      "name": "Handle Missing Emails",
      "type": "n8n-nodes-base.code",
      "position": [
        3760,
        192
      ],
      "parameters": {
        "jsCode": "const item = $input.item.json;\n\nreturn {\n  json: {\n    businessId: item.businessId,\n    primary_email: null,\n    all_emails: []\n  },\n  pairedItem: {\n    item: $input.item.index\n  }\n};"
      },
      "typeVersion": 2
    },
    {
      "id": "f9af7e34-b570-4674-b6dd-3fd1e4da5583",
      "name": "Process Page Batches",
      "type": "n8n-nodes-base.splitInBatches",
      "position": [
        3584,
        224
      ],
      "parameters": {
        "options": {
          "reset": "={{ $json.priority === 0 }}"
        }
      },
      "typeVersion": 3
    },
    {
      "id": "29ee7c8e-df89-433c-9baf-ec57f0895813",
      "name": "Combine Email and Map Data",
      "type": "n8n-nodes-base.merge",
      "position": [
        5392,
        736
      ],
      "parameters": {
        "mode": "combine",
        "options": {},
        "advanced": true,
        "mergeByFields": {
          "values": [
            {
              "field1": "businessId",
              "field2": "website"
            }
          ]
        }
      },
      "typeVersion": 3.2
    },
    {
      "id": "dfbf1e30-ca7e-4ce4-b3ce-68f3f1520cf8",
      "name": "Clean Up Data Fields",
      "type": "n8n-nodes-base.set",
      "position": [
        5616,
        720
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "75eea9dc-53d6-497d-93c5-b4049715881a",
              "name": "position",
              "type": "number",
              "value": "={{ $json.position }}"
            },
            {
              "id": "da577ea4-3998-4350-9e47-153a7c53d7e8",
              "name": "title",
              "type": "string",
              "value": "={{ $json.title }}"
            },
            {
              "id": "c619440a-80f6-4eb8-9dc0-7cf980f769a0",
              "name": "rating",
              "type": "number",
              "value": "={{ $json.rating }}"
            },
            {
              "id": "ae1fc06a-5a77-4ec3-839e-b157e6fe2f88",
              "name": "reviews",
              "type": "number",
              "value": "={{ $json.reviews }}"
            },
            {
              "id": "f423d9ce-254a-4343-90ec-164058f90588",
              "name": "type",
              "type": "string",
              "value": "={{ $json.type }}"
            },
            {
              "id": "274cf809-066c-450a-aa6f-f1ccad87655c",
              "name": "phone",
              "type": "string",
              "value": "={{ $json.phone }}"
            },
            {
              "id": "856400da-f749-450a-a871-702553aa539b",
              "name": "website",
              "type": "string",
              "value": "={{ $json.website }}"
            },
            {
              "id": "9c9545d8-33ea-495e-b977-d0fe035533a6",
              "name": "user_review",
              "type": "string",
              "value": "={{ $json.user_review }}"
            },
            {
              "id": "3bc35b9b-c73f-4fdb-bd51-4aa8dcc6b3d3",
              "name": "primary_email",
              "type": "string",
              "value": "={{ $json.primary_email }}"
            },
            {
              "id": "b19f3996-3b25-4b59-8847-e8c5904d776f",
              "name": "error",
              "type": "string",
              "value": "={{ $json.error }}"
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "a8ffec25-5d17-4b65-b466-ef922e3fb108",
      "name": "Process SerpAPI Results",
      "type": "n8n-nodes-base.code",
      "position": [
        1488,
        784
      ],
      "parameters": {
        "jsCode": "// Process all input items, not just the first\nconst allResults = [];\n\n$input.all().forEach((item, index) => {\n  const localResults = item.json.local_results;\n  const offset = index * 20;\n  \n  // Map through each result and extract only the fields you want\n  const cleanedResults = localResults.map(business => ({\n    position: business.position + offset,\n    title: business.title,\n    rating: business.rating,\n    reviews: business.reviews,\n    type: business.type,\n    types: business.types,\n    operating_hours: business.operating_hours,\n    phone: business.phone,\n    website: business.website,\n    user_review: business.user_review\n  }));\n  \n  allResults.push(...cleanedResults);\n});\n\n// Return as separate items (one per business)\nreturn allResults.map(item => ({ json: item }));"
      },
      "typeVersion": 2
    },
    {
      "id": "e4becf5e-b36d-4266-aede-709825981128",
      "name": "Verify Website Existence",
      "type": "n8n-nodes-base.if",
      "position": [
        1856,
        784
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 2,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "f09d7d4e-179e-44bb-a8bf-8befa30ffbb3",
              "operator": {
                "type": "string",
                "operation": "exists",
                "singleValue": true
              },
              "leftValue": "={{ $json.website }}",
              "rightValue": ""
            }
          ]
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "e5a63069-94b8-4db3-8461-85bed94925eb",
      "name": "Remove Duplicate Entries",
      "type": "n8n-nodes-base.code",
      "position": [
        1680,
        784
      ],
      "parameters": {
        "jsCode": "const seen = new Map();\n\nfor (const item of $input.all()) {\n  const business = item.json;\n  \n  // Normalize the website URL for comparison\n  const normalizeUrl = (url) => {\n    if (!url) return null;\n    return url\n      .toLowerCase()\n      .replace(/^https?:\\/\\//, '')\n      .replace(/^www\\./, '')\n      .replace(/\\/+$/, '')\n      .split('/')[0]; // Get just the domain\n  };\n  \n  const domain = normalizeUrl(business.website);\n  const phone = business.phone?.replace(/\\D/g, ''); // Normalize phone\n  \n  // Create a composite key - prefer domain, fall back to phone\n  const key = domain || phone || business.title;\n  \n  if (!seen.has(key)) {\n    seen.set(key, {\n      ...business,\n      _dedupeKey: key\n    });\n  } else {\n    // Optional: merge data from duplicate (e.g., keep higher position)\n    const existing = seen.get(key);\n    if (business.position < existing.position) {\n      seen.set(key, { ...business, _dedupeKey: key });\n    }\n  }\n}\n\nreturn Array.from(seen.values()).map(item => ({ json: item }));"
      },
      "typeVersion": 2
    },
    {
      "id": "76793f77-2a00-4d6c-a76e-98182db27cd9",
      "name": "Restore Business Identifier",
      "type": "n8n-nodes-base.set",
      "position": [
        2928,
        192
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "16b5a234-8d28-4f5d-83db-56df7e98e248",
              "name": "businessId",
              "type": "string",
              "value": "={{ $('Define Processing Priorities').item.json.businessId }}"
            }
          ]
        },
        "includeOtherFields": true
      },
      "typeVersion": 3.4
    },
    {
      "id": "83506f8b-7f01-414b-892d-43e60b6a991b",
      "name": "Preserve Business Identifier",
      "type": "n8n-nodes-base.set",
      "position": [
        3040,
        400
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "af43ece4-5177-4377-a3ec-3672b38bf398",
              "name": "primary_email",
              "type": "string",
              "value": "={{ $json.primary_email }}"
            },
            {
              "id": "1c895f98-16c9-4c65-afa1-10d3a5b8ebca",
              "name": "emails",
              "type": "string",
              "value": "={{ $json.emails }}"
            },
            {
              "id": "57c47560-915c-4150-bc4f-90201d336815",
              "name": "businessId",
              "type": "string",
              "value": "={{ $('Define Processing Priorities').item.json.businessId }}"
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "ad0b8fd3-4219-48e0-a81c-4473046ccaad",
      "name": "Evaluate Main Domain Priority",
      "type": "n8n-nodes-base.if",
      "position": [
        3712,
        400
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 2,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "227a848b-f189-4e66-8386-57976021cc4f",
              "operator": {
                "type": "number",
                "operation": "equals"
              },
              "leftValue": "={{ $json.priority }}",
              "rightValue": 0
            }
          ]
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "4c28e11e-4f83-4363-b226-5d8fc7438dd1",
      "name": "Set Website Error Status",
      "type": "n8n-nodes-base.set",
      "position": [
        4176,
        384
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "6991e969-d801-4626-8670-5f13e5bf1fc4",
              "name": "error",
              "type": "string",
              "value": "Website Down"
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "38efa349-5ba2-4db3-8efb-840ff29ee31f",
      "name": "Airtable Record Sync",
      "type": "n8n-nodes-base.airtable",
      "position": [
        5840,
        720
      ],
      "parameters": {
        "base": {
          "__rl": true,
          "mode": "list",
          "value": "appoNeA9PziJOgynD",
          "cachedResultUrl": "https://airtable.com/appoNeA9PziJOgynD",
          "cachedResultName": "My Airtables"
        },
        "table": {
          "__rl": true,
          "mode": "list",
          "value": "tblsjPuQ8aiJGCusY",
          "cachedResultUrl": "https://airtable.com/appoNeA9PziJOgynD/tblsjPuQ8aiJGCusY",
          "cachedResultName": "Google Maps Scraping"
        },
        "columns": {
          "value": {},
          "schema": [
            {
              "id": "id",
              "type": "string",
              "display": true,
              "removed": false,
              "readOnly": true,
              "required": false,
              "displayName": "id",
              "defaultMatch": true
            },
            {
              "id": "position",
              "type": "number",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "position",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "title",
              "type": "string",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "title",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "rating",
              "type": "number",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "rating",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "reviews",
              "type": "number",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "reviews",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "type",
              "type": "string",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "type",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "types",
              "type": "array",
              "display": true,
              "options": [],
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "types",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "phone",
              "type": "string",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "phone",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "website",
              "type": "string",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "website",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "user_review",
              "type": "string",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "user_review",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "emails",
              "type": "array",
              "display": true,
              "options": [],
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "emails",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "error",
              "type": "string",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "error",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            }
          ],
          "mappingMode": "autoMapInputData",
          "matchingColumns": [
            "position"
          ],
          "attemptToConvertTypes": false,
          "convertFieldsToString": false
        },
        "options": {},
        "operation": "upsert"
      },
      "credentials": {
        "airtableTokenApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 2.1
    },
    {
      "id": "610dac7d-2381-44e3-b41d-5ef3d05b7130",
      "name": "Initiate Pages Batch",
      "type": "n8n-nodes-base.splitInBatches",
      "position": [
        992,
        800
      ],
      "parameters": {
        "options": {}
      },
      "typeVersion": 3
    },
    {
      "id": "b5f326c8-c08f-4c2a-8ca1-f71ce1b8f2b3",
      "name": "Replicate Search Queries",
      "type": "n8n-nodes-base.set",
      "position": [
        1104,
        1136
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "f83f4667-a514-46b8-8142-bdd2b05398cb",
              "name": "Search Query",
              "type": "string",
              "value": "={{ $json['Search Query'] }}"
            },
            {
              "id": "9d5a2d82-8372-4849-b2dd-c4f0517ca912",
              "name": "GPS Coordinates",
              "type": "string",
              "value": "={{ $json['GPS Coordinates'] }}"
            }
          ]
        },
        "duplicateItem": true
      },
      "typeVersion": 3.4
    },
    {
      "id": "93af7dff-47ed-4f35-ba76-3a574b0f0b72",
      "name": "Initialize Search Query",
      "type": "n8n-nodes-base.set",
      "position": [
        768,
        1152
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "bc7e2239-2aa7-41cb-bd34-f32e69093c0a",
              "name": "Search Query",
              "type": "string",
              "value": "={{ $json['Search Query'] }}"
            },
            {
              "id": "fc85eb79-a7d0-4bc9-a739-4fc831613d7e",
              "name": "GPS Coordinates",
              "type": "string",
              "value": "={{ $json['GPS Coordinates'] }}"
            },
            {
              "id": "6dfdf7ac-1395-4238-a644-3a2757c31eef",
              "name": "Start",
              "type": "string",
              "value": "={{ $itemIndex * 20 }}"
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "ec0ebd05-954c-4015-8092-23bff8e31de8",
      "name": "Perform Google Maps Search",
      "type": "n8n-nodes-serpapi.serpApi",
      "position": [
        1216,
        784
      ],
      "parameters": {
        "q": "={{ $json['Search Query'] }}",
        "ll": "={{ $json['GPS Coordinates'] }}",
        "operation": "google_maps",
        "requestOptions": {},
        "additionalFields": {
          "start": "={{ $json.Start }}"
        }
      },
      "credentials": {
        "serpApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "5fb65c98-15f2-43f6-98da-09ebeda629e4",
      "name": "Retrieve Searches from Airtable",
      "type": "n8n-nodes-base.airtable",
      "position": [
        720,
        800
      ],
      "parameters": {
        "base": {
          "__rl": true,
          "mode": "list",
          "value": "appoNeA9PziJOgynD",
          "cachedResultUrl": "https://airtable.com/appoNeA9PziJOgynD",
          "cachedResultName": "My Airtables"
        },
        "table": {
          "__rl": true,
          "mode": "list",
          "value": "tblu3wcNxBcyTkfq1",
          "cachedResultUrl": "https://airtable.com/appoNeA9PziJOgynD/tblu3wcNxBcyTkfq1",
          "cachedResultName": "Google Maps Scrape Queries"
        },
        "options": {},
        "operation": "search"
      },
      "credentials": {
        "airtableTokenApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 2.1
    },
    {
      "id": "88b019f1-8e81-4278-8363-6db865c57b7a",
      "name": "Set Subpage URLs",
      "type": "n8n-nodes-base.set",
      "position": [
        1952,
        240
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "a86ca8ba-cbd2-43dc-b321-b84dbc9989bd",
              "name": "website",
              "type": "string",
              "value": "={{ $json.website }}"
            },
            {
              "id": "736025da-f01f-4e54-9b96-db4003ed43ed",
              "name": "website - contact",
              "type": "string",
              "value": "={{ $json.website }}contact/"
            },
            {
              "id": "4d08d2a8-7916-4f96-a9b4-fef148faf6d1",
              "name": "website - contact-us",
              "type": "string",
              "value": "={{ $json.website }}contact-us/"
            },
            {
              "id": "7d3bc22c-4f0d-45e4-9176-9ddf75ba31c1",
              "name": "website - about",
              "type": "string",
              "value": "={{ $json.website }}about/"
            },
            {
              "id": "70f1e2d9-8e15-4662-8c98-da7361c56195",
              "name": "website - about-us",
              "type": "string",
              "value": "={{ $json.website }}about-us/"
            },
            {
              "id": "d9e2aa4a-eb78-48ef-b610-9ccf7df68bf0",
              "name": "website - contactus",
              "type": "string",
              "value": "={{ $json.website }}contactus/"
            },
            {
              "id": "1c86e981-d3e2-4ad5-89bd-f90ed66f0d04",
              "name": "website - aboutus",
              "type": "string",
              "value": "={{ $json.website }}aboutus/"
            }
          ]
        }
      },
      "typeVersion": 3.4
    }
  ],
  "active": false,
  "settings": {
    "availableInMCP": false,
    "executionOrder": "v1"
  },
  "versionId": "2c56b087-0470-4261-b38f-e5e159aa2c09",
  "connections": {
    "Extract Emails": {
      "main": [
        [
          {
            "node": "Remove Invalid Emails",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Wait 4 Seconds": {
      "main": [
        [
          {
            "node": "Extract Emails",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Fetch Sub Pages": {
      "main": [
        [
          {
            "node": "Wait 4 Seconds",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Set Subpage URLs": {
      "main": [
        [
          {
            "node": "Batch Website Processing",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Fetch Main Domain": {
      "main": [
        [
          {
            "node": "Wait 4 Seconds",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Set Website Error Status",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Clean Up Data Fields": {
      "main": [
        [
          {
            "node": "Airtable Record Sync",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Initiate Pages Batch": {
      "main": [
        [
          {
            "node": "Perform Google Maps Search",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Replicate Search Queries",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Process Page Batches": {
      "main": [
        [
          {
            "node": "Handle Missing Emails",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Evaluate Main Domain Priority",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Handle Missing Emails": {
      "main": [
        [
          {
            "node": "Batch Website Processing",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Remove Invalid Emails": {
      "main": [
        [
          {
            "node": "Check for Email Presence",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Initialize Search Query": {
      "main": [
        [
          {
            "node": "Initiate Pages Batch",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Manual Workflow Trigger": {
      "main": [
        [
          {
            "node": "Retrieve Searches from Airtable",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Process SerpAPI Results": {
      "main": [
        [
          {
            "node": "Remove Duplicate Entries",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Batch Website Processing": {
      "main": [
        [
          {
            "node": "Combine Email and Map Data",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Define Processing Priorities",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Check for Email Presence": {
      "main": [
        [
          {
            "node": "Restore Business Identifier",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Preserve Business Identifier",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Remove Duplicate Entries": {
      "main": [
        [
          {
            "node": "Verify Website Existence",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Replicate Search Queries": {
      "main": [
        [
          {
            "node": "Initialize Search Query",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Set Website Error Status": {
      "main": [
        [
          {
            "node": "Restore Business Identifier",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Verify Website Existence": {
      "main": [
        [
          {
            "node": "Combine Email and Map Data",
            "type": "main",
            "index": 1
          },
          {
            "node": "Set Subpage URLs",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Combine Email and Map Data": {
      "main": [
        [
          {
            "node": "Clean Up Data Fields",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Perform Google Maps Search": {
      "main": [
        [
          {
            "node": "Process SerpAPI Results",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Restore Business Identifier": {
      "main": [
        [
          {
            "node": "Batch Website Processing",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Define Processing Priorities": {
      "main": [
        [
          {
            "node": "Process Page Batches",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Preserve Business Identifier": {
      "main": [
        [
          {
            "node": "Process Page Batches",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Evaluate Main Domain Priority": {
      "main": [
        [
          {
            "node": "Fetch Main Domain",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Fetch Sub Pages",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Retrieve Searches from Airtable": {
      "main": [
        [
          {
            "node": "Initiate Pages Batch",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}