{
  "name": "Agent 2 (Tavily): Contact Enrichment — finds founder & email every 3h",
  "description": null,
  "active": false,
  "nodes": [
    {
      "parameters": {},
      "id": "t_manual",
      "name": "Manual Trigger",
      "type": "n8n-nodes-base.manualTrigger",
      "typeVersion": 1,
      "position": [
        -608,
        -80
      ]
    },
    {
      "parameters": {
        "operation": "getAll",
        "tableId": "prospects",
        "limit": 15,
        "filters": {
          "conditions": [
            {
              "keyName": "status",
              "condition": "eq",
              "keyValue": "new"
            }
          ]
        }
      },
      "id": "n_fetch",
      "name": "Fetch New Prospects",
      "type": "n8n-nodes-base.supabase",
      "typeVersion": 1,
      "position": [
        -384,
        16
      ]
    },
    {
      "parameters": {
        "jsCode": "\nconst items = $input.all();\nreturn items.map(item => {\n  const f = item.json.fields || item.json;\n  const company = f.Company || item.json.company || '';\n  const website = f.Website || item.json.website || '';\n  const id = item.json.id || item.json.ID || '';\n  const prompt = `Search LinkedIn and the web to find the founder, CEO, and top board members or directors of \"${company}\" (website: ${website}).\nFind up to 4 key decision-makers. Prioritize: Founder > CEO > MD > Director.\nFor each person return: full name, job title, and LinkedIn profile URL.\nReturn ONLY valid JSON array: [{\"name\": \"...\", \"title\": \"...\", \"linkedin_url\": \"...\"}, ...]`;\n  return { json: { id, company, website,\n    city: f.City || item.json.city || '',\n    niche: f.Niche || item.json.niche || '',\n    country: f.Country || item.json.country || '',\n    prompt } };\n});\n"
      },
      "id": "n_people_prompt",
      "name": "Build People Search Prompt",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        -160,
        16
      ]
    },
    {
      "parameters": {
        "method": "POST",
        "url": "https://moviesbar1-resource.openai.azure.com/openai/v1/responses",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth",
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={{ {\"model\":\"gpt-5.4\",\"input\":$json.prompt,\"max_output_tokens\":3000} }}",
        "options": {
          "batching": {
            "batch": {
              "batchSize": 4
            }
          },
          "timeout": 280000
        }
      },
      "id": "n_people",
      "name": "GPT Extract — Key People",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        512,
        16
      ],
      "retryOnFail": true,
      "maxTries": 5,
      "waitBetweenTries": 5000,
      "onError": "continueRegularOutput"
    },
    {
      "parameters": {
        "mode": "runOnceForEachItem",
        "jsCode": "const __ext=(j)=>j.choices?.[0]?.message?.content || ((j.output||[]).flatMap(o=>o.content||[]).filter(c=>c.type==='output_text').map(c=>c.text).join('')) || '';\n\nconst src = $('Build People Search Prompt').item.json;\nlet content = __ext($json) || '';\nconst fence = content.match(/```(?:json)?\\s*([\\s\\S]*?)```/);\nif (fence) content = fence[1].trim();\nconst arr = content.match(/\\[[\\s\\S]*\\]/); if (arr) content = arr[0];\nlet people = []; try { people = JSON.parse(content); } catch(e) {}\nconst p1 = people[0] || {}; const p2 = people[1] || {};\nreturn { json: { id: src.id, company: src.company, website: src.website, city: src.city,\n  niche: src.niche, country: src.country, key_people: JSON.stringify(people),\n  founder_name: p1.name || '', founder_linkedin: p1.linkedin_url || '',\n  person2_name: p2.name || '', person2_linkedin: p2.linkedin_url || '' } };\n"
      },
      "id": "n_parse_people",
      "name": "Parse Key People",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        736,
        16
      ]
    },
    {
      "parameters": {
        "jsCode": "\nconst items = $input.all();\nreturn items.map(item => {\n  const { company, website, founder_name, person2_name } = item.json;\n  const prompt = `Find valid business email addresses for decision-makers at \"${company}\" (website: ${website}).\nTarget people in priority order: ${founder_name || 'the founder/CEO'}${person2_name ? ', then ' + person2_name : ''}, then a generic role inbox.\nSearch: the website contact/about page, LinkedIn, press releases, Crunchbase, and public mentions.\nReturn the 2-3 BEST distinct candidate emails, most likely first.\nCommon formats: firstname@domain.com, firstname.lastname@domain.com, info@domain.com, hello@domain.com.\nReturn ONLY valid JSON: {\"emails\":[{\"email\":\"...\",\"person_name\":\"...\",\"confidence\":\"high|medium|low\",\"source\":\"...\"}]}`;\n  return { json: { ...item.json, prompt } };\n});\n"
      },
      "id": "n_email_prompt",
      "name": "Build Email Find Prompt",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        960,
        16
      ]
    },
    {
      "parameters": {
        "method": "POST",
        "url": "https://moviesbar1-resource.openai.azure.com/openai/v1/responses",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth",
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={{ {\"model\":\"gpt-5.4\",\"input\":$json.prompt,\"max_output_tokens\":3000} }}",
        "options": {
          "batching": {
            "batch": {
              "batchSize": 4
            }
          },
          "timeout": 280000
        }
      },
      "id": "n_find_email",
      "name": "GPT Extract — Emails",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        1632,
        16
      ],
      "retryOnFail": true,
      "maxTries": 5,
      "waitBetweenTries": 5000,
      "onError": "continueRegularOutput"
    },
    {
      "parameters": {
        "mode": "runOnceForEachItem",
        "jsCode": "const __ext=(j)=>j.choices?.[0]?.message?.content || ((j.output||[]).flatMap(o=>o.content||[]).filter(c=>c.type==='output_text').map(c=>c.text).join('')) || '';\n\nconst src = $('Build Email Find Prompt').item.json;\nlet content = __ext($json) || '';\nconst fence = content.match(/```(?:json)?\\s*([\\s\\S]*?)```/);\nif (fence) content = fence[1].trim();\nlet parsed = {}; try { parsed = JSON.parse(content); } catch(e) {}\nlet list = Array.isArray(parsed.emails) ? parsed.emails : [];\nif (!list.length && parsed.email) list = [{ email: parsed.email, confidence: parsed.confidence, source: parsed.source }];\nconst seen = new Set(); const emails = [];\nfor (const e of list) {\n  const addr = ((e && e.email) || '').toLowerCase().trim();\n  if (!addr || !addr.includes('@') || seen.has(addr)) continue;\n  seen.add(addr);\n  emails.push({ email: addr, person_name: e.person_name || '', confidence: (e.confidence||'medium').toLowerCase(), source: e.source || 'kimi-email-search' });\n}\nreturn { json: { ...src, email_list: emails, found_email: emails[0]?.email || '', email_confidence: emails[0]?.confidence || 'low' } };\n"
      },
      "id": "n_parse_email",
      "name": "Parse Email Result",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1856,
        16
      ]
    },
    {
      "parameters": {
        "mode": "runOnceForEachItem",
        "jsCode": "const __ext=(j)=>j.choices?.[0]?.message?.content || ((j.output||[]).flatMap(o=>o.content||[]).filter(c=>c.type==='output_text').map(c=>c.text).join('')) || '';\n\nconst src = $('Build Website Extract Prompt').item.json;\nlet content = __ext($json) || '{}';\nconst fence = content.match(/```(?:json)?\\s*([\\s\\S]*?)```/);\nif (fence) content = fence[1].trim();\nlet parsed = {}; try { parsed = JSON.parse(content); } catch(e) {}\nconst cand = Array.isArray(src.email_list) ? src.email_list.slice() : [];\nconst seen = new Set(cand.map(c => c.email));\nconst web = (parsed.contact_email || '').toLowerCase().trim();\nif (web && web.includes('@') && !seen.has(web)) { seen.add(web); cand.push({ email: web, person_name: '', confidence: 'medium', source: 'website-contact' }); }\nconst final_email = cand[0]?.email || '';\nconst premium_fit = (parsed.premium_fit || 'medium').toLowerCase();\n\n// After MAX_ATTEMPTS tries with no email found, park as 'enrich_failed' instead of resetting\n// to 'new' — resetting made every failed prospect re-burn 3 web searches on every run.\nconst MAX_ATTEMPTS = 2;\nconst sd = $getWorkflowStaticData('global');\nif (typeof sd.enrichAttempts !== 'object' || !sd.enrichAttempts) sd.enrichAttempts = {};\nlet status_out = 'enriched';\nif (final_email && final_email.includes('@')) {\n  delete sd.enrichAttempts[src.id];\n} else {\n  const n = (sd.enrichAttempts[src.id] || 0) + 1;\n  sd.enrichAttempts[src.id] = n;\n  status_out = n >= MAX_ATTEMPTS ? 'enrich_failed' : 'new';\n}\n\nreturn { json: { ...src, raw_website_data: JSON.stringify({\n    title: parsed.title, meta_desc: parsed.meta_desc, has_blog: parsed.has_blog,\n    social_links: parsed.social_links, has_testimonials: parsed.has_testimonials }),\n  email_candidates: cand, final_email, premium_fit, premium_signals: parsed.premium_signals || '', status_out } };\n"
      },
      "id": "n_parse_website",
      "name": "Parse Website Data",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        2752,
        16
      ]
    },
    {
      "parameters": {
        "operation": "update",
        "tableId": "prospects",
        "filters": {
          "conditions": [
            {
              "keyName": "id",
              "condition": "eq",
              "keyValue": "={{ $json.id }}"
            }
          ]
        },
        "fieldsUi": {
          "fieldValues": [
            {
              "fieldId": "status",
              "fieldValue": "={{ $json.status_out }}"
            },
            {
              "fieldId": "founder_name",
              "fieldValue": "={{ $json.founder_name }}"
            },
            {
              "fieldId": "founder_linkedin",
              "fieldValue": "={{ $json.founder_linkedin }}"
            },
            {
              "fieldId": "key_people",
              "fieldValue": "={{ $json.key_people }}"
            },
            {
              "fieldId": "email",
              "fieldValue": "={{ $json.final_email }}"
            },
            {
              "fieldId": "raw_website_data",
              "fieldValue": "={{ $json.raw_website_data }}"
            },
            {
              "fieldId": "premium_fit",
              "fieldValue": "={{ $json.premium_fit }}"
            },
            {
              "fieldId": "premium_signals",
              "fieldValue": "={{ $json.premium_signals }}"
            },
            {
              "fieldId": "person2_name",
              "fieldValue": "={{ $json.person2_name }}"
            },
            {
              "fieldId": "person2_linkedin",
              "fieldValue": "={{ $json.person2_linkedin }}"
            },
            {
              "fieldId": "email_valid",
              "fieldValue": "={{ $json.email_valid_out }}"
            },
            {
              "fieldId": "email_status",
              "fieldValue": "={{ $json.email_status_out }}"
            }
          ]
        }
      },
      "id": "n_at_update",
      "name": "Airtable — Update Enriched",
      "type": "n8n-nodes-base.supabase",
      "typeVersion": 1,
      "position": [
        3200,
        -80
      ],
      "retryOnFail": true,
      "maxTries": 2,
      "waitBetweenTries": 2000,
      "onError": "continueRegularOutput"
    },
    {
      "parameters": {
        "jsCode": "\nconst allItems = $('Parse Website Data').all();\nconst retried = allItems.filter(i => !i.json.founder_name && !i.json.final_email).length;\nconst items = allItems.filter(i => i.json.founder_name || i.json.final_email);\nconst count = items.length;\nconst withEmail = items.filter(i => i.json.final_email && i.json.final_email.includes('@')).length;\nconst premium = items.filter(i => ['high','medium'].includes((i.json.premium_fit||'').toLowerCase())).length;\nconst td='padding:8px;border:1px solid #e2e8f0;font-size:13px;vertical-align:top';\nconst th='padding:8px;border:1px solid #e2e8f0;text-align:left;font-size:13px;background:#ebf8ff';\nconst badge=(f)=>{const c={high:'#48bb78',medium:'#ecc94b',low:'#fc8181'}[f]||'#a0aec0';return `<span style=\"background:${c};color:#fff;padding:2px 8px;border-radius:10px;font-size:11px\">${f||'?'}</span>`;};\nconst rows=items.map(i=>{const d=i.json;let pc=0;try{pc=JSON.parse(d.key_people||'[]').length;}catch(e){}\n  const li=d.founder_linkedin?`<a href=\"${d.founder_linkedin}\">Profile</a>`:'—';\n  const site=d.website?`<a href=\"${d.website}\">${d.website.replace(/^https?:\\/\\//,'').replace(/\\/$/,'')}</a>`:'—';\n  const email=(d.final_email&&d.final_email.includes('@'))?d.final_email:'<span style=\"color:#e53e3e\">not found</span>';\n  return `<tr><td style=\"${td}\"><strong>${d.company||'—'}</strong></td><td style=\"${td}\">${badge((d.premium_fit||'').toLowerCase())}</td><td style=\"${td}\">${d.city||'—'}</td><td style=\"${td}\">${d.founder_name||'—'}</td><td style=\"${td}\">${email}</td><td style=\"${td}\">${li}</td><td style=\"${td}\">${pc} ppl</td><td style=\"${td}\">${site}</td></tr>`;\n}).join('');\nconst html=`<html><body style=\"font-family:Arial,sans-serif;max-width:950px;margin:auto;padding:20px\">\n<h2 style=\"color:#2d3748\">🔎 Contact Enrichment Report</h2>\n<p>Enriched <strong>${count}</strong> · <strong>${premium}</strong> premium fit · <strong>${withEmail}</strong> with email.${retried ? ` · <strong style=\"color:#dd6b20\">${retried}</strong> failed (Kimi unreachable) — left as <code>new</code>, will retry next run.` : ''}</p>\n<table style=\"width:100%;border-collapse:collapse;margin-top:10px\">\n<tr><th style=\"${th}\">Company</th><th style=\"${th}\">Premium</th><th style=\"${th}\">City</th><th style=\"${th}\">Founder</th><th style=\"${th}\">Email</th><th style=\"${th}\">LinkedIn</th><th style=\"${th}\">Key People</th><th style=\"${th}\">Website</th></tr>\n${rows}</table>\n<p style=\"color:#a0aec0;font-size:12px;margin-top:20px\">Meraku Contact Enrichment · premium-qualified · 7x daily</p>\n</body></html>`;\nreturn [{ json: { count, withEmail, premium, summary_html: html } }];\n"
      },
      "id": "n_summary",
      "name": "Build Summary",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        3424,
        -80
      ]
    },
    {
      "parameters": {
        "sendTo": "you@yourdomain.com",
        "subject": "=[FYI] Enrichment — {{ $json.total }} leads enriched (24h)",
        "message": "={{ $json.summary_html }}",
        "options": {
          "appendAttribution": false
        }
      },
      "id": "n_summary_email",
      "name": "Gmail — Daily Summary",
      "type": "n8n-nodes-base.gmail",
      "typeVersion": 2.1,
      "position": [
        64,
        336
      ]
    },
    {
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "cronExpression",
              "expression": "30 7 * * *"
            },
            {
              "field": "cronExpression",
              "expression": "45 19 * * *"
            },
            {
              "field": "cronExpression",
              "expression": "30 11 * * *"
            },
            {
              "field": "cronExpression",
              "expression": "30 15 * * *"
            }
          ]
        }
      },
      "id": "t_sched",
      "name": "2x Daily (off-peak)",
      "type": "n8n-nodes-base.scheduleTrigger",
      "typeVersion": 1.1,
      "position": [
        -608,
        112
      ],
      "retryOnFail": true
    },
    {
      "parameters": {
        "tableId": "activity_log",
        "fieldsUi": {
          "fieldValues": [
            {
              "fieldId": "summary",
              "fieldValue": "=Enrichment: {{ $json.premium }} premium / {{ $json.count }} enriched"
            },
            {
              "fieldId": "workflow",
              "fieldValue": "Contact Enrichment"
            },
            {
              "fieldId": "action",
              "fieldValue": "enriched"
            },
            {
              "fieldId": "count",
              "fieldValue": "={{ $json.premium }}"
            },
            {
              "fieldId": "details",
              "fieldValue": "={{ $json.count }} enriched · {{ $json.withEmail }} with email · {{ $json.premium }} premium-fit"
            }
          ]
        }
      },
      "id": "log_enrich",
      "name": "Log → Activity_Log",
      "type": "n8n-nodes-base.supabase",
      "typeVersion": 1,
      "position": [
        3648,
        -80
      ],
      "retryOnFail": true,
      "maxTries": 2,
      "waitBetweenTries": 2000,
      "onError": "continueRegularOutput"
    },
    {
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "cronExpression",
              "expression": "45 7 * * *"
            }
          ]
        }
      },
      "id": "enr_daily_trig",
      "name": "Daily 7:45 AM IST",
      "type": "n8n-nodes-base.scheduleTrigger",
      "typeVersion": 1.1,
      "position": [
        -608,
        336
      ]
    },
    {
      "parameters": {
        "operation": "getAll",
        "tableId": "activity_log",
        "returnAll": true,
        "matchType": "allFilters",
        "filters": {
          "conditions": [
            {
              "keyName": "workflow",
              "condition": "eq",
              "keyValue": "Contact Enrichment"
            },
            {
              "keyName": "created_at",
              "condition": "gt",
              "keyValue": "={{ new Date(Date.now() - 86400000).toISOString() }}"
            }
          ]
        }
      },
      "id": "enr_fetch24",
      "name": "Fetch 24h Activity",
      "type": "n8n-nodes-base.supabase",
      "typeVersion": 1,
      "position": [
        -384,
        336
      ],
      "onError": "continueRegularOutput"
    },
    {
      "parameters": {
        "jsCode": "\n// ROBUST: keep only Contact Enrichment rows in JS, so it's correct regardless of the\n// Supabase node's \"Must Match\" (Any/All) setting which was silently pulling every workflow.\nconst cutoff = new Date(Date.now() - 86400000).toISOString();\nconst rows = $input.all().filter(r => {\n  if ((r.json.workflow || '') !== 'Contact Enrichment') return false;\n  const ts = String(r.json.created_at || r.json.inserted_at || '');\n  return ts && ts >= cutoff;   // enforce 24h in code (the Supabase created_at filter is ignored)\n});\n\nlet enriched = 0, withEmail = 0, premium = 0;\nconst re = /(\\d+)\\s*enriched.*?(\\d+)\\s*with email.*?(\\d+)\\s*premium/i;\nfor (const r of rows) {\n  const m = String(r.json.details || '').match(re);\n  if (m) { enriched += +m[1]; withEmail += +m[2]; premium += +m[3]; }\n  else   { enriched += Number(r.json.count || 0); }   // fallback for old rows\n}\nconst runs     = rows.length;\nconst noEmail  = Math.max(0, enriched - withEmail);\nconst emailPct = enriched ? Math.round(withEmail / enriched * 100) : 0;\nconst premPct  = enriched ? Math.round(premium  / enriched * 100) : 0;\n\nconst card = (val, label, sub) => `<td style=\"padding:16px 10px;border:1px solid #e2e8f0;border-radius:12px;text-align:center;background:#fff;width:33%\">\n  <div style=\"font-size:28px;font-weight:700;color:#2d3748;line-height:1\">${val}</div>\n  <div style=\"font-size:12px;color:#718096;margin-top:4px\">${label}</div>${sub ? `<div style=\"font-size:11px;color:#a0aec0;margin-top:1px\">${sub}</div>` : ''}</td>`;\n\nconst html = `<!doctype html><html><body style=\"font-family:-apple-system,Segoe UI,Arial,sans-serif;color:#1a202c;max-width:560px;margin:auto;padding:8px\">\n<h2 style=\"margin:0 0 2px\">Contact Enrichment — last 24h</h2>\n<p style=\"color:#718096;margin:0 0 16px;font-size:13px\">${runs} enrichment run${runs === 1 ? '' : 's'} · new prospects turned into contactable leads</p>\n<table style=\"border-collapse:separate;border-spacing:8px;width:100%\"><tr>\n${card(enriched, 'prospects enriched', '')}\n${card(withEmail, 'with a valid email', emailPct + '%')}\n${card(premium, 'premium-fit', premPct + '%')}\n</tr></table>\n${noEmail ? `<p style=\"color:#dd6b20;font-size:13px;margin:14px 4px 0\">${noEmail} enriched without a usable email yet — kept as <code>new</code> and retried next runs.</p>` : ''}\n<p style=\"color:#a0aec0;font-size:12px;margin-top:22px\">Meraku · daily enrichment summary · 7:45 AM IST<br>Replies &amp; bounces are handled in the main Digest email.</p>\n</body></html>`;\n\nreturn [{ json: { total: enriched, enriched, withEmail, premium, runs, summary_html: html } }];\n"
      },
      "id": "enr_agg24",
      "name": "Aggregate 24h Enrichment",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        -160,
        336
      ]
    },
    {
      "parameters": {
        "jsCode": "\nconst out = [];\nfor (const item of $input.all()) {\n  const d = item.json;\n  const cands = Array.isArray(d.email_candidates) ? d.email_candidates : [];\n  cands.forEach((c, idx) => {\n    if (!c.email || !c.email.includes('@')) return;\n    const vs = String(c.verify_status || '');\n    out.push({ json: {\n      prospect_id: String(d.id),\n      email: c.email,\n      person_name: c.person_name || (idx === 0 ? d.founder_name : d.person2_name) || '',\n      person_role: '',\n      linkedin_url: idx === 0 ? (d.founder_linkedin || '') : (idx === 1 ? (d.person2_linkedin || '') : ''),\n      rank: idx + 1,\n      confidence: c.confidence || 'medium',\n      source: c.source || 'agent2',\n      status: vs === 'undeliverable' ? 'invalid' : ((vs === 'deliverable' || vs === 'unknown') ? 'verified' : 'pending'),\n      status_detail: vs || '',\n      is_active: !!d.final_email && c.email.toLowerCase() === String(d.final_email).toLowerCase()\n    }});\n  });\n}\nreturn out;"
      },
      "id": "pe_flatten_cands",
      "name": "Flatten Email Candidates",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        3200,
        112
      ]
    },
    {
      "parameters": {
        "tableId": "prospect_emails",
        "fieldsUi": {
          "fieldValues": [
            {
              "fieldId": "prospect_id",
              "fieldValue": "={{ $json.prospect_id }}"
            },
            {
              "fieldId": "email",
              "fieldValue": "={{ $json.email }}"
            },
            {
              "fieldId": "person_name",
              "fieldValue": "={{ $json.person_name }}"
            },
            {
              "fieldId": "person_role",
              "fieldValue": "={{ $json.person_role }}"
            },
            {
              "fieldId": "linkedin_url",
              "fieldValue": "={{ $json.linkedin_url }}"
            },
            {
              "fieldId": "rank",
              "fieldValue": "={{ $json.rank }}"
            },
            {
              "fieldId": "confidence",
              "fieldValue": "={{ $json.confidence }}"
            },
            {
              "fieldId": "source",
              "fieldValue": "={{ $json.source }}"
            },
            {
              "fieldId": "is_active",
              "fieldValue": "={{ $json.is_active }}"
            },
            {
              "fieldId": "status",
              "fieldValue": "={{ $json.status }}"
            },
            {
              "fieldId": "status_detail",
              "fieldValue": "={{ $json.status_detail }}"
            }
          ]
        }
      },
      "id": "pe_insert_cand",
      "name": "Insert Email Candidate",
      "type": "n8n-nodes-base.supabase",
      "typeVersion": 1,
      "position": [
        3424,
        112
      ],
      "continueOnFail": true
    },
    {
      "parameters": {
        "url": "={{ $json.website }}",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "User-Agent",
              "value": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36"
            }
          ]
        },
        "options": {
          "response": {
            "response": {
              "responseFormat": "text",
              "outputPropertyName": "html"
            }
          },
          "timeout": 30000
        }
      },
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        2080,
        16
      ],
      "id": "aaaa1111-cost-fix-0000-fetchhtml0001",
      "name": "Fetch Website HTML",
      "alwaysOutputData": true,
      "onError": "continueRegularOutput"
    },
    {
      "parameters": {
        "mode": "runOnceForEachItem",
        "jsCode": "\nconst src = $('Parse Email Result').item.json;\nconst html = String($json.html || $json.data || '');\nconst title = (html.match(/<title[^>]*>([\\s\\S]*?)<\\/title>/i) || [])[1] || '';\nconst metaDesc = (html.match(/<meta[^>]+name=[\"']description[\"'][^>]*content=[\"']([^\"']*)[\"']/i) || html.match(/<meta[^>]+content=[\"']([^\"']*)[\"'][^>]*name=[\"']description[\"']/i) || [])[1] || '';\nconst links = [...html.matchAll(/href=[\"']([^\"'\\s>]+)[\"']/gi)].map(m => m[1]);\nconst social = [...new Set(links.filter(h => /facebook\\.com|instagram\\.com|linkedin\\.com|twitter\\.com|x\\.com|youtube\\.com/i.test(h)))].slice(0, 10);\nconst text = html\n  .replace(/<script[\\s\\S]*?<\\/script>/gi, ' ')\n  .replace(/<style[\\s\\S]*?<\\/style>/gi, ' ')\n  .replace(/<[^>]+>/g, ' ')\n  .replace(/&[a-zA-Z#0-9]+;/g, ' ')\n  .replace(/\\s+/g, ' ')\n  .trim()\n  .slice(0, 15000);\n\nconst pageBlock = text\n  ? `FETCHED HOMEPAGE DATA for \"${src.company}\" (${src.website}):\nTITLE: ${title}\nMETA DESCRIPTION: ${metaDesc}\nSOCIAL LINKS FOUND IN HTML: ${JSON.stringify(social)}\nPAGE TEXT (truncated):\n${text}`\n  : `The website ${src.website} for \"${src.company}\" could not be fetched (unreachable or no website). A missing/broken website is a NOT-premium signal, but mark premium_fit \"medium\" if you cannot tell.`;\n\nconst prompt = `Analyze this business website from the fetched content below. Do NOT search the web.\n\n${pageBlock}\n\nPART A — Basic data:\n1. Page title and meta description (exact text)\n2. Has a blog/news section? (yes/no)\n3. Social media links present (list)\n4. Has testimonials/reviews? (yes/no)\n5. Contact email visible? (copy it)\n\nPART B — PREMIUM FIT (critical): Meraku Media works with premium high-ticket service businesses (elder care, addiction treatment, private clinics, timeshare exit, tax relief, equipment financing, residency/citizenship services) that can pay a $5,000+/month retainer (Rs 1,00,000+/month in India). Judge whether THIS business is a premium prospect in ITS industry.\n- Signs of PREMIUM (high): luxury/concierge positioning, high fees or premium packages, affluent target clientele, polished branding, multiple locations, corporate/investor backing, awards, professional website.\n- Signs of NOT premium (low): free/government/charity/trust/NGO/subsidized, \"affordable\"/budget messaging, welfare-oriented, poor or no real website, serves a welfare/charity segment with no marketing budget.\n- medium = paid private business but unclear if they have real marketing budget.\n\nReturn ONLY valid JSON: {\"title\":\"...\",\"meta_desc\":\"...\",\"has_blog\":true,\"social_links\":[],\"has_testimonials\":true,\"contact_email\":\"...\",\"premium_fit\":\"high|medium|low\",\"premium_signals\":\"1-2 sentences citing specific evidence from the site\"}`;\nreturn { json: { ...src, prompt } };\n"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        2304,
        16
      ],
      "id": "aaaa1111-cost-fix-0000-buildprmpt01",
      "name": "Build Website Extract Prompt"
    },
    {
      "parameters": {
        "method": "POST",
        "url": "https://moviesbar1-resource.openai.azure.com/openai/v1/responses",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth",
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={{ {\"model\":\"gpt-5.4\",\"input\":$json.prompt,\"max_output_tokens\":3000} }}",
        "options": {
          "batching": {
            "batch": {
              "batchSize": 4
            }
          },
          "timeout": 280000
        }
      },
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        2528,
        16
      ],
      "id": "aaaa1111-cost-fix-0000-gptextract1",
      "name": "GPT Extract — Website Data"
    },
    {
      "parameters": {
        "jsCode": "const TAVILY_KEYS=[\"YOUR_TAVILY_API_KEY_1\",\"YOUR_TAVILY_API_KEY_2\",\"YOUR_TAVILY_API_KEY_3\"];\nconst __sd=$getWorkflowStaticData('global');\nif(typeof __sd.tavilyExhausted!=='object'||!__sd.tavilyExhausted) __sd.tavilyExhausted={};\nconst __sleep=(ms)=>new Promise(r=>setTimeout(r,ms));\nconst tavilySearch=async(query,maxResults)=>{\n  for(const k of TAVILY_KEYS){\n    const ex=__sd.tavilyExhausted[k];\n    if(ex && Date.now()-ex<24*3600*1000) continue;\n    let attempts=0;\n    while(attempts<3){\n      attempts++;\n      try{\n        const res=await this.helpers.httpRequest({method:'POST',url:'https://api.tavily.com/search',\n          headers:{'Authorization':'Bearer '+k,'Content-Type':'application/json'},\n          body:{query:String(query||'').slice(0,390),search_depth:'advanced',max_results:maxResults,include_answer:true},\n          json:true,returnFullResponse:true,ignoreHttpStatusErrors:true,timeout:90000});\n        const code=Number(res.statusCode||0);\n        if(code===429){ if(attempts<3){await __sleep(1500*attempts);continue;} break; }\n        if(code===432||code===433||code===401||code===403){ __sd.tavilyExhausted[k]=Date.now(); break; }\n        if(code<200||code>=300){ break; }\n        return res.body||{results:[]};\n      }catch(e){ break; }\n    }\n  }\n  try{ await this.helpers.httpRequest({method:'GET',url:'https://your-n8n-instance.com/webhook/yourbrand-tavily-exhausted',timeout:10000}); }catch(e){}\n  return {results:[],answer:'',tavily_error:'all_keys_exhausted'};\n};\nconst out=[];\nfor(const it of $input.all()){\n  const d=it.json||{};\n  out.push({json: await tavilySearch(((d.company || \"\") + \" founder CEO director owner LinkedIn\"), 8)});\n  await __sleep(400);\n}\nreturn out;"
      },
      "name": "Tavily — Find Key People",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        64,
        16
      ],
      "id": "2c02d33e-6a30-4d1e-8e5b-0ec8832fafcc"
    },
    {
      "parameters": {
        "jsCode": "const items = $input.all();\nconst srcs = $('Build People Search Prompt').all();\nreturn items.map((it, i) => {\n  const src = srcs[i] ? srcs[i].json : {};\n  const t = it.json || {};\n  const results = (t.results || []).map((r, n) => `[${n+1}] ${r.title || ''}\\nURL: ${r.url || ''}\\n${(r.content || '').slice(0, 1200)}`).join('\\n\\n');\n  const ctx = (t.answer ? `Search answer: ${t.answer}\\n\\n` : '') + (results || 'No web results found.');\n  const prompt = (src.prompt || '') + `\\n\\n---\\nWEB SEARCH RESULTS below — use these as your ONLY research data. Extract what is asked from them; do not invent facts:\\n\\n` + ctx;\n  return { json: { ...src, prompt } };\n});"
      },
      "name": "Attach Web Context — Find Key People",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        288,
        16
      ],
      "id": "484508d8-9ab3-4b0f-86a1-38619a9e6005"
    },
    {
      "parameters": {
        "jsCode": "const TAVILY_KEYS=[\"YOUR_TAVILY_API_KEY_1\",\"YOUR_TAVILY_API_KEY_2\",\"YOUR_TAVILY_API_KEY_3\"];\nconst __sd=$getWorkflowStaticData('global');\nif(typeof __sd.tavilyExhausted!=='object'||!__sd.tavilyExhausted) __sd.tavilyExhausted={};\nconst __sleep=(ms)=>new Promise(r=>setTimeout(r,ms));\nconst tavilySearch=async(query,maxResults)=>{\n  for(const k of TAVILY_KEYS){\n    const ex=__sd.tavilyExhausted[k];\n    if(ex && Date.now()-ex<24*3600*1000) continue;\n    let attempts=0;\n    while(attempts<3){\n      attempts++;\n      try{\n        const res=await this.helpers.httpRequest({method:'POST',url:'https://api.tavily.com/search',\n          headers:{'Authorization':'Bearer '+k,'Content-Type':'application/json'},\n          body:{query:String(query||'').slice(0,390),search_depth:'advanced',max_results:maxResults,include_answer:true},\n          json:true,returnFullResponse:true,ignoreHttpStatusErrors:true,timeout:90000});\n        const code=Number(res.statusCode||0);\n        if(code===429){ if(attempts<3){await __sleep(1500*attempts);continue;} break; }\n        if(code===432||code===433||code===401||code===403){ __sd.tavilyExhausted[k]=Date.now(); break; }\n        if(code<200||code>=300){ break; }\n        return res.body||{results:[]};\n      }catch(e){ break; }\n    }\n  }\n  try{ await this.helpers.httpRequest({method:'GET',url:'https://your-n8n-instance.com/webhook/yourbrand-tavily-exhausted',timeout:10000}); }catch(e){}\n  return {results:[],answer:'',tavily_error:'all_keys_exhausted'};\n};\nconst out=[];\nfor(const it of $input.all()){\n  const d=it.json||{};\n  out.push({json: await tavilySearch((((d.founder_name ? d.founder_name + \" \" : \"\") + (d.company || \"\") + \" email contact \" + (d.website || \"\"))), 8)});\n  await __sleep(400);\n}\nreturn out;"
      },
      "name": "Tavily — Find Email",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1184,
        16
      ],
      "id": "61d75759-5ad7-4cb8-8d6c-9b6b6ebec8fe"
    },
    {
      "parameters": {
        "jsCode": "const items = $input.all();\nconst srcs = $('Build Email Find Prompt').all();\nreturn items.map((it, i) => {\n  const src = srcs[i] ? srcs[i].json : {};\n  const t = it.json || {};\n  const results = (t.results || []).map((r, n) => `[${n+1}] ${r.title || ''}\\nURL: ${r.url || ''}\\n${(r.content || '').slice(0, 1200)}`).join('\\n\\n');\n  const ctx = (t.answer ? `Search answer: ${t.answer}\\n\\n` : '') + (results || 'No web results found.');\n  const prompt = (src.prompt || '') + `\\n\\n---\\nWEB SEARCH RESULTS below — use these as your ONLY research data. Extract what is asked from them; do not invent facts:\\n\\n` + ctx;\n  return { json: { ...src, prompt } };\n});"
      },
      "name": "Attach Web Context — Find Email",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1408,
        16
      ],
      "id": "50bdec7d-5b4f-411b-92e9-5dd67e89bb94"
    },
    {
      "parameters": {
        "jsCode": "const ABSTRACT_KEYS=[\"YOUR_ABSTRACTAPI_KEY_1\",\"YOUR_ABSTRACTAPI_KEY_2\",\"YOUR_ABSTRACTAPI_KEY\",\"YOUR_ABSTRACTAPI_KEY\"];\nconst __sd=$getWorkflowStaticData('global');\nif(typeof __sd.abstractExhausted!=='object'||!__sd.abstractExhausted) __sd.abstractExhausted={};\nif(typeof __sd.abstractCache!=='object'||!__sd.abstractCache) __sd.abstractCache={};\nconst __sleep=(ms)=>new Promise(r=>setTimeout(r,ms));\nconst verifyEmail=async(email)=>{\n  email=String(email||'').toLowerCase().trim();\n  if(!email||!email.includes('@')) return 'undeliverable';\n  const cached=__sd.abstractCache[email];\n  if(cached && Date.now()-cached.ts < 30*24*3600*1000) return cached.status;\n  const KEY_COOLDOWN=24*3600*1000;\n  for(const key of ABSTRACT_KEYS){\n    const ex=__sd.abstractExhausted[key];\n    if(ex && Date.now()-ex<KEY_COOLDOWN) continue;\n    let attempts=0;\n    while(attempts<5){\n      attempts++;\n      try{\n        await __sleep(1400+Math.floor(Math.random()*800));\n        const res=await this.helpers.httpRequest({method:'GET',\n          url:'https://emailreputation.abstractapi.com/v1/?api_key='+key+'&email='+encodeURIComponent(email),\n          json:true, returnFullResponse:true, ignoreHttpStatusErrors:true, timeout:20000});\n        const code=Number(res.statusCode||0);\n        if(code===422){\n          const msg422=JSON.stringify(res.body||'').toLowerCase();\n          if(msg422.includes('quota')){ __sd.abstractExhausted[key]=Date.now(); break; }\n          return 'error';\n        }\n        if(code===429){\n          const msg=JSON.stringify(res.body||'').toLowerCase();\n          const isQuota = msg.includes('quota') && !msg.includes('per second');\n          if(isQuota){ __sd.abstractExhausted[key]=Date.now(); break; }\n          if(attempts<5){ await __sleep(1200*attempts+500); continue; }\n          return 'error';\n        }\n        if(code===401||code===402||code===403){ __sd.abstractExhausted[key]=Date.now(); break; }\n        if(code<200||code>=300) return 'error';\n        const status=String((res.body&&res.body.email_deliverability&&res.body.email_deliverability.status)||'unknown').toLowerCase();\n        const final=(status==='deliverable')?'deliverable':(status==='undeliverable'?'undeliverable':'unknown');\n        __sd.abstractCache[email]={status:final,ts:Date.now()};\n        return final;\n      }catch(e){ return 'error'; }\n    }\n  }\n  return 'quota_exhausted';\n};\n\nconst out=[];\nfor(const item of $input.all()){\n  const d={...item.json};\n  const cands=Array.isArray(d.email_candidates)?d.email_candidates.map(c=>({...c})):[];\n  let pick='', pickStatus='', firstUnknown='', quota=false;\n  for(const c of cands){\n    const em=String(c.email||'').toLowerCase().trim();\n    if(!em||!em.includes('@')){c.verify_status='skipped';continue;}\n    const st=await verifyEmail(em);\n    c.verify_status=st;\n    if(st==='quota_exhausted'){quota=true;break;}\n    if(st==='deliverable'){pick=em;pickStatus='verified_deliverable';break;}\n    if((st==='unknown'||st==='error')&&!firstUnknown) firstUnknown=em;\n  }\n  if(!pick&&firstUnknown){pick=firstUnknown;pickStatus='verified_unknown';}\n  if(quota){\n    d.email_status_out='unverified';\n    if(pick) d.final_email=pick;\n    d.email_valid_out=!!(d.final_email);\n  } else if(pick){\n    d.final_email=pick; d.email_valid_out=true; d.email_status_out=pickStatus;\n  } else if(cands.length){\n    d.final_email=''; d.email_valid_out=false; d.email_status_out='no_valid_email';\n  } else {\n    d.email_valid_out=!!d.final_email; d.email_status_out='unverified';\n  }\n  d.email_candidates=cands;\n  out.push({json:d});\n}\nreturn out;"
      },
      "name": "Verify Emails (AbstractAPI)",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        2976,
        16
      ],
      "id": "c2341d0a-8923-4337-83e8-bea8771d84b2"
    }
  ],
  "connections": {
    "Manual Trigger": {
      "main": [
        [
          {
            "node": "Fetch New Prospects",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Fetch New Prospects": {
      "main": [
        [
          {
            "node": "Build People Search Prompt",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Build People Search Prompt": {
      "main": [
        [
          {
            "node": "Tavily — Find Key People",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "GPT Extract — Key People": {
      "main": [
        [
          {
            "node": "Parse Key People",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Parse Key People": {
      "main": [
        [
          {
            "node": "Build Email Find Prompt",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Build Email Find Prompt": {
      "main": [
        [
          {
            "node": "Tavily — Find Email",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "GPT Extract — Emails": {
      "main": [
        [
          {
            "node": "Parse Email Result",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Parse Email Result": {
      "main": [
        [
          {
            "node": "Fetch Website HTML",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Parse Website Data": {
      "main": [
        [
          {
            "node": "Verify Emails (AbstractAPI)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Airtable — Update Enriched": {
      "main": [
        [
          {
            "node": "Build Summary",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Build Summary": {
      "main": [
        [
          {
            "node": "Log → Activity_Log",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Fetch 24h Activity": {
      "main": [
        [
          {
            "node": "Aggregate 24h Enrichment",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Aggregate 24h Enrichment": {
      "main": [
        [
          {
            "node": "Gmail — Daily Summary",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Daily 7:45 AM IST": {
      "main": [
        [
          {
            "node": "Fetch 24h Activity",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Flatten Email Candidates": {
      "main": [
        [
          {
            "node": "Insert Email Candidate",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Fetch Website HTML": {
      "main": [
        [
          {
            "node": "Build Website Extract Prompt",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Build Website Extract Prompt": {
      "main": [
        [
          {
            "node": "GPT Extract — Website Data",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "GPT Extract — Website Data": {
      "main": [
        [
          {
            "node": "Parse Website Data",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "2x Daily (off-peak)": {
      "main": [
        [
          {
            "node": "Fetch New Prospects",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Tavily — Find Key People": {
      "main": [
        [
          {
            "node": "Attach Web Context — Find Key People",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Attach Web Context — Find Key People": {
      "main": [
        [
          {
            "node": "GPT Extract — Key People",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Tavily — Find Email": {
      "main": [
        [
          {
            "node": "Attach Web Context — Find Email",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Attach Web Context — Find Email": {
      "main": [
        [
          {
            "node": "GPT Extract — Emails",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Verify Emails (AbstractAPI)": {
      "main": [
        [
          {
            "node": "Airtable — Update Enriched",
            "type": "main",
            "index": 0
          },
          {
            "node": "Flatten Email Candidates",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "settings": {
    "executionOrder": "v1",
    "callerPolicy": "workflowsFromSameOwner",
    "availableInMCP": false,
    "binaryMode": "separate"
  }
}