{
  "name": "Google Maps Lead Generation with Email Discovery",
  "nodes": [
    {
      "parameters": {
        "resource": "google_maps",
        "q": "={{ $json.queries.leads_query }}",
        "additionalFields": {
          "start": "={{ $json.queries.pageOffset }}",
          "ll": "@40.685676,-74.009913,14z"
        }
      },
      "id": "56d672ee-fe20-4218-973a-6de94a122460",
      "name": "Search Google Maps",
      "type": "@hasdata/n8n-nodes-hasdata.hasData",
      "typeVersion": 1,
      "position": [
        -1072,
        112
      ],
      "credentials": {
        "hasDataApi": {
          "name": "<your credential>"
        }
      },
      "onError": "continueRegularOutput"
    },
    {
      "parameters": {
        "jsCode": "const emailNotFound = 'Not found';\n\nconst PLACEHOLDER_EMAILS = new Set([\n  'user@domain.com', 'name@domain.com', 'email@domain.com',\n  'user@example.com', 'name@example.com', 'email@example.com',\n  'info@example.com', 'test@test.com', 'admin@example.com',\n  'your@email.com', 'email@website.com', 'name@yoursite.com',\n  'email@email.com', 'name@company.com', 'yourname@domain.com',\n  'username@domain.com', 'your@domain.com', 'you@example.com',\n]);\n\nconst FAKE_DOMAINS = new Set([\n  'example.com', 'domain.com', 'website.com', 'email.com',\n  'test.com', 'yoursite.com', 'yourdomain.com', 'company.com',\n  'sentry.io', 'wixpress.com', 'placeholder.com',\n]);\n\nconst INVALID_EXTENSIONS = [\n  '.png', '.jpg', '.jpeg', '.gif', '.svg', '.webp',\n  '.ico', '.css', '.js', '.json', '.xml',\n];\n\nconst NOREPLY_PREFIXES = [\n  'noreply', 'no-reply', 'mailer-daemon', 'postmaster',\n  'donotreply', 'do-not-reply',\n];\n\nfunction isValidCompanyEmail(email, websiteUrl) {\n  const lower = email.toLowerCase();\n  if (PLACEHOLDER_EMAILS.has(lower)) return false;\n\n  const parts = lower.split('@');\n  if (parts.length !== 2) return false;\n  const [local, emailDomain] = parts;\n\n  if (FAKE_DOMAINS.has(emailDomain)) return false;\n  if (/example|test|sample|demo|fake|placeholder/.test(emailDomain)) return false;\n  if (NOREPLY_PREFIXES.some(p => local === p)) return false;\n\n  return true;\n}\n\nfunction domainMatchesWebsite(email, websiteUrl) {\n  if (!websiteUrl) return false;\n  try {\n    const emailDomain = email.toLowerCase().split('@')[1];\n    const siteDomain = websiteUrl\n      .replace(/^https?:\\/\\//, '')\n      .replace(/^www\\./, '')\n      .split('/')[0].toLowerCase();\n    return emailDomain === siteDomain ||\n      siteDomain.endsWith('.' + emailDomain) ||\n      emailDomain.endsWith('.' + siteDomain);\n  } catch (e) { return false; }\n}\n\nfunction extractAllEmails(html) {\n  if (!html) return [];\n  html = html\n    .replace(/\\s*\\[at\\]\\s*|\\s*\\(at\\)\\s*/gi, '@')\n    .replace(/\\s*\\[dot\\]\\s*|\\s*\\(dot\\)\\s*/gi, '.');\n\n  const regex = /\\b[a-zA-Z0-9._%+-]+@([a-zA-Z0-9-]+\\.)+[a-zA-Z]{2,}\\b/g;\n  const matches = html.match(regex);\n  if (!matches) return [];\n\n  const valid = [];\n  for (const email of matches) {\n    const lower = email.toLowerCase();\n    if (INVALID_EXTENSIONS.some(ext => lower.endsWith(ext))) continue;\n    if (/-\\d+x\\d+/i.test(lower)) continue;\n    valid.push(lower);\n  }\n  return [...new Set(valid)];\n}\n\nconst zipped = $input.all().map((x, i) => [x, $('Is there a website?').all()[i]]);\nlet results = [];\nfor (const [webScrapeItem, mapsScrapeItem] of zipped) {\n  const content = webScrapeItem.json.content;\n  const locationData = mapsScrapeItem.json.locationResults;\n  const website = locationData.website || '';\n\n  let allEmails = extractAllEmails(content)\n    .filter(e => isValidCompanyEmail(e, website));\n\n  // Sort: domain-matching emails first\n  allEmails.sort((a, b) => {\n    const aMatch = domainMatchesWebsite(a, website) ? 0 : 1;\n    const bMatch = domainMatchesWebsite(b, website) ? 0 : 1;\n    return aMatch - bMatch;\n  });\n\n  const email = allEmails.length > 0 ? allEmails.join(', ') : emailNotFound;\n  results.push({ json: { locationResults: locationData, email: email } });\n}\nreturn results;\n"
      },
      "id": "bbd00c82-6599-41c3-bea0-0e23bb272594",
      "name": "Extract Email from Website",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        240,
        0
      ],
      "alwaysOutputData": false
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "strict",
            "version": 3
          },
          "conditions": [
            {
              "leftValue": "={{ $json.email }}",
              "operator": {
                "type": "string",
                "operation": "notEquals"
              },
              "rightValue": "Not found",
              "id": "68d704b2-147e-4792-8091-85acb6180186"
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "96109f96-c25e-42f3-aaf3-f16f495f58ad",
      "name": "Email Found?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.3,
      "position": [
        480,
        0
      ]
    },
    {
      "parameters": {
        "q": "={{ $json.locationResults.title }} email",
        "additionalFields": {}
      },
      "id": "3a969281-2648-4981-8840-d98183929629",
      "name": "Search Google for Email",
      "type": "@hasdata/n8n-nodes-hasdata.hasData",
      "typeVersion": 1,
      "position": [
        240,
        240
      ],
      "credentials": {
        "hasDataApi": {
          "name": "<your credential>"
        }
      },
      "onError": "continueRegularOutput"
    },
    {
      "parameters": {
        "jsCode": "const PLACEHOLDER_EMAILS = new Set([\n  'user@domain.com', 'name@domain.com', 'email@domain.com',\n  'user@example.com', 'name@example.com', 'email@example.com',\n  'info@example.com', 'test@test.com', 'admin@example.com',\n  'your@email.com', 'email@website.com', 'name@yoursite.com',\n  'email@email.com', 'name@company.com', 'yourname@domain.com',\n  'username@domain.com', 'your@domain.com', 'you@example.com',\n]);\n\nconst FAKE_DOMAINS = new Set([\n  'example.com', 'domain.com', 'website.com', 'email.com',\n  'test.com', 'yoursite.com', 'yourdomain.com', 'company.com',\n  'sentry.io', 'wixpress.com', 'placeholder.com',\n]);\n\nconst INVALID_EXTENSIONS = [\n  '.png', '.jpg', '.jpeg', '.gif', '.svg', '.webp',\n  '.ico', '.css', '.js', '.json', '.xml',\n];\n\nconst NOREPLY_PREFIXES = [\n  'noreply', 'no-reply', 'mailer-daemon', 'postmaster',\n  'donotreply', 'do-not-reply',\n];\n\nfunction isValidCompanyEmail(email, websiteUrl) {\n  const lower = email.toLowerCase();\n  if (PLACEHOLDER_EMAILS.has(lower)) return false;\n\n  const parts = lower.split('@');\n  if (parts.length !== 2) return false;\n  const [local, emailDomain] = parts;\n\n  if (FAKE_DOMAINS.has(emailDomain)) return false;\n  if (/example|test|sample|demo|fake|placeholder/.test(emailDomain)) return false;\n  if (NOREPLY_PREFIXES.some(p => local === p)) return false;\n\n  return true;\n}\n\nfunction extractAllEmails(html) {\n  if (!html) return [];\n  html = html\n    .replace(/\\s*\\[at\\]\\s*|\\s*\\(at\\)\\s*/gi, '@')\n    .replace(/\\s*\\[dot\\]\\s*|\\s*\\(dot\\)\\s*/gi, '.');\n\n  const regex = /\\b[a-zA-Z0-9._%+-]+@([a-zA-Z0-9-]+\\.)+[a-zA-Z]{2,}\\b/g;\n  const matches = html.match(regex);\n  if (!matches) return [];\n\n  const valid = [];\n  for (const email of matches) {\n    const lower = email.toLowerCase();\n    if (INVALID_EXTENSIONS.some(ext => lower.endsWith(ext))) continue;\n    if (/-\\d+x\\d+/i.test(lower)) continue;\n    valid.push(lower);\n  }\n  return [...new Set(valid)];\n}\n\nconst zipped = $input.all().map((x, i) => [x, $('Merge1').all()[i]]);\nfor (const [serpItem, mapsData] of zipped) {\n  const serpData = serpItem.json.organicResults ?? [];\n  const website = mapsData.json.locationResults.website || '';\n  mapsData.json.email = 'Not found';\n\n  let foundEmails = [];\n  for (const data of serpData.slice(0, 5)) {\n    const snippet = data.snippet ?? '';\n    const emails = extractAllEmails(snippet)\n      .filter(e => isValidCompanyEmail(e, website));\n    foundEmails.push(...emails);\n  }\n\n  foundEmails = [...new Set(foundEmails)];\n  if (foundEmails.length > 0) {\n    mapsData.json.email = foundEmails.join(', ');\n  }\n}\n\nreturn $('Merge1').all();\n"
      },
      "id": "4f884b38-6786-47c8-a228-5a917ff8c627",
      "name": "Extract Email from Google",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        480,
        240
      ]
    },
    {
      "parameters": {
        "resource": "spreadsheet",
        "title": "=Lead - {{ $now.toFormat(\"yyyy-MM-dd HH:mm\") }}",
        "sheetsUi": {
          "sheetValues": [
            {
              "title": "Leads"
            },
            {
              "title": "Runs"
            }
          ]
        },
        "options": {}
      },
      "id": "4f81c024-ef5d-44b7-ab0d-cdb1ac21ba09",
      "name": "Create Spreadsheet",
      "type": "n8n-nodes-base.googleSheets",
      "typeVersion": 4.7,
      "position": [
        1488,
        16
      ],
      "credentials": {
        "googleSheetsOAuth2Api": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "operation": "appendOrUpdate",
        "documentId": {
          "__rl": true,
          "value": "={{ $('Create Spreadsheet').item.json.spreadsheetId }}",
          "mode": "id"
        },
        "sheetName": {
          "__rl": true,
          "value": "={{ $('Create Spreadsheet').item.json.sheets[0].properties.sheetId }}",
          "mode": "id"
        },
        "columns": {
          "mappingMode": "autoMapInputData",
          "value": {},
          "matchingColumns": [],
          "schema": [],
          "attemptToConvertTypes": false,
          "convertFieldsToString": false
        },
        "options": {}
      },
      "id": "74871695-cadc-4ddc-9fe6-58160f859403",
      "name": "Save Leads to Sheet",
      "type": "n8n-nodes-base.googleSheets",
      "typeVersion": 4.7,
      "position": [
        1936,
        16
      ],
      "credentials": {
        "googleSheetsOAuth2Api": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {},
      "type": "n8n-nodes-base.merge",
      "typeVersion": 3.2,
      "position": [
        800,
        112
      ],
      "id": "d9567831-5d9a-4b9e-947f-cc8a07cd8803",
      "name": "Merge"
    },
    {
      "parameters": {
        "aggregate": "aggregateAllItemData",
        "options": {}
      },
      "type": "n8n-nodes-base.aggregate",
      "typeVersion": 1,
      "position": [
        1024,
        112
      ],
      "id": "56e64a6c-7b64-4277-801c-52f2fbee1c74",
      "name": "Aggregate"
    },
    {
      "parameters": {
        "fieldToSplitOut": "queries",
        "include": "={{ $json.queries[0] }}",
        "options": {}
      },
      "type": "n8n-nodes-base.splitOut",
      "typeVersion": 1,
      "position": [
        -1296,
        112
      ],
      "id": "0fe29978-5b43-407e-9259-fa15b86a29c4",
      "name": "Split Out1"
    },
    {
      "parameters": {
        "aggregate": "aggregateAllItemData",
        "include": "={{ $json.localResults }}",
        "options": {}
      },
      "type": "n8n-nodes-base.aggregate",
      "typeVersion": 1,
      "position": [
        -832,
        112
      ],
      "id": "2db837a4-3392-4b18-9ffa-9a9aa8c38f48",
      "name": "Aggregate1"
    },
    {
      "parameters": {
        "jsCode": "const combined_localResults = [];\nconst seenTitles = new Set();\n\nfor (const data of $input.first().json.data) {\n  for (const result of (data.localResults || [])) {\n    const title = result.title;\n\n    if (!seenTitles.has(title)) {\n      seenTitles.add(title);\n\n      combined_localResults.push({\n        json: {\n          locationResults: result\n        }\n      });\n    }\n  }\n}\n\nreturn combined_localResults;\n"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        -576,
        112
      ],
      "id": "b94cb123-bb10-4c82-a86a-c4e5e5e7d776",
      "name": "Remove Duplicates"
    },
    {
      "parameters": {
        "jsCode": "const leads_query = $input.first().json['Search Locations Query'];\nconst defaultPageCount = Number($input.first().json['Pages per query to scrape'] ?? 0);\n\nconst outputQuery = [];\nfor (let i = 0; i < defaultPageCount; i++) {\n  outputQuery.push({ pageOffset: i * 20, leads_query });\n}\n\nreturn { json: { queries: outputQuery } };\n"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        -1520,
        112
      ],
      "id": "cfe54683-4ce4-40c6-95f7-f943eb235f09",
      "name": "Prepare Queries"
    },
    {
      "parameters": {
        "resource": "web_scraping",
        "url": "={{ $json.locationResults.website }}",
        "additionalFields": {}
      },
      "id": "ea935903-4c87-4326-89e8-de16ad6056a3",
      "name": "Scrape Websites",
      "type": "@hasdata/n8n-nodes-hasdata.hasData",
      "typeVersion": 1,
      "position": [
        0,
        0
      ],
      "alwaysOutputData": true,
      "credentials": {
        "hasDataApi": {
          "name": "<your credential>"
        }
      },
      "onError": "continueRegularOutput"
    },
    {
      "parameters": {
        "formTitle": "Form input",
        "formFields": {
          "values": [
            {
              "fieldLabel": "Search Locations Query",
              "placeholder": "example: dentist in Brooklyn, NY (city/street/zip/neighborhood)"
            },
            {
              "fieldLabel": "Pages per query to scrape",
              "fieldType": "number",
              "defaultValue": "3"
            },
            {
              "fieldLabel": "Google Sheets Document Id",
              "placeholder": "Leave this empty if you want to create a new file"
            },
            {
              "fieldLabel": "Google Sheets ID",
              "placeholder": "Leave this empty if you want to create a new file"
            }
          ]
        },
        "options": {}
      },
      "type": "n8n-nodes-base.formTrigger",
      "typeVersion": 2.5,
      "position": [
        -1776,
        112
      ],
      "id": "6a6a1ae9-2dc2-4fc3-bc98-c4056b0ca470",
      "name": "On form submission"
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "strict",
            "version": 3
          },
          "conditions": [
            {
              "id": "c4353e2a-28aa-4718-9f6a-3081a1401259",
              "leftValue": "={{ $json.locationResults.website }}",
              "rightValue": "",
              "operator": {
                "type": "string",
                "operation": "exists",
                "singleValue": true
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.3,
      "position": [
        -352,
        112
      ],
      "id": "8300b730-c77e-48cb-b678-8da9d75a8809",
      "name": "Is there a website?"
    },
    {
      "parameters": {},
      "type": "n8n-nodes-base.merge",
      "typeVersion": 3.2,
      "position": [
        0,
        240
      ],
      "id": "83ccc6b4-1481-4435-8bc5-c44f9dc62d72",
      "name": "Merge1"
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "strict",
            "version": 3
          },
          "conditions": [
            {
              "id": "a4891843-7c4a-4c0e-b9e3-ea82cd748ba8",
              "leftValue": "={{ $('On form submission').item.json['Google Sheets Document Id'] }}",
              "rightValue": "",
              "operator": {
                "type": "string",
                "operation": "empty",
                "singleValue": true
              }
            },
            {
              "id": "5937010c-ddbc-4887-b88e-1f164c66dd16",
              "leftValue": "={{ $('On form submission').item.json['Google Sheets ID'] }}",
              "rightValue": "",
              "operator": {
                "type": "string",
                "operation": "empty",
                "singleValue": true
              }
            }
          ],
          "combinator": "or"
        },
        "options": {}
      },
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.3,
      "position": [
        1248,
        112
      ],
      "id": "6a896ad0-687b-4184-9df0-a00afc3f0343",
      "name": "Is google sheet empty"
    },
    {
      "parameters": {
        "operation": "appendOrUpdate",
        "documentId": {
          "__rl": true,
          "value": "={{ $('On form submission').item.json['Google Sheets Document Id'] }}",
          "mode": "id"
        },
        "sheetName": {
          "__rl": true,
          "value": "={{ $('On form submission').item.json['Google Sheets ID'] }}",
          "mode": "id"
        },
        "columns": {
          "mappingMode": "autoMapInputData",
          "value": {},
          "matchingColumns": [
            "Title"
          ],
          "schema": [
            {
              "id": "Title",
              "displayName": "Title",
              "required": false,
              "defaultMatch": true,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true,
              "removed": false
            },
            {
              "id": "Email",
              "displayName": "Email",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true,
              "removed": false
            },
            {
              "id": "Phone",
              "displayName": "Phone",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true,
              "removed": false
            },
            {
              "id": "Rating",
              "displayName": "Rating",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true,
              "removed": false
            },
            {
              "id": "Reviews",
              "displayName": "Reviews",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true,
              "removed": false
            },
            {
              "id": "Website",
              "displayName": "Website",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true,
              "removed": false
            },
            {
              "id": "Address",
              "displayName": "Address",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true,
              "removed": false
            },
            {
              "id": "Latitude",
              "displayName": "Latitude",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true,
              "removed": false
            },
            {
              "id": "Longitude",
              "displayName": "Longitude",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true,
              "removed": false
            }
          ],
          "attemptToConvertTypes": false,
          "convertFieldsToString": false
        },
        "options": {}
      },
      "type": "n8n-nodes-base.googleSheets",
      "typeVersion": 4.7,
      "position": [
        1712,
        208
      ],
      "id": "25598919-5580-4123-b92e-244153e1d4ca",
      "name": "Save leads to existing sheet",
      "credentials": {
        "googleSheetsOAuth2Api": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "jsCode": "let output = [];\nfor (const item of $('Aggregate').first().json.data) {\n  output.push({\n    Title: item.locationResults.title ?? \"\",\n    Email: item.email ?? \"\",\n    Phone: item.locationResults.phone ?? \"\",\n    Rating: item.locationResults.rating ?? \"\",\n    Reviews: item.locationResults.reviews ?? \"\",\n    Website: item.locationResults.website ?? \"\",\n    Address: item.locationResults.address ?? \"\",\n    Latitude: item.locationResults.gpsCoordinates.latitude ?? \"\",\n    Longitude: item.locationResults.gpsCoordinates.longitude ?? \"\",\n  });\n}\n\nreturn output;\n"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1488,
        208
      ],
      "id": "639ec17d-d716-4222-816f-e367c690e6d5",
      "name": "Combine with old and remove duplicates"
    },
    {
      "parameters": {
        "jsCode": "let output = [];\nfor (const item of $('Aggregate').first().json.data) {\n  output.push({\n    Title: item.locationResults.title ?? \"\",\n    Email: item.email ?? \"\",\n    Phone: item.locationResults.phone ?? \"\",\n    Rating: item.locationResults.rating ?? \"\",\n    Reviews: item.locationResults.reviews ?? \"\",\n    Website: item.locationResults.website ?? \"\",\n    Address: item.locationResults.address ?? \"\",\n    Latitude: item.locationResults.gpsCoordinates.latitude ?? \"\",\n    Longitude: item.locationResults.gpsCoordinates.longitude ?? \"\",\n  });\n}\n\nreturn output;\n"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1712,
        16
      ],
      "id": "de1b6236-6635-477e-a1ba-0a5014978d63",
      "name": "Structure data"
    },
    {
      "parameters": {
        "jsCode": "const form = $('On form submission').first().json;\nconst aggregated = $('Aggregate').first().json.data || [];\n\nfunction hasEmail(raw) {\n  if (raw === undefined || raw === null) return false;\n  const value = String(raw).trim().toLowerCase();\n  return value !== '' && value !== 'not found';\n}\n\nconst leadsFound = aggregated.length;\nconst leadsWithEmails = aggregated.filter((item) => hasEmail(item.email)).length;\n\nreturn [{\n  json: {\n    RunAtUTC: new Date().toISOString(),\n    SearchQuery: form['Search Locations Query'] ?? '',\n    PagesRequested: String(form['Pages per query to scrape'] ?? ''),\n    LeadsFound: String(leadsFound),\n    LeadsWithEmails: String(leadsWithEmails),\n    LeadsWithoutEmails: String(leadsFound - leadsWithEmails),\n    DocumentID: $('Create Spreadsheet').first().json.spreadsheetId ?? '',\n    LeadsSheetID: String($('Create Spreadsheet').first().json.sheets?.[0]?.properties?.sheetId ?? ''),\n    RunMode: 'new_sheet'\n  }\n}];\n"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        2160,
        16
      ],
      "id": "5bd82a1b-7e44-4a0f-bc10-ebd96d25c84c",
      "name": "Prepare Run Log (New Sheet)"
    },
    {
      "parameters": {
        "operation": "appendOrUpdate",
        "documentId": {
          "__rl": true,
          "value": "={{ $('Create Spreadsheet').item.json.spreadsheetId }}",
          "mode": "id"
        },
        "sheetName": {
          "__rl": true,
          "value": "Runs",
          "mode": "name"
        },
        "columns": {
          "mappingMode": "autoMapInputData",
          "value": {},
          "matchingColumns": [],
          "schema": [
            {
              "id": "RunAtUTC",
              "displayName": "RunAtUTC",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true,
              "removed": false
            },
            {
              "id": "SearchQuery",
              "displayName": "SearchQuery",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true,
              "removed": false
            },
            {
              "id": "PagesRequested",
              "displayName": "PagesRequested",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true,
              "removed": false
            },
            {
              "id": "LeadsFound",
              "displayName": "LeadsFound",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true,
              "removed": false
            },
            {
              "id": "LeadsWithEmails",
              "displayName": "LeadsWithEmails",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true,
              "removed": false
            },
            {
              "id": "LeadsWithoutEmails",
              "displayName": "LeadsWithoutEmails",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true,
              "removed": false
            },
            {
              "id": "DocumentID",
              "displayName": "DocumentID",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true,
              "removed": false
            },
            {
              "id": "LeadsSheetID",
              "displayName": "LeadsSheetID",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true,
              "removed": false
            },
            {
              "id": "RunMode",
              "displayName": "RunMode",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true,
              "removed": false
            }
          ],
          "attemptToConvertTypes": false,
          "convertFieldsToString": false
        },
        "options": {}
      },
      "type": "n8n-nodes-base.googleSheets",
      "typeVersion": 4.7,
      "position": [
        2384,
        16
      ],
      "id": "d128e612-2c41-4087-8525-8da5199963ed",
      "name": "Save Run History (New Sheet)",
      "credentials": {
        "googleSheetsOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "onError": "continueRegularOutput"
    },
    {
      "parameters": {
        "jsCode": "const form = $('On form submission').first().json;\nconst aggregated = $('Aggregate').first().json.data || [];\n\nfunction hasEmail(raw) {\n  if (raw === undefined || raw === null) return false;\n  const value = String(raw).trim().toLowerCase();\n  return value !== '' && value !== 'not found';\n}\n\nconst leadsFound = aggregated.length;\nconst leadsWithEmails = aggregated.filter((item) => hasEmail(item.email)).length;\n\nreturn [{\n  json: {\n    RunAtUTC: new Date().toISOString(),\n    SearchQuery: form['Search Locations Query'] ?? '',\n    PagesRequested: String(form['Pages per query to scrape'] ?? ''),\n    LeadsFound: String(leadsFound),\n    LeadsWithEmails: String(leadsWithEmails),\n    LeadsWithoutEmails: String(leadsFound - leadsWithEmails),\n    DocumentID: String(form['Google Sheets Document Id'] ?? ''),\n    LeadsSheetID: String(form['Google Sheets ID'] ?? ''),\n    RunMode: 'existing_sheet'\n  }\n}];\n"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1936,
        208
      ],
      "id": "b55e8dc4-eab9-4679-89ae-b7d01c3f99a3",
      "name": "Prepare Run Log (Existing Sheet)"
    },
    {
      "parameters": {
        "operation": "appendOrUpdate",
        "documentId": {
          "__rl": true,
          "value": "={{ $('On form submission').item.json['Google Sheets Document Id'] }}",
          "mode": "id"
        },
        "sheetName": {
          "__rl": true,
          "value": "Runs",
          "mode": "name"
        },
        "columns": {
          "mappingMode": "autoMapInputData",
          "value": {},
          "matchingColumns": [],
          "schema": [
            {
              "id": "RunAtUTC",
              "displayName": "RunAtUTC",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true,
              "removed": false
            },
            {
              "id": "SearchQuery",
              "displayName": "SearchQuery",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true,
              "removed": false
            },
            {
              "id": "PagesRequested",
              "displayName": "PagesRequested",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true,
              "removed": false
            },
            {
              "id": "LeadsFound",
              "displayName": "LeadsFound",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true,
              "removed": false
            },
            {
              "id": "LeadsWithEmails",
              "displayName": "LeadsWithEmails",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true,
              "removed": false
            },
            {
              "id": "LeadsWithoutEmails",
              "displayName": "LeadsWithoutEmails",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true,
              "removed": false
            },
            {
              "id": "DocumentID",
              "displayName": "DocumentID",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true,
              "removed": false
            },
            {
              "id": "LeadsSheetID",
              "displayName": "LeadsSheetID",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true,
              "removed": false
            },
            {
              "id": "RunMode",
              "displayName": "RunMode",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true,
              "removed": false
            }
          ],
          "attemptToConvertTypes": false,
          "convertFieldsToString": false
        },
        "options": {}
      },
      "type": "n8n-nodes-base.googleSheets",
      "typeVersion": 4.7,
      "position": [
        2160,
        208
      ],
      "id": "c7c0b0f8-d86d-4c83-89fe-e61a81914009",
      "name": "Save Run History (Existing Sheet)",
      "credentials": {
        "googleSheetsOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "onError": "continueRegularOutput"
    }
  ],
  "connections": {
    "Search Google Maps": {
      "main": [
        [
          {
            "node": "Aggregate1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Extract Email from Website": {
      "main": [
        [
          {
            "node": "Email Found?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Email Found?": {
      "main": [
        [
          {
            "node": "Merge",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Merge1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Search Google for Email": {
      "main": [
        [
          {
            "node": "Extract Email from Google",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Extract Email from Google": {
      "main": [
        [
          {
            "node": "Merge",
            "type": "main",
            "index": 1
          }
        ]
      ]
    },
    "Create Spreadsheet": {
      "main": [
        [
          {
            "node": "Structure data",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Merge": {
      "main": [
        [
          {
            "node": "Aggregate",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Aggregate": {
      "main": [
        [
          {
            "node": "Is google sheet empty",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Split Out1": {
      "main": [
        [
          {
            "node": "Search Google Maps",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Aggregate1": {
      "main": [
        [
          {
            "node": "Remove Duplicates",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Remove Duplicates": {
      "main": [
        [
          {
            "node": "Is there a website?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Prepare Queries": {
      "main": [
        [
          {
            "node": "Split Out1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Scrape Websites": {
      "main": [
        [
          {
            "node": "Extract Email from Website",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "On form submission": {
      "main": [
        [
          {
            "node": "Prepare Queries",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Is there a website?": {
      "main": [
        [
          {
            "node": "Scrape Websites",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Merge1",
            "type": "main",
            "index": 1
          }
        ]
      ]
    },
    "Merge1": {
      "main": [
        [
          {
            "node": "Search Google for Email",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Is google sheet empty": {
      "main": [
        [
          {
            "node": "Create Spreadsheet",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Combine with old and remove duplicates",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Combine with old and remove duplicates": {
      "main": [
        [
          {
            "node": "Save leads to existing sheet",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Structure data": {
      "main": [
        [
          {
            "node": "Save Leads to Sheet",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Save Leads to Sheet": {
      "main": [
        [
          {
            "node": "Prepare Run Log (New Sheet)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Prepare Run Log (New Sheet)": {
      "main": [
        [
          {
            "node": "Save Run History (New Sheet)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Save leads to existing sheet": {
      "main": [
        [
          {
            "node": "Prepare Run Log (Existing Sheet)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Prepare Run Log (Existing Sheet)": {
      "main": [
        [
          {
            "node": "Save Run History (Existing Sheet)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "active": false,
  "settings": {
    "executionOrder": "v1",
    "binaryMode": "separate"
  },
  "versionId": "e529f524-83f6-4784-a64c-ce4cb3137618",
  "meta": {
    "templateCredsSetupCompleted": true
  },
  "id": "8yOJhMP3aH6rztyb",
  "tags": []
}