{
  "name": "Lodge Lead Engine",
  "nodes": [
    {
      "parameters": {},
      "id": "trigger",
      "name": "Manual Trigger",
      "type": "n8n-nodes-base.manualTrigger",
      "typeVersion": 1,
      "position": [
        200,
        300
      ]
    },
    {
      "parameters": {
        "values": {
          "number": [
            {
              "name": "page",
              "value": 1
            },
            {
              "name": "per_page",
              "value": 25
            }
          ],
          "string": [
            {
              "name": "keywords",
              "value": "fishing lodge"
            }
          ]
        },
        "options": {}
      },
      "id": "config",
      "name": "Config",
      "type": "n8n-nodes-base.set",
      "typeVersion": 3,
      "position": [
        420,
        300
      ]
    },
    {
      "parameters": {
        "method": "POST",
        "url": "https://api.apollo.io/api/v1/mixed_people/search",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "Content-Type",
              "value": "application/json"
            },
            {
              "name": "Cache-Control",
              "value": "no-cache"
            }
          ]
        },
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={\n  \"q_keywords\": \"{{$json.keywords}}\",\n  \"person_titles\": [\"owner\", \"general manager\", \"president\", \"founder\", \"proprietor\"],\n  \"person_locations\": [\"United States\"],\n  \"organization_num_employees_ranges\": [\"1,10\", \"11,20\", \"21,50\"],\n  \"page\": {{$json.page}},\n  \"per_page\": {{$json.per_page}}\n}",
        "options": {}
      },
      "id": "apollo",
      "name": "Apollo: Search Lodges",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        640,
        300
      ],
      "credentials": {
        "httpHeaderAuth": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "fieldToSplitOut": "people",
        "options": {}
      },
      "id": "split",
      "name": "Split People",
      "type": "n8n-nodes-base.itemLists",
      "typeVersion": 3,
      "position": [
        860,
        300
      ]
    },
    {
      "parameters": {
        "jsCode": "// Normalize one Apollo person into a lead shape and fetch their site\nconst p = $json;\nconst org = p.organization || {};\nconst website = org.website_url || (org.primary_domain ? `https://${org.primary_domain}` : null);\n\nif (!website || !p.email) {\n  return { json: { skip: true, reason: 'no website or email', person: p } };\n}\n\nlet html = '';\nlet detected = [];\nlet fetchError = null;\ntry {\n  const res = await this.helpers.httpRequest({\n    method: 'GET',\n    url: website,\n    timeout: 15000,\n    returnFullResponse: false,\n    headers: { 'User-Agent': 'Mozilla/5.0 (compatible; LodgeQualifier/1.0)' }\n  });\n  html = (typeof res === 'string' ? res : JSON.stringify(res)).toLowerCase();\n} catch (e) {\n  fetchError = e.message;\n}\n\nconst signatures = {\n  checkfront: ['checkfront.com', 'cfwidget'],\n  resnexus:   ['resnexus.com'],\n  fareharbor: ['fareharbor.com', 'fh-button'],\n  bookeo:     ['bookeo.com'],\n  cloudbeds:  ['cloudbeds.com', 'mybookings.cloudbeds'],\n  lodgify:    ['lodgify.com'],\n  thinkres:   ['thinkreservations.com'],\n  innroad:    ['innroad.com'],\n  rezdy:      ['rezdy.com'],\n  peek:       ['peekpro.com', 'bookwithpeek'],\n  webrez:     ['webrezpro.com'],\n  resharmonics:['resharmonics.com'],\n};\nfor (const [name, needles] of Object.entries(signatures)) {\n  if (needles.some(n => html.includes(n))) detected.push(name);\n}\n\nconst booking_widget_detected = detected.length > 0;\n\nreturn {\n  json: {\n    skip: false,\n    business_name: org.name || p.name || 'Unknown',\n    contact_name: p.name || null,\n    email: p.email,\n    phone: org.phone || null,\n    city: p.city || org.city || null,\n    state: p.state || org.state || null,\n    website,\n    booking_widget_detected,\n    detected_widgets: detected,\n    fetch_error: fetchError,\n    html_snippet: html.slice(0, 3000),\n    title: p.title || null,\n    source: 'apollo+n8n'\n  }\n};"
      },
      "id": "qualify",
      "name": "Fetch Site + Detect Widgets",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1080,
        300
      ]
    },
    {
      "parameters": {
        "conditions": {
          "boolean": [
            {
              "value1": "={{$json.skip}}",
              "value2": false
            },
            {
              "value1": "={{$json.booking_widget_detected}}",
              "value2": false
            }
          ]
        }
      },
      "id": "filter",
      "name": "Qualified (No Widget)",
      "type": "n8n-nodes-base.if",
      "typeVersion": 1,
      "position": [
        1300,
        300
      ]
    },
    {
      "parameters": {
        "resource": "message",
        "modelId": {
          "mode": "list",
          "value": "claude-sonnet-4-6"
        },
        "messages": {
          "values": [
            {
              "role": "user",
              "content": "=You are writing ONE opening sentence for a cold email to a fishing lodge owner.\n\nBusiness: {{$json.business_name}}\nLocation: {{$json.city}}, {{$json.state}}\nWebsite text snippet: {{$json.html_snippet}}\n\nWrite a single sentence (under 25 words) that feels personal and specific to their lodge. Reference something real from the snippet \u2014 a species, a lake, a guide, an experience. Do NOT mention websites, booking, software, or sales. Just a warm opener. Output only the sentence, no quotes, no prefix."
            }
          ]
        },
        "options": {
          "maxTokens": 100
        }
      },
      "id": "personalize",
      "name": "Claude: Personalize",
      "type": "@n8n/n8n-nodes-langchain.anthropic",
      "typeVersion": 1,
      "position": [
        1520,
        240
      ],
      "credentials": {
        "anthropicApi": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "jsCode": "// Attach personalization line back onto the lead and strip the html_snippet\nconst lead = $('Qualified (No Widget)').item.json;\nconst line = ($json.content?.[0]?.text || $json.text || '').trim();\nreturn {\n  json: {\n    business_name: lead.business_name,\n    contact_name: lead.contact_name,\n    email: lead.email,\n    phone: lead.phone,\n    city: lead.city,\n    state: lead.state,\n    website: lead.website,\n    booking_widget_detected: lead.booking_widget_detected,\n    detected_widgets: lead.detected_widgets,\n    personalization_line: line,\n    source: lead.source\n  }\n};"
      },
      "id": "shape",
      "name": "Shape Lead",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1740,
        240
      ]
    },
    {
      "parameters": {
        "aggregate": "aggregateAllItemData",
        "destinationFieldName": "leads",
        "options": {}
      },
      "id": "aggregate",
      "name": "Aggregate to Batch",
      "type": "n8n-nodes-base.itemLists",
      "typeVersion": 3,
      "position": [
        1960,
        240
      ]
    },
    {
      "parameters": {
        "method": "POST",
        "url": "https://crm.twenty1-media.com/api/leads/ingest",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "Content-Type",
              "value": "application/json"
            }
          ]
        },
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={{ JSON.stringify({ leads: $json.leads }) }}",
        "options": {}
      },
      "id": "ingest",
      "name": "POST to CRM /api/leads/ingest",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        2180,
        240
      ],
      "credentials": {
        "httpHeaderAuth": {
          "name": "<your credential>"
        }
      }
    }
  ],
  "connections": {
    "Manual Trigger": {
      "main": [
        [
          {
            "node": "Config",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Config": {
      "main": [
        [
          {
            "node": "Apollo: Search Lodges",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Apollo: Search Lodges": {
      "main": [
        [
          {
            "node": "Split People",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Split People": {
      "main": [
        [
          {
            "node": "Fetch Site + Detect Widgets",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Fetch Site + Detect Widgets": {
      "main": [
        [
          {
            "node": "Qualified (No Widget)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Qualified (No Widget)": {
      "main": [
        [
          {
            "node": "Claude: Personalize",
            "type": "main",
            "index": 0
          }
        ],
        []
      ]
    },
    "Claude: Personalize": {
      "main": [
        [
          {
            "node": "Shape Lead",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Shape Lead": {
      "main": [
        [
          {
            "node": "Aggregate to Batch",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Aggregate to Batch": {
      "main": [
        [
          {
            "node": "POST to CRM /api/leads/ingest",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "settings": {
    "executionOrder": "v1"
  }
}