{
  "name": "Prospecting Engine with Google Places API",
  "nodes": [
    {
      "name": "Webhook",
      "type": "n8n-nodes-base.webhook",
      "position": [
        304,
        -64
      ],
      "parameters": {
        "path": "your-webhook-path",
        "options": {},
        "httpMethod": "POST",
        "responseMode": "responseNode"
      },
      "typeVersion": 2.1
    },
    {
      "name": "Set Config",
      "type": "n8n-nodes-base.code",
      "position": [
        480,
        -64
      ],
      "parameters": {
        "jsCode": "const b = $json.body || {};\nconst vertical = String(b.vertical || 'HVAC contractor').trim();\nconst location = String(b.location || 'Orlando, Florida').trim();\nconst limit = Math.min(parseInt(b.limit) || 20, 20);\nreturn [{ json: { vertical, location, limit, textQuery: `${vertical} in ${location}` } }];"
      },
      "typeVersion": 2
    },
    {
      "name": "Places Text Search",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        656,
        -64
      ],
      "parameters": {
        "url": "https://places.googleapis.com/v1/places:searchText",
        "method": "POST",
        "options": {},
        "jsonBody": "={{ JSON.stringify({ textQuery: $json.textQuery, maxResultCount: $json.limit }) }}",
        "sendBody": true,
        "sendHeaders": true,
        "specifyBody": "json",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth",
        "headerParameters": {
          "parameters": [
            {
              "name": "Content-Type",
              "value": "application/json"
            },
            {
              "name": "X-Goog-FieldMask",
              "value": "places.id,places.displayName,places.websiteUri,places.nationalPhoneNumber,places.formattedAddress,places.businessStatus,places.primaryTypeDisplayName"
            }
          ]
        }
      },
      "credentials": {
        "httpHeaderAuth": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 4.2
    },
    {
      "name": "Map Businesses",
      "type": "n8n-nodes-base.code",
      "position": [
        832,
        -64
      ],
      "parameters": {
        "jsCode": "const resp = $input.first().json;\nconst places = (resp && resp.places) || [];\n// Exclude YourCo's own competitors (IT / AI / automation / software / digital-agency / MSP shops)\nconst COMPETITOR = /\\b(it services|it support|information technology|managed (it|services?)|msp|software|saas|web (design|develop|developer|development)|app (design|develop|developer|development)|mobile app|digital marketing|marketing agency|advertising agency|seo|artificial intelligence|a\\.?i\\.?|automation|computer (repair|services?)|tech(nology)? (support|solutions?|services?|consult\\w*)|cyber\\s?security|cloud (services?|solutions?)|data (analytics|science)|consulting (firm|group|services))\\b/i;\nconst isComp = (n,cat)=>COMPETITOR.test(((n||'')+' '+(cat||'')).toLowerCase());\nreturn places\n  .filter(p => p.businessStatus === 'OPERATIONAL' && p.websiteUri)\n  .filter(p => !isComp((p.displayName&&p.displayName.text)||'', (p.primaryTypeDisplayName&&p.primaryTypeDisplayName.text)||''))\n  .map(p => ({ json: {\n    business_name: (p.displayName && p.displayName.text) || '',\n    website_url: p.websiteUri || '',\n    phone: p.nationalPhoneNumber || '',\n    address: p.formattedAddress || '',\n    place_id: p.id || '',\n    category: (p.primaryTypeDisplayName && p.primaryTypeDisplayName.text) || '',\n    vertical: $('Set Config').first().json.vertical,\n    location: $('Set Config').first().json.location,\n  }}));"
      },
      "typeVersion": 2
    },
    {
      "name": "Respond",
      "type": "n8n-nodes-base.respondToWebhook",
      "position": [
        1440,
        336
      ],
      "parameters": {
        "options": {},
        "respondWith": "json",
        "responseBody": "={{ JSON.stringify({ ok: true, vertical: $('Map + Dedup').first().json.vertical, location: $('Map + Dedup').first().json.location, total_found: $('Map + Dedup').first().json.total_found, total_emailable: $('Map + Dedup').first().json.total_emailable, fresh_inserted: $('Map + Dedup').first().json.fresh_inserted, duplicates: $('Map + Dedup').first().json.duplicates }) }}"
      },
      "typeVersion": 1.1
    },
    {
      "name": "Firecrawl Scrape",
      "type": "n8n-nodes-base.httpRequest",
      "onError": "continueRegularOutput",
      "position": [
        1136,
        -64
      ],
      "parameters": {
        "url": "https://api.firecrawl.dev/v1/scrape",
        "method": "POST",
        "options": {
          "timeout": 25000
        },
        "jsonBody": "={{ JSON.stringify({ url: $json.website_url, formats: ['markdown'], onlyMainContent: false, timeout: 20000 }) }}",
        "sendBody": true,
        "specifyBody": "json",
        "authentication": "predefinedCredentialType",
        "nodeCredentialType": "firecrawlApi"
      },
      "credentials": {
        "firecrawlApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 4.2
    },
    {
      "name": "Extract Email",
      "type": "n8n-nodes-base.code",
      "position": [
        1680,
        -64
      ],
      "parameters": {
        "mode": "runOnceForEachItem",
        "jsCode": "const ctx = $('Find Contact').item.json;\nconst fc = $json || {};\nconst md = (fc.data && fc.data.markdown) || fc.markdown || '';\nconst parked = !!ctx.is_parked;\nlet ce = (md.match(/[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}/g) || [])\n  .map(e => e.toLowerCase())\n  .filter(e => !/\\.(png|jpe?g|gif|webp|svg|css|js)$/.test(e))\n  .filter(e => !/(example\\.com|sentry|wixpress|godaddy|squarespace|cloudflare|@2x)/.test(e));\nlet emails = [...new Set([...(ctx.homepage_emails || []), ...ce])];\nconst domain = String(ctx.website_url || '').replace(/^https?:\\/\\//, '').replace(/^www\\./, '').split('/')[0].toLowerCase();\nconst onDomain = emails.filter(e => domain && e.endsWith('@' + domain));\nconst role = emails.find(e => /^(info|contact|hello|office|admin|sales|frontdesk|reception|appointments|service|hi)@/.test(e));\nconst preferred = parked ? '' : (onDomain[0] || role || emails[0] || '');\nconst fromContact = ce.includes(preferred);\nreturn { json: {\n  business_name: ctx.business_name, website_url: ctx.website_url, phone: ctx.phone,\n  address: ctx.address, place_id: ctx.place_id, category: ctx.category,\n  vertical: ctx.vertical, location: ctx.location,\n  verified_email: preferred, all_emails: emails.slice(0, 5), is_parked: parked,\n  email_source_url: preferred ? (fromContact ? ctx.contact_url : ctx.website_url) : ctx.website_url,\n  content_fetch_status: parked ? 'parked' : ((ctx.homepage_md_len || md.length) ? 'ok' : 'no_content')\n} };"
      },
      "typeVersion": 2
    },
    {
      "name": "Collect Results",
      "type": "n8n-nodes-base.code",
      "position": [
        2288,
        -64
      ],
      "parameters": {
        "jsCode": "const all = $input.all().map(i => i.json);\nconst withEmail = all.filter(b => b.verified_email);\nreturn [{ json: { ok: true, vertical: all[0] && all[0].vertical, location: all[0] && all[0].location,\n  total: all.length, with_email: withEmail.length, businesses: all } }];"
      },
      "typeVersion": 2
    },
    {
      "name": "Find Contact",
      "type": "n8n-nodes-base.code",
      "position": [
        1312,
        -64
      ],
      "parameters": {
        "mode": "runOnceForEachItem",
        "jsCode": "const biz = $('Map Businesses').item.json;\nconst fc = $json || {};\nconst md = (fc.data && fc.data.markdown) || fc.markdown || '';\nconst lower = md.toLowerCase();\nconst is_parked = md.length < 150 || /this domain (is|may be|name)|domain( name)? for sale|buy this domain|get this domain|hugedomains|parked free|courtesy of (godaddy|the)|domain parking|sedoparking|domainmarket|this web ?page is parked|future home of|parked free of charge/.test(lower);\nlet he = (md.match(/[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}/g) || [])\n  .map(e => e.toLowerCase())\n  .filter(e => !/\\.(png|jpe?g|gif|webp|svg|css|js)$/.test(e))\n  .filter(e => !/(example\\.com|sentry|wixpress|godaddy|squarespace|cloudflare|@2x)/.test(e));\nconst cm = md.match(/\\]\\((https?:\\/\\/[^)]*contact[^)]*)\\)/i);\nlet contact_url = cm ? cm[1].split('#')[0] : '';\nif (!contact_url && biz.website_url) contact_url = biz.website_url.replace(/\\/$/, '') + '/contact';\nreturn { json: { ...biz, homepage_emails: [...new Set(he)], homepage_md_len: md.length, contact_url, is_parked } };"
      },
      "typeVersion": 2
    },
    {
      "name": "Firecrawl Contact",
      "type": "n8n-nodes-base.httpRequest",
      "onError": "continueRegularOutput",
      "position": [
        1488,
        -64
      ],
      "parameters": {
        "url": "https://api.firecrawl.dev/v1/scrape",
        "method": "POST",
        "options": {
          "timeout": 25000
        },
        "jsonBody": "={{ JSON.stringify({ url: $json.contact_url, formats: ['markdown'], onlyMainContent: false, timeout: 20000 }) }}",
        "sendBody": true,
        "specifyBody": "json",
        "authentication": "predefinedCredentialType",
        "nodeCredentialType": "firecrawlApi"
      },
      "credentials": {
        "firecrawlApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 4.2
    },
    {
      "name": "Fetch Existing",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        304,
        336
      ],
      "parameters": {
        "url": "https://YOUR-PROJECT.supabase.co/rest/v1/pilot_prospects?select=website_url,verified_email&limit=5000",
        "options": {},
        "authentication": "predefinedCredentialType",
        "nodeCredentialType": "supabaseApi"
      },
      "credentials": {
        "supabaseApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 4.2
    },
    {
      "name": "Map + Dedup",
      "type": "n8n-nodes-base.code",
      "position": [
        480,
        336
      ],
      "parameters": {
        "jsCode": "const col = $('Collect Results').first().json;\nconst businesses = col.businesses || [];\nconst existing = $input.all().map(i => i.json);\nconst norm = u => String(u || '').toLowerCase().replace(/\\/$/, '');\nconst exUrls = new Set(existing.map(e => norm(e.website_url)));\nconst exEmails = new Set(existing.map(e => String(e.verified_email || '').toLowerCase()).filter(Boolean));\nconst emailable = businesses.filter(b => b.verified_email && b.mx_record_found && !b.is_parked);\nconst fresh = emailable.filter(b => !exUrls.has(norm(b.website_url)) && !exEmails.has(String(b.verified_email).toLowerCase()));\nconst uuid = () => { try { return crypto.randomUUID(); } catch (e) { return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, c => { const r = Math.random() * 16 | 0; const v = c === 'x' ? r : (r & 0x3 | 0x8); return v.toString(16); }); } };\nconst pains = [\n  'Manual lead intake and follow-up that eats staff hours',\n  'Missed-call and after-hours inquiries turning into lost jobs',\n  'Manual quoting, scheduling, and reminders',\n  'Chasing invoices and payments by hand'\n];\nconst now = new Date().toISOString();\nconst prospects = fresh.map(b => ({\n  id: uuid(),\n  workflow_name: 'YourCo - Prospecting Engine v2', workflow_version: '2.0', pilot_state: 'discovered',\n  pilot_vertical: b.vertical, business_name: b.business_name, category: b.category,\n  city_or_region: b.location, website_url: b.website_url, verified_email: b.verified_email,\n  email_source_url: b.email_source_url, phone: b.phone, mx_record_found: !!b.mx_record_found,\n  pain_points: pains,\n  YourCo_fit_summary: `${b.vertical} in ${b.location} likely loses hours to manual intake, scheduling, and follow-up that AI automation can handle.`,\n  confidence_score: 0.7, send_recommendation: 'review', review_status: 'new', generated_at: now, received_at: now\n}));\nconst esc = s => String(s || '').replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');\nconst reviewBase = 'https://YOUR-N8N-INSTANCE/webhook/YOUR-WEBHOOK-PATH';\nconst btn = (url, bg, label) => `<a href=\"${url}\" style=\"display:inline-block;padding:6px 14px;margin:0 3px;border-radius:5px;background:${bg};color:#ffffff;text-decoration:none;font-size:13px;font-weight:bold;\">${label}</a>`;\nconst rows = prospects.map(p => {\n  const site = esc(p.website_url);\n  const fire = btn(reviewBase + '?action=approve&id=' + p.id, '#16a34a', '\ud83d\udd25 Fire');\n  const deny = btn(reviewBase + '?action=reject&id=' + p.id, '#6b7280', 'Deny');\n  return `<tr><td style=\"padding:8px;border:1px solid #ddd;\"><strong>${esc(p.business_name)}</strong><br><a href=\"${site}\" style=\"color:#1B4F8A;font-size:12px;\">${site}</a></td><td style=\"padding:8px;border:1px solid #ddd;\"><a href=\"mailto:${esc(p.verified_email)}\">${esc(p.verified_email)}</a></td><td style=\"padding:8px;border:1px solid #ddd;\">${esc(p.phone)}</td><td style=\"padding:8px;border:1px solid #ddd;white-space:nowrap;\">${fire}${deny}</td></tr>`;\n}).join('');\nconst digest_html = `<div style=\"font-family:Arial,sans-serif;color:#1a1a1a;max-width:880px;\"><h2 style=\"color:#0B2545;margin:0 0 6px;\">${prospects.length} new ${esc(col.vertical)} prospects - ${esc(col.location)}</h2><p style=\"color:#555;\">Found ${businesses.length}, emailable+MX ${emailable.length}, NEW ${prospects.length}, dupes skipped ${emailable.length - fresh.length}. Parked/dead sites auto-filtered.</p>${prospects.length ? `<table style=\"border-collapse:collapse;font-size:14px;width:100%;\"><tr><th style=\"padding:8px;border:1px solid #ddd;background:#f4f6f9;text-align:left;\">Business</th><th style=\"padding:8px;border:1px solid #ddd;background:#f4f6f9;text-align:left;\">Email</th><th style=\"padding:8px;border:1px solid #ddd;background:#f4f6f9;text-align:left;\">Phone</th><th style=\"padding:8px;border:1px solid #ddd;background:#f4f6f9;text-align:left;\">Action</th></tr>${rows}</table>` : '<p>No new prospects this run.</p>'}<p style=\"color:#555;font-size:13px;margin-top:14px;line-height:1.6;\"><strong>\ud83d\udd25 Fire</strong> = approve for outreach. The next promote window (Mon/Thu 11am ET) drafts and sends the email.<br><strong>Deny</strong> = reject. This business is never emailed, and won't resurface in future runs.</p><p style=\"color:#888;font-size:12px;margin-top:6px;\">All rows inserted as review_status=new in pilot_prospects. Nothing goes out until you Fire it.</p></div>`;\nreturn [{ json: { vertical: col.vertical, location: col.location, total_found: businesses.length,\n  total_emailable: emailable.length, fresh_inserted: prospects.length,\n  duplicates: emailable.length - fresh.length, prospects, digest_html } }];"
      },
      "typeVersion": 2
    },
    {
      "name": "Insert Prospects",
      "type": "n8n-nodes-base.httpRequest",
      "onError": "continueRegularOutput",
      "position": [
        656,
        336
      ],
      "parameters": {
        "url": "https://YOUR-PROJECT.supabase.co/rest/v1/pilot_prospects",
        "method": "POST",
        "options": {},
        "jsonBody": "={{ JSON.stringify($json.prospects) }}",
        "sendBody": true,
        "sendHeaders": true,
        "specifyBody": "json",
        "authentication": "predefinedCredentialType",
        "headerParameters": {
          "parameters": [
            {
              "name": "Content-Type",
              "value": "application/json"
            },
            {
              "name": "Prefer",
              "value": "return=minimal"
            }
          ]
        },
        "nodeCredentialType": "supabaseApi"
      },
      "credentials": {
        "supabaseApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 4.2
    },
    {
      "name": "Telegram Digest",
      "type": "n8n-nodes-base.httpRequest",
      "onError": "continueRegularOutput",
      "position": [
        1264,
        336
      ],
      "parameters": {
        "url": "https://YOUR-N8N-INSTANCE/webhook/YOUR-WEBHOOK-PATH",
        "method": "POST",
        "options": {},
        "jsonBody": "={{ JSON.stringify({ text: '\\uD83E\\uDDF2 <b>Prospecting run</b>\\n' + $('Map + Dedup').first().json.vertical + ' \u00b7 ' + $('Map + Dedup').first().json.location + '\\nFound ' + $('Map + Dedup').first().json.total_found + ', emailable ' + $('Map + Dedup').first().json.total_emailable + ', NEW inserted ' + $('Map + Dedup').first().json.fresh_inserted + ' (' + $('Map + Dedup').first().json.duplicates + ' dupes skipped)' }) }}",
        "sendBody": true,
        "specifyBody": "json"
      },
      "typeVersion": 4.2
    },
    {
      "name": "MX Lookup",
      "type": "n8n-nodes-base.httpRequest",
      "onError": "continueRegularOutput",
      "position": [
        1968,
        -64
      ],
      "parameters": {
        "url": "=https://dns.google/resolve?type=MX&name={{ (($json.verified_email || '@invalid').split('@')[1]) || 'invalid' }}",
        "options": {}
      },
      "typeVersion": 4.2
    },
    {
      "name": "Apply MX",
      "type": "n8n-nodes-base.code",
      "position": [
        2128,
        -64
      ],
      "parameters": {
        "mode": "runOnceForEachItem",
        "jsCode": "const biz = $('Extract Email').item.json;\nconst r = $json || {};\nconst hasMX = !!(r.Answer && r.Answer.length) || (r.Status === 0 && Array.isArray(r.Answer) && r.Answer.length > 0);\nreturn { json: { ...biz, mx_record_found: biz.verified_email ? hasMX : false } };"
      },
      "typeVersion": 2
    },
    {
      "name": "Email Digest",
      "type": "n8n-nodes-base.microsoftOutlook",
      "onError": "continueRegularOutput",
      "position": [
        1088,
        336
      ],
      "parameters": {
        "subject": "=New prospects: {{ $('Map + Dedup').first().json.vertical }} - {{ $('Map + Dedup').first().json.location }} ({{ $('Map + Dedup').first().json.fresh_inserted }} new)",
        "bodyContent": "={{ $('Map + Dedup').first().json.digest_html }}",
        "toRecipients": "you@example.com",
        "additionalFields": {
          "from": "you@example.com",
          "bodyContentType": "html"
        }
      },
      "credentials": {
        "microsoftOutlookOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 2
    },
    {
      "name": "One Digest Email",
      "type": "n8n-nodes-base.code",
      "position": [
        912,
        336
      ],
      "parameters": {
        "jsCode": "// Email Digest was firing once per inserted row -> N identical digests.\n// The digest_html (from Map + Dedup) already lists every prospect with Fire/Deny buttons.\n// Send it ONCE; send nothing when no new prospects were inserted.\nreturn $input.all().length ? [$input.first()] : [];"
      },
      "typeVersion": 2
    },
    {
      "name": "Sticky: Overview & Setup",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        880,
        -608
      ],
      "parameters": {
        "color": 7,
        "width": 896,
        "height": 320,
        "content": "## Local lead prospecting engine\n\nGive it a business type and a location. It finds local businesses, scrapes their sites for a contact email, validates the email's domain, dedupes against leads you already have, saves the new ones, and emails you one clean digest.\n\n### Setup (replace before running)\n- Credentials: **Google Places API**, **Firecrawl**, **Supabase**, **Microsoft Outlook**, and a **Telegram relay** webhook\n- `YOUR-PROJECT.supabase.co` -> your Supabase URL, with a `pilot_prospects` table\n- Trigger: POST the webhook a body like `{ \"vertical\": \"dentists\", \"location\": \"Orlando FL\" }`\n- Set your notification email and Telegram chat in the digest nodes"
      },
      "typeVersion": 1
    },
    {
      "name": "Sticky: 1 Find businesses",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        256,
        -256
      ],
      "parameters": {
        "color": 4,
        "width": 748,
        "height": 358,
        "content": "## 1. Find businesses\nThe **Webhook** receives a business type + location. **Set Config** parses it, **Places Text Search** queries Google Places, and **Map Businesses** turns the results into one item per business."
      },
      "typeVersion": 1
    },
    {
      "name": "Sticky: 2 Find a contact email",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1040,
        -256
      ],
      "parameters": {
        "color": 4,
        "width": 816,
        "height": 358,
        "content": "## 2. Find a contact email\n**Firecrawl** scrapes each business site (and a likely contact page); **Find Contact** and **Extract Email** pull out the best email address."
      },
      "typeVersion": 1
    },
    {
      "name": "Sticky: 3 Validate the email",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1888,
        -256
      ],
      "parameters": {
        "color": 4,
        "width": 576,
        "height": 358,
        "content": "## 3. Validate the email\n**MX Lookup** checks the domain has real mail servers (a DNS MX record); **Apply MX** keeps only addresses that can actually receive mail. **Collect Results** gathers the businesses with a valid email."
      },
      "typeVersion": 1
    },
    {
      "name": "Sticky: 4 Dedup and save",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        256,
        144
      ],
      "parameters": {
        "color": 4,
        "width": 560,
        "height": 390,
        "content": "## 4. Dedup and save\n**Fetch Existing** pulls prospects you already have from Supabase, **Map + Dedup** drops anyone already on your list, and **Insert Prospects** saves only the new ones."
      },
      "typeVersion": 1
    },
    {
      "name": "Sticky: 5 Send one digest",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        848,
        144
      ],
      "parameters": {
        "color": 4,
        "width": 780,
        "height": 390,
        "content": "## 5. Send one digest\n**One Digest Email** bundles the new prospects into a single message (one email, not one per lead). **Email Digest** sends it via Outlook, **Telegram Digest** pings your chat, and **Respond** closes the webhook."
      },
      "typeVersion": 1
    }
  ],
  "settings": {
    "executionOrder": "v1"
  },
  "connections": {
    "Webhook": {
      "main": [
        [
          {
            "node": "Set Config",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Apply MX": {
      "main": [
        [
          {
            "node": "Collect Results",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "MX Lookup": {
      "main": [
        [
          {
            "node": "Apply MX",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Set Config": {
      "main": [
        [
          {
            "node": "Places Text Search",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Map + Dedup": {
      "main": [
        [
          {
            "node": "Insert Prospects",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Email Digest": {
      "main": [
        [
          {
            "node": "Telegram Digest",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Find Contact": {
      "main": [
        [
          {
            "node": "Firecrawl Contact",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Extract Email": {
      "main": [
        [
          {
            "node": "MX Lookup",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Fetch Existing": {
      "main": [
        [
          {
            "node": "Map + Dedup",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Map Businesses": {
      "main": [
        [
          {
            "node": "Firecrawl Scrape",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Collect Results": {
      "main": [
        [
          {
            "node": "Fetch Existing",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Telegram Digest": {
      "main": [
        [
          {
            "node": "Respond",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Firecrawl Scrape": {
      "main": [
        [
          {
            "node": "Find Contact",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Insert Prospects": {
      "main": [
        [
          {
            "node": "One Digest Email",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "One Digest Email": {
      "main": [
        [
          {
            "node": "Email Digest",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Firecrawl Contact": {
      "main": [
        [
          {
            "node": "Extract Email",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Places Text Search": {
      "main": [
        [
          {
            "node": "Map Businesses",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}