{
  "id": "akQ25uAX0U54kTCW",
  "meta": {
    "templateCredsSetupCompleted": true
  },
  "name": "TOFU Sales Intelligence",
  "tags": [],
  "nodes": [
    {
      "id": "4f8ab4a4-9058-44bd-a715-0efc7bd39653",
      "name": "Webhook",
      "type": "n8n-nodes-base.webhook",
      "notes": "This webhook can be from any source, which is through new trial sign ups. It could be a website lead magnet, it could be an OpenClaw/CrewAI endpoint for your agent. Recommend adding a heather auth to protect your endpoint. ",
      "position": [
        -6880,
        1104
      ],
      "parameters": {
        "path": "new-lead",
        "options": {},
        "httpMethod": "POST"
      },
      "notesInFlow": true,
      "typeVersion": 2
    },
    {
      "id": "89d9501f-7668-4195-96e9-b53e67fa9058",
      "name": "Extract Email Root Domain",
      "type": "n8n-nodes-base.set",
      "notes": "This node will simply extract the root domain from the email address. ",
      "position": [
        -6656,
        1008
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "1bfef44b-ad78-49f7-9b17-b5c0243cbe3d",
              "name": "root_domain",
              "type": "string",
              "value": "={{ $json.email.split('@')[1] }}"
            }
          ]
        }
      },
      "notesInFlow": true,
      "typeVersion": 3.4
    },
    {
      "id": "666523a8-c730-468b-b32a-77067f828c90",
      "name": "Check to make sure email is not null",
      "type": "n8n-nodes-base.if",
      "notes": "This node is a safety/error-handling check to ensure the email is not null/empty",
      "position": [
        -6208,
        1008
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 2,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "8b7d6a14-7545-4684-a8a6-7fcd9543c44b",
              "operator": {
                "type": "string",
                "operation": "notEmpty",
                "singleValue": true
              },
              "leftValue": "={{ $('Extract Email Root Domain').item.json.root_domain }}",
              "rightValue": "null"
            },
            {
              "id": "72cfc6f1-f802-4abd-b98f-f7ecab73f2a3",
              "operator": {
                "type": "string",
                "operation": "notEquals"
              },
              "leftValue": "={{ $('Extract Email Root Domain').item.json.root_domain }}",
              "rightValue": "null"
            }
          ]
        }
      },
      "notesInFlow": true,
      "typeVersion": 2.2
    },
    {
      "id": "a7105881-9d32-4500-a093-e6688b2ea386",
      "name": "Structured Output Parser1",
      "type": "@n8n/n8n-nodes-langchain.outputParserStructured",
      "position": [
        -4416,
        1216
      ],
      "parameters": {
        "schemaType": "manual",
        "inputSchema": "{\n  \"output\": {\n    \"company_name\": \"Cardinal Refer\",\n    \"followers\": 48,\n    \"employee_count\": 14,\n    \"headquarters_location\": \"Stanford, California\",\n    \"industry\": \"Healthcare\",\n    \"description\": \"Founded in 2020 by two Stanford students...\"\n  }\n}"
      },
      "typeVersion": 1.2
    },
    {
      "id": "7afdd847-8471-4ba6-89a5-4122bfa4d066",
      "name": "LinkedIn Agent",
      "type": "@n8n/n8n-nodes-langchain.agent",
      "notes": "This agent is configured to extract LinkedIn information such as followers, employee_count, headquarters_location, industry, and description. It uses scrap.io as the enrichment tool. ",
      "position": [
        -4624,
        992
      ],
      "parameters": {
        "text": "=Based on the input below, extract the following information from the company linkedin page and ensure numeric values are returned as numbers, not strings: \n\n- Company Name (as text)\n- Linkedin followers (as a number)\n- Number of employees or staff (as a number)\n- Company headquarters location (City and country as text)\n- Industry (extract the primary industry category as shown on LinkedIn)\n- Description or overview of the company (as text). Do not make anything up. Just extract the description that has been put into the LinkedIn company Url and make it easy to read and proper english.\n\nOutput format should maintain consistent field names:\n{\n  \"output\": {\n    \"company_name\": string,\n    \"followers\": number,\n    \"employee_count\": number,\n    \"headquarters_location\": string,\n    \"industry\": string,\n    \"description\": string\n  }\n}\n\nInput the entire URL as described below using the website scraper tool\nInput: {{ $json.debugInfo.cleanLinkedInUrl }}",
        "options": {
          "systemMessage": "=You are a Linkedin data analyst and your job is to extract company information such as the followers, headquarters location, company description, industry, and staff (employee) count of a company page on linkedin and nothing else. Do not output any other numbers other than the scraped data. Use the website_tool to extract this information."
        },
        "promptType": "define",
        "hasOutputParser": true
      },
      "notesInFlow": true,
      "retryOnFail": true,
      "typeVersion": 1.7
    },
    {
      "id": "c03a70c8-72fa-4517-a9dc-3acb414fd22d",
      "name": "Check if website exists",
      "type": "n8n-nodes-base.httpRequest",
      "notes": "This is an error handling node to ensure the website is responsive and valid.",
      "onError": "continueRegularOutput",
      "position": [
        -6000,
        1008
      ],
      "parameters": {
        "url": "=https://{{ $json.root_domain }}",
        "method": "HEAD",
        "options": {
          "response": {
            "response": {
              "neverError": true,
              "fullResponse": true
            }
          }
        }
      },
      "notesInFlow": true,
      "retryOnFail": true,
      "typeVersion": 4.2
    },
    {
      "id": "02ca2491-7ad5-48fa-a146-6fb7b8d7cb33",
      "name": "Normalize Country",
      "type": "@n8n/n8n-nodes-langchain.openAi",
      "notes": "This normalizes the company location to ensure we always get structured output for scoring downstream.",
      "position": [
        -3808,
        992
      ],
      "parameters": {
        "modelId": {
          "__rl": true,
          "mode": "list",
          "value": "gpt-4.1-nano",
          "cachedResultName": "GPT-4.1-NANO"
        },
        "options": {},
        "messages": {
          "values": [
            {
              "role": "system",
              "content": "=Given the location text: {{ $json.output.headquarters_location }}, return the official country name or \"Unknown\"."
            },
            {
              "content": "=You are a helpful assistant that extracts standardized country names from any location string. The user will provide a location that might be a city, state, region, or country. \n\nYour job:\n1. Identify the country the location is in. \n2. Return only the official country name in English (e.g., \"United States\", \"Canada\", \"United Kingdom\", \"India\", etc.).\n3. If the location is a city and/or state within the United States (e.g., \"Denver, CO\", \"New York, NY\", \"Texas\"), return \"United States\".\n4. If you cannot determine the country with confidence, return \"Unknown\".\n5. Do not provide any additional text or explanations\u2014only the country name or \"Unknown\".\n\n"
            }
          ]
        },
        "jsonOutput": "={{ true }}"
      },
      "credentials": {
        "openAiApi": {
          "name": "<your credential>"
        }
      },
      "notesInFlow": true,
      "retryOnFail": true,
      "typeVersion": 1.8
    },
    {
      "id": "7834015a-904d-4acb-9e78-96429a83280b",
      "name": "Score Country",
      "type": "n8n-nodes-base.code",
      "notes": "Location Score Assignment\n\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nPurpose:\nAssigns a numeric locationScore (0\u201310) to each lead based on the company's country \u2014 prioritizes high-value markets like US, UK, Canada, etc.\n\nHow it works:\n\u2022 Pulls the country from the incoming item (from previous normalization step)\n\u2022 Normalizes to lowercase + trims whitespace\n\u2022 Matches against tiered lists of countries/regions\n\u2022 Assigns score:\n  - 10: US\n  - 8: Tier 2 (UK, Canada, Australia, NZ)\n  - 7: Western Europe, Nordics, East Asia, Singapore/HK\n  - 6: Middle East (UAE, Israel, etc.)\n  - 4: Other EU\n  - 3: LatAm\n  - 2: South Asia, SE Asia, Eastern Europe\n  - 1: Africa\n  - 0: Rest of world / unknown\n\nOutput fields added:\n- normalizedCountry: original country value (for reference)\n- locationScore: integer 0\u201310\n\nWhy this structure?\n- Avoids node-name lookups (prevents execution hangs in recent n8n versions)\n- Uses direct input data access ($input.all() + item.json)\n- Safe fallbacks for missing/misnamed fields\n\nDebug tips:\n- If score is always 0: check upstream node outputs the country correctly (look at JSON preview)\n- To change scoring: edit the arrays or if/else conditions\n- Test with 1 item first (switch node to \"Execute Once\")\n\nThis keeps the workflow fast and reliable even with hundreds of leads.",
      "position": [
        -3472,
        992
      ],
      "parameters": {
        "jsCode": "/**\n * Assign location score based on country\n * Assumes the country is already available in the incoming item (from previous node)\n * This avoids node-name lookups that cause hangs in recent n8n versions\n */\n\nconst newItems = [];\n\nfor (const item of $input.all()) {\n  // Get country from incoming data - adjust field name if needed\n  // Common possibilities: item.json.country, item.json.normalizedCountry, item.json.message.content.country, etc.\n  let rawCountry = '';\n\n  // Try most common paths - pick the one that matches your upstream output\n  if (item.json.country) {\n    rawCountry = item.json.country;\n  } else if (item.json.normalizedCountry) {\n    rawCountry = item.json.normalizedCountry;\n  } else if (item.json.message?.content?.country) {\n    rawCountry = item.json.message.content.country;\n  } else if (item.json.output?.country) {\n    rawCountry = item.json.output.country;\n  }\n\n  const country = (rawCountry || '').trim().toLowerCase();\n\n  let locationScore = 0;\n\n  // Your original tier lists (unchanged)\n  const locUS = ['united states', 'usa', 'us'];\n  const locTier2 = ['united kingdom', 'uk', 'canada', 'australia', 'new zealand'];\n  const locWesternEurope = ['germany', 'france', 'netherlands', 'belgium', 'switzerland', 'austria', 'ireland', 'luxembourg'];\n  const locNordic = ['sweden', 'norway', 'denmark', 'finland', 'iceland'];\n  const locEastAsia = ['japan', 'south korea', 'korea', 'taiwan'];\n  const locSingHK = ['singapore', 'hong kong'];\n  const locMiddleEast = ['uae', 'israel', 'saudi arabia', 'qatar', 'kuwait', 'bahrain', 'oman'];\n  const locOtherEU = ['spain', 'italy', 'portugal', 'poland', 'czech republic', 'greece', 'slovakia', 'hungary', 'lithuania', 'estonia', 'latvia', 'malta'];\n  const locLatAm = ['brazil', 'mexico', 'argentina', 'chile', 'colombia', 'peru', 'uruguay', 'costa rica', 'panama'];\n  const locSouthAsia = ['india', 'bangladesh', 'pakistan', 'sri lanka', 'nepal'];\n  const locSEAsia = ['vietnam', 'indonesia', 'thailand', 'philippines', 'malaysia', 'myanmar', 'cambodia', 'laos', 'brunei'];\n  const locEasternEurope = ['russia', 'ukraine', 'romania', 'bulgaria', 'serbia', 'croatia', 'slovenia', 'moldova', 'georgia', 'armenia', 'belarus', 'albania', 'montenegro', 'bosnia & herzegovina', 'north macedonia'];\n  const locAfrica = ['south africa', 'nigeria', 'kenya', 'egypt', 'morocco', 'ghana', 'ethiopia', 'tanzania', 'tunisia', 'algeria', 'rwanda', 'botswana', 'uganda', 'senegal', 'zambia', 'namibia'];\n\n  // Scoring\n  if (locUS.includes(country)) {\n    locationScore = 10;\n  } else if (locTier2.includes(country)) {\n    locationScore = 8;\n  } else if (locWesternEurope.includes(country) ||\n             locNordic.includes(country) ||\n             locEastAsia.includes(country) ||\n             locSingHK.includes(country)) {\n    locationScore = 7;\n  } else if (locMiddleEast.includes(country)) {\n    locationScore = 6;\n  } else if (locOtherEU.includes(country)) {\n    locationScore = 4;\n  } else if (locLatAm.includes(country)) {\n    locationScore = 3;\n  } else if (locSouthAsia.includes(country) ||\n             locSEAsia.includes(country) ||\n             locEasternEurope.includes(country)) {\n    locationScore = 2;\n  } else if (locAfrica.includes(country)) {\n    locationScore = 1;\n  } else {\n    locationScore = 0;\n  }\n\n  newItems.push({\n    json: {\n      ...item.json,              // keep all previous fields\n      normalizedCountry: rawCountry,  // original value for reference\n      locationScore\n    }\n  });\n}\n\nreturn newItems;"
      },
      "notesInFlow": true,
      "typeVersion": 2
    },
    {
      "id": "695c763d-faf3-4a21-b4b6-5899d747d3a0",
      "name": "Score Staff Count",
      "type": "n8n-nodes-base.code",
      "notes": "Headcount Scoring (Templatized)\n\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nPurpose: Scores companies by employee count (headcountScore 0\u201310) based on customizable ranges.\n\nCustomization (edit in code):\n\u2022 sourceNodeName / sourceFieldPath \u2192 change if upstream node/field renamed\n\u2022 defaultScore \u2192 fallback when count missing/invalid\n\u2022 scoreRanges array \u2192 add/remove/edit buckets & scores\n  Example: { min: 51, max: 200, score: 10 } = sweet spot\n\nCurrent ranges:\n- 1\u201310     \u2192 3\n- 11\u201350    \u2192 7\n- 51\u2013200   \u2192 10 (ideal target)\n- 201\u2013500  \u2192 9\n- 501\u20135000 \u2192 8\n- 5001+    \u2192 7\n- Missing  \u2192 0\n\nOutput fields:\n- staffCount: cleaned number\n- headcountScore: 0\u201310\n\nTips:\n- Test changes with small data first\n- Use Infinity for \"and above\" ranges\n- Numbers only \u2014 non-numeric input \u2192 0",
      "position": [
        -3264,
        992
      ],
      "parameters": {
        "jsCode": "/**\n * Templatized Code node: Score company headcount (staff size)\n * \n * Users can customize:\n * - The ranges and their corresponding scores\n * - The field name/path where employee_count comes from\n * - Default score for unknown/missing values\n * \n * How to customize:\n * 1. Edit the `scoreRanges` array below \u2014 add/remove/change ranges and scores\n * 2. Adjust `sourceFieldPath` if your employee_count is in a different spot\n * 3. Change `defaultScore` if you want a different fallback\n */\n\nconst newItems = [];\n\n// \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n// USER CONFIGURATION SECTION \u2500\u2500 EDIT HERE\n// \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nconst sourceNodeName = \"Sanitize Description\";          // Change if upstream node renamed\nconst sourceFieldPath = \"employee_count\";               // e.g. \"output.employee_count\", \"data.staff\", etc.\n\nconst defaultScore = 0;                                 // Score when count is missing / invalid / 0\n\n// Define your scoring buckets here (lowest \u2192 highest)\n// Format: { min: number, max: number, score: number }\n// Use Infinity for \"and above\", null/undefined for open-ended\nconst scoreRanges = [\n  { min: 1,    max: 10,   score: 3 },      // Very small (1\u201310)\n  { min: 11,   max: 50,   score: 7 },      // Small-mid (11\u201350)\n  { min: 51,   max: 200,  score: 10 },     // Sweet spot (51\u2013200)\n  { min: 201,  max: 500,  score: 9 },      // Mid-size (201\u2013500)\n  { min: 501,  max: 5000, score: 8 },      // Large (501\u20135000)\n  { min: 5001, max: Infinity, score: 7 }   // Enterprise / very large (>5000)\n];\n\n// \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n// END OF USER CONFIG \u2500\u2500 NO NEED TO EDIT BELOW\n// \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nfor (const item of $input.all()) {\n  // Safely retrieve the employee count (modern n8n style)\n  let staffCount = 0;\n\n  // Try to get it from the configured source\n  const sourceData = item.json;\n  if (sourceFieldPath.includes('.')) {\n    // Deep path (e.g. \"output.employee_count\")\n    const parts = sourceFieldPath.split('.');\n    let current = sourceData;\n    for (const part of parts) {\n      current = current?.[part];\n      if (current === undefined) break;\n    }\n    staffCount = Number(current) || 0;\n  } else {\n    // Simple top-level field\n    staffCount = Number(sourceData?.[sourceFieldPath]) || 0;\n  }\n\n  // Find matching range and assign score\n  let headcountScore = defaultScore;\n\n  for (const range of scoreRanges) {\n    if (staffCount >= range.min && (range.max === Infinity || staffCount <= range.max)) {\n      headcountScore = range.score;\n      break;\n    }\n  }\n\n  newItems.push({\n    json: {\n      ...item.json,\n      staffCount,           // original value (cleaned to number)\n      headcountScore\n    }\n  });\n}\n\nreturn newItems;"
      },
      "notesInFlow": true,
      "typeVersion": 2
    },
    {
      "id": "0a58f859-e986-4566-84d9-a0d20dfc3d1c",
      "name": "Sanitize Description",
      "type": "n8n-nodes-base.code",
      "notes": "Sanitize Description\n\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nPurpose:\nCleans up the raw LinkedIn company description from the \"LinkedIn Agent\" node so it's Slack-friendly, readable, and short.\n\nWhat it does:\n\u2022 Safely grabs the description (handles missing data gracefully)\n\u2022 Replaces fancy/smart quotes with normal ones\n\u2022 Converts dashes/ellipses/newlines into clean single spaces\n\u2022 Collapses extra whitespace\n\u2022 Trims leading/trailing spaces\n\u2022 Caps length at ~280 chars (optional, good for messaging)\n\nWhy we need this:\nRaw LinkedIn descriptions often contain weird Unicode characters, multiple newlines, and promotional fluff that looks terrible in Slack notifications or short-form outputs.\n\nTips for debugging:\n- If the node hangs: double-check the upstream node is exactly named \"LinkedIn Agent\" (no extra spaces).\n- Test with a minimal version first: return items with hardcoded \"TEST OK\" to confirm execution.\n- Output includes original data + new field: sanitized_description\n\nExpected output field: sanitized_description (string)",
      "position": [
        -4032,
        992
      ],
      "parameters": {
        "jsCode": "// Clean LinkedIn company description for Slack / short use\n// Safe access + aggressive sanitization\n\nconst newItems = [];\n\nfor (const item of $input.all()) {\n  let desc = \"\";\n\n  // Safe path: get description from upstream \"LinkedIn Agent\" node\n  const agentOutput = $(\"LinkedIn Agent\").first()?.json?.output;\n  if (agentOutput?.description) {\n    desc = agentOutput.description;\n  } else {\n    // Fallback: try current item's json if description is already there\n    desc = item.json.description || \"\";\n  }\n\n  // Sanitize for clean text output\n  desc = desc\n    .replace(/[\\u2018\\u2019\\u201C\\u201D]/g, \"'\")     // fancy quotes \u2192 normal\n    .replace(/[\\u2013\\u2014]/g, \"-\")                 // dashes\n    .replace(/\\u2026/g, \"...\")                       // ellipsis\n    .replace(/\\r\\n|\\r|\\n/g, \" \")                     // newlines \u2192 space\n    .replace(/\\s+/g, \" \")                            // collapse whitespace\n    .trim();\n\n  // Optional: cap length for Slack-ish contexts\n  if (desc.length > 280) {\n    desc = desc.slice(0, 277) + \"...\";\n  }\n\n  // Pass through original data + add cleaned field\n  newItems.push({\n    json: {\n      ...item.json,\n      sanitized_description: desc\n    }\n  });\n}\n\nreturn newItems;"
      },
      "notesInFlow": true,
      "typeVersion": 2
    },
    {
      "id": "e8652025-9325-4ad9-815e-6612288f3af0",
      "name": "OpenAI Chat Model1",
      "type": "@n8n/n8n-nodes-langchain.lmChatOpenAi",
      "position": [
        -4736,
        1232
      ],
      "parameters": {
        "model": {
          "__rl": true,
          "mode": "list",
          "value": "gpt-5.2",
          "cachedResultName": "gpt-5.2"
        },
        "options": {}
      },
      "credentials": {
        "openAiApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1.2
    },
    {
      "id": "9b1bbf8e-ecca-4bf7-baa4-27c95f0e1f3b",
      "name": "Execution Data",
      "type": "n8n-nodes-base.executionData",
      "position": [
        -6656,
        1280
      ],
      "parameters": {
        "dataToSave": {
          "values": [
            {
              "key": "email",
              "value": "={{ $item(\"0\").$node[\"Webhook\"].json[\"body\"][\"data\"][\"item\"][\"email\"] }}"
            }
          ]
        }
      },
      "typeVersion": 1
    },
    {
      "id": "804917d6-6098-4723-a3f3-ff37f891716e",
      "name": "Industry Scoring",
      "type": "n8n-nodes-base.code",
      "notes": "Industry Normalization & Scoring (Templatized)\n\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nPurpose: Converts raw industry strings \u2192 clean category + numeric score (higher = better fit).\n\nCustomization (edit in code):\n\u2022 sourceFieldPath \u2192 change if industry is nested (e.g. \"data.industry\")\n\u2022 fallbackCategory \u2192 default when no match\n\u2022 industryCategories array:\n  - Add/remove categories\n  - Change score (0\u201310 recommended)\n  - Edit keywords (partial, case-insensitive matches)\n  - Order matters: higher-priority categories first\n\nCurrent top priorities:\n- Technology/Media \u2192 10\n- Financial/Fintech \u2192 9\n- Most others \u2192 6 or lower\n\nOutput fields added:\n- normalizedIndustry: clean category name\n- originalIndustry: raw input for reference\n- industryScore: numeric score\n\nTips:\n- Test with varied industries first\n- Use broad keywords to catch variations\n- If too many \"Other\", add more specific keywords\n- First match wins \u2014 reorder array for priority\n\nKeeps workflow flexible for different lead criteria!",
      "position": [
        -3024,
        992
      ],
      "parameters": {
        "jsCode": "/**\n * Templatized Code node: Normalize & Score Company Industry\n * \n * Maps raw industry strings to broader categories and assigns scores.\n * \n * Customization guide (edit in USER CONFIG section below):\n * 1. Add/remove/edit entries in `industryCategories`\n * 2. Adjust keyword matches (case-insensitive partial matches)\n * 3. Change fallback category & score\n * 4. Modify source field/path if needed\n */\n\nconst newItems = [];\n\n// \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n// USER CONFIGURATION SECTION \u2500\u2500 EDIT HERE\n// \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nconst sourceFieldPath = \"output.industry\";              // Path in incoming json, e.g. \"industry\", \"output.industry\", \"data.company.industry\"\n\nconst fallbackCategory = {\n  category: \"Other Industries\",\n  score: 3,\n  note: \"No strong match found\"\n};\n\nconst industryCategories = [\n  {\n    category: \"Technology, Information and Media\",\n    score: 10,\n    keywords: [\n      \"technology\", \"software\", \"it \", \"information technology\", \"saas\", \"cloud\",\n      \"cybersecurity\", \"artificial intelligence\", \"data\", \"internet\", \"computer\",\n      \"digital\", \"tech\", \"ai\", \"ml\", \"platform\", \"it services\", \"it consulting\"\n    ]\n  },\n  {\n    category: \"Financial Services\",\n    score: 9,\n    keywords: [\n      \"financial\", \"fintech\", \"insurance\", \"investment\", \"wealth management\",\n      \"capital markets\", \"venture capital\", \"private equity\"\n    ]\n  },\n  {\n    category: \"Healthcare\",\n    score: 6,\n    keywords: [\n      \"healthcare\", \"health\", \"medical\", \"biotech\", \"pharmaceutical\", \"life sciences\",\n      \"hospital\", \"clinical\", \"wellness\"\n    ]\n  },\n  {\n    category: \"Retail & E-commerce\",\n    score: 6,\n    keywords: [\n      \"retail\", \"e-commerce\", \"ecommerce\", \"shop\", \"store\", \"marketplace\",\n      \"commerce\", \"online shopping\"\n    ]\n  },\n  {\n    category: \"Manufacturing\",\n    score: 6,\n    keywords: [\n      \"manufacturing\", \"industrial\", \"production\", \"factory\", \"fabrication\",\n      \"assembly\", \"engineering\"\n    ]\n  },\n  {\n    category: \"Professional Services / Consulting\",\n    score: 5,\n    keywords: [\n      \"professional services\", \"consulting\", \"management consulting\", \"strategy consulting\",\n      \"business consulting\", \"advisory\", \"accounting\"\n    ]\n  },\n  {\n    category: \"Advertising & Marketing\",\n    score: 6,\n    keywords: [\n      \"advertising\", \"marketing\", \"ad tech\", \"ad agency\", \"digital marketing\",\n      \"marketing agency\", \"branding\"\n    ]\n  },\n  {\n    category: \"Education\",\n    score: 4,\n    keywords: [\n      \"education\", \"learning\", \"training\", \"school\", \"university\", \"academic\",\n      \"edtech\", \"teaching\"\n    ]\n  },\n  {\n    category: \"Travel & Hospitality\",\n    score: 4,\n    keywords: [\n      \"travel\", \"hospitality\", \"hotel\", \"tourism\", \"airline\", \"vacation\",\n      \"accommodation\"\n    ]\n  },\n  {\n    category: \"Consumer Goods\",\n    score: 3,\n    keywords: [\n      \"consumer goods\", \"fmcg\", \"consumer products\", \"packaged goods\",\n      \"consumer brands\"\n    ]\n  },\n  {\n    category: \"Real Estate\",\n    score: 3,\n    keywords: [\n      \"real estate\", \"property\", \"realty\", \"housing\", \"commercial property\"\n    ]\n  },\n  {\n    category: \"Entertainment & Media\",\n    score: 3,\n    keywords: [\n      \"entertainment\", \"gaming\", \"games\", \"music\", \"movies\", \"media production\",\n      \"film\", \"video\", \"broadcasting\"\n    ]\n  }\n  // Add more categories here as needed, highest priority first\n];\n\n// \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n// END OF USER CONFIG \u2500\u2500 NO NEED TO EDIT BELOW\n// \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nfor (const item of $input.all()) {\n  // Safely extract raw industry\n  let rawIndustry = '';\n\n  if (sourceFieldPath.includes('.')) {\n    const parts = sourceFieldPath.split('.');\n    let current = item.json;\n    for (const part of parts) {\n      current = current?.[part];\n      if (current === undefined) break;\n    }\n    rawIndustry = (current || '').toString();\n  } else {\n    rawIndustry = (item.json?.[sourceFieldPath] || '').toString();\n  }\n\n  const normalized = rawIndustry.trim().toLowerCase();\n\n  // Find best match (first match wins - order in array matters!)\n  let matched = { ...fallbackCategory, originalIndustry: rawIndustry };\n\n  for (const cat of industryCategories) {\n    for (const kw of cat.keywords) {\n      if (normalized.includes(kw)) {\n        matched = {\n          category: cat.category,\n          score: cat.score,\n          originalIndustry: rawIndustry\n        };\n        break; // Stop at first match\n      }\n    }\n    if (matched.category !== fallbackCategory.category) break;\n  }\n\n  newItems.push({\n    json: {\n      ...item.json,\n      normalizedIndustry: matched.category,\n      originalIndustry: matched.originalIndustry,\n      industryScore: matched.score\n    }\n  });\n}\n\nreturn newItems;"
      },
      "notesInFlow": true,
      "typeVersion": 2
    },
    {
      "id": "ae55a0b9-6acb-42df-a1b5-e9773fda2e8d",
      "name": "Algo Score",
      "type": "n8n-nodes-base.code",
      "notes": "Final Lead Scoring (Templatized)\n\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nPurpose: Combines 3 sub-scores (each 0\u201310) \u2192 final 0\u2013100 score + rating label.\n\nCustomization (edit config object in code):\n\u2022 sources \u2192 update node/field names if upstream changes\n\u2022 weights \u2192 adjust relative importance (e.g. {headcount: 1.5, location: 1, industry: 2})\n\u2022 ratingTiers \u2192 change thresholds/labels (highest first)\n\u2022 defaultSubScore \u2192 fallback when data missing\n\nCurrent setup:\n- Equal weights (1:1:1)\n- Max per sub-score = 10\n- Ratings: \u226580 Excellent, \u226560 Good, \u226540 Moderate, else Poor\n\nOutput structure:\noutput.scores: {\n  headcount, location, industry,\n  totalRaw, finalScore (0\u2013100), rating\n}\n\nTips:\n- Test with known good/bad leads\n- To emphasize one factor \u2192 increase its weight\n- Add new sub-scores by extending config.sources & weights",
      "position": [
        -2800,
        992
      ],
      "parameters": {
        "jsCode": "/**\n * Final Lead Scoring (Templatized)\n * Combines headcount, location, and industry scores into a 0\u2013100 final score + qualitative rating.\n * \n * Customization guide (edit in USER CONFIG section):\n * - Change weights for each component (they auto-normalize to 100)\n * - Adjust rating thresholds and labels\n * - Modify node names or field paths if upstream nodes change\n * - Add more sub-scores later if needed\n */\n\nconst newItems = [];\n\n// \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n// USER CONFIGURATION SECTION \u2500\u2500 EDIT HERE\n// \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nconst config = {\n  // Source node names and field paths (update if renamed)\n  sources: {\n    headcount: { node: \"Score Staff Count\",   field: \"headcountScore\" },\n    location:  { node: \"Score Country\",       field: \"locationScore\"  },\n    industry:  { node: \"Industry Scoring\",    field: \"industryScore\"  }\n  },\n\n  // Weights (higher = more important) \u2014 will be normalized to total 100\n  weights: {\n    headcount: 1,   // e.g. give headcount more/less emphasis\n    location:  1,\n    industry:  1\n  },\n\n  // Rating tiers (thresholds from high \u2192 low)\n  ratingTiers: [\n    { min: 80, label: \"Excellent Fit\" },\n    { min: 60, label: \"Good Fit\"      },\n    { min: 40, label: \"Moderate Fit\"  },\n    { min:  0, label: \"Poor Fit\"      }\n  ],\n\n  // Fallback if a score is missing/invalid\n  defaultSubScore: 0\n};\n\n// \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n// END OF USER CONFIG \u2500\u2500 NO NEED TO EDIT BELOW\n// \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nfor (const item of $input.all()) {\n  // Helper to safely get a score from upstream\n  const getScore = (source) => {\n    try {\n      return Number(item.json?.[source.field]) || config.defaultSubScore;\n    } catch (e) {\n      return config.defaultSubScore;\n    }\n  };\n\n  // Extract individual scores (using incoming data, not node lookups)\n  const headcountScore = getScore(config.sources.headcount);\n  const locationScore  = getScore(config.sources.location);\n  const industryScore  = getScore(config.sources.industry);\n\n  // Weighted total (raw points)\n  const totalRaw = \n    headcountScore * config.weights.headcount +\n    locationScore  * config.weights.location  +\n    industryScore  * config.weights.industry;\n\n  // Normalize to max possible raw = sum of max scores \u00d7 weights\n  const maxPossible = \n    10 * config.weights.headcount +\n    10 * config.weights.location  +\n    10 * config.weights.industry;\n\n  const finalScore = maxPossible > 0 ? Math.round((totalRaw / maxPossible) * 100) : 0;\n\n  // Find matching rating\n  let rating = config.ratingTiers[config.ratingTiers.length - 1].label; // fallback\n  for (const tier of config.ratingTiers) {\n    if (finalScore >= tier.min) {\n      rating = tier.label;\n      break;\n    }\n  }\n\n  newItems.push({\n    json: {\n      output: {\n        scores: {\n          headcount: headcountScore,\n          location:  locationScore,\n          industry:  industryScore,\n          totalRaw,\n          finalScore,\n          rating\n        }\n      }\n    }\n  });\n}\n\nreturn newItems;"
      },
      "notesInFlow": true,
      "typeVersion": 2
    },
    {
      "id": "143888a2-a992-4e54-bb87-a5152512bdfd",
      "name": "Extract Name From Email",
      "type": "@n8n/n8n-nodes-langchain.openAi",
      "position": [
        -1648,
        976
      ],
      "parameters": {
        "modelId": {
          "__rl": true,
          "mode": "list",
          "value": "gpt-4.1-nano",
          "cachedResultName": "GPT-4.1-NANO"
        },
        "options": {},
        "messages": {
          "values": [
            {
              "role": "system",
              "content": "You are an AI assistant that extracts names from email addresses. You receive an email address and should look at the part before the \u201c@\u201d to determine if it is a person\u2019s first name or first and last name.\n\t1.\tIf you are extremely confident it is a real first name (e.g., \u201cjohn\u201d), output the first name only (e.g., \u201cJohn\u201d).\n\t2.\tIf you are extremely confident it is a real first and last name (e.g., \u201cjohn.smith\u201d), output both (e.g., \u201cJohn Smith\u201d).\n\t3.\tIf there is any doubt\u2014meaning it looks like a role, department, organization, or anything that is not clearly a person\u2019s name (e.g., \u201csupport,\u201d \u201cadmin,\u201d \u201cinfo,\u201d etc.)\u2014output the word \"there\" as the Name and First Name without the quotes.\n\nCapitalize names properly. Output only the name or there, with no additional text or punctuation.\n\nOutput the following:\n- Name\n- First Name\n- Last Name"
            },
            {
              "content": "=Email: {{ $item(\"0\").$node[\"Webhook\"].json[\"email\"] }}"
            }
          ]
        },
        "jsonOutput": true
      },
      "credentials": {
        "openAiApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1.8
    },
    {
      "id": "36a13dec-7a3e-49af-9e37-fc51ab70f921",
      "name": "Wait",
      "type": "n8n-nodes-base.wait",
      "position": [
        -1280,
        976
      ],
      "parameters": {
        "unit": "minutes"
      },
      "typeVersion": 1.1
    },
    {
      "id": "b33a8eb7-390f-4951-97bf-58667ea434df",
      "name": "Extract LinkedIn Url",
      "type": "n8n-nodes-base.code",
      "notes": "This node will extract the LI profile URL",
      "position": [
        -5344,
        1008
      ],
      "parameters": {
        "jsCode": "const data = $input.item.json;\n\n// Step 1: Recursively search for the first LinkedIn URL\nfunction findLinkedInUrl(obj, path = '') {\n  if (!obj || typeof obj !== 'object') return null;\n\n  if (Array.isArray(obj)) {\n    for (const item of obj) {\n      if (typeof item === 'string' && item.includes('linkedin.com')) {\n        return item;\n      }\n    }\n    for (let i = 0; i < obj.length; i++) {\n      const result = findLinkedInUrl(obj[i], `${path}[${i}]`);\n      if (result) return result;\n    }\n  } else {\n    for (const [key, value] of Object.entries(obj)) {\n      if (typeof value === 'string' && value.includes('linkedin.com')) {\n        return value;\n      }\n      const result = findLinkedInUrl(value, path ? `${path}.${key}` : key);\n      if (result) return result;\n    }\n  }\n\n  return null;\n}\n\nconst rawLinkedInUrl = findLinkedInUrl(data);\n\n// Step 2: Normalize the LinkedIn URL (vanity or numeric company page)\nlet cleanLinkedInUrl = null;\nif (typeof rawLinkedInUrl === 'string') {\n  // Match pattern like: https://www.linkedin.com/company/zeva-global-inc or /company/98872926\n  const match = rawLinkedInUrl.match(/https?:\\/\\/(www\\.)?linkedin\\.com\\/company\\/[a-zA-Z0-9-]+/);\n  if (match) {\n    cleanLinkedInUrl = match[0].replace(/\\/+$/, '') + '/'; // Ensure single trailing slash\n  }\n}\n\n// Step 3: Fallback in case no valid LinkedIn URL is found\nconst safeLinkedInUrl = cleanLinkedInUrl || null;\nconst hasLinkedIn = safeLinkedInUrl !== null;\n\nreturn {\n  json: {\n    domain: data.metadata?.sourceURL?.replace(/^https?:\\/\\//, '').split('/')[0] || '',\n    originalUrl: data.metadata?.sourceURL || '',\n    linkedInUrl: safeLinkedInUrl,\n    hasLinkedIn,\n    debugInfo: {\n      rawLinkedInUrl,\n      cleanLinkedInUrl,\n      dataKeys: Object.keys(data),\n      searchComplete: true\n    }\n  }\n};"
      },
      "notesInFlow": true,
      "typeVersion": 2
    },
    {
      "id": "99f68bfa-6dc8-4a55-a930-f7fa24fe78aa",
      "name": "Check if Company or Personal LI Profile",
      "type": "n8n-nodes-base.if",
      "notes": "This node only passes through company LI profiles (and not personal LI profiles). If personal LI profiles still fits your ICP, route that via the 'False' branch.",
      "position": [
        -4896,
        1008
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 2,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "c60eb149-fdda-4d58-b9ef-1dbc21472c63",
              "operator": {
                "type": "string",
                "operation": "contains"
              },
              "leftValue": "={{ $json.debugInfo.cleanLinkedInUrl }}",
              "rightValue": "linkedin.com/company"
            }
          ]
        }
      },
      "notesInFlow": true,
      "typeVersion": 2.2
    },
    {
      "id": "f46b91bc-f5b5-43e6-8055-089bc7932dcc",
      "name": "Check root URL",
      "type": "n8n-nodes-base.if",
      "notes": "This node checks the status code to ensure the domain is healthy and responsive (so it does not waste any credits/scraping efforts).",
      "position": [
        -5808,
        1008
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 2,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "47b571c7-b3de-4b32-a11d-acc68379f8a6",
              "operator": {
                "type": "number",
                "operation": "gte"
              },
              "leftValue": "={{ $json.statusCode }}",
              "rightValue": 200
            },
            {
              "id": "7b8e4747-cbc4-467a-a84a-7557b70dcc64",
              "operator": {
                "type": "number",
                "operation": "lte"
              },
              "leftValue": "={{ $json.statusCode }}",
              "rightValue": 399
            }
          ]
        }
      },
      "notesInFlow": true,
      "typeVersion": 2.2
    },
    {
      "id": "6e209053-4c83-4106-b1c0-fae7e3734d0f",
      "name": "Extract Name From Email2",
      "type": "@n8n/n8n-nodes-langchain.openAi",
      "position": [
        -1648,
        1200
      ],
      "parameters": {
        "modelId": {
          "__rl": true,
          "mode": "list",
          "value": "gpt-4.1-nano",
          "cachedResultName": "GPT-4.1-NANO"
        },
        "options": {},
        "messages": {
          "values": [
            {
              "role": "system",
              "content": "You are an AI assistant that extracts names from email addresses. You receive an email address and should look at the part before the \u201c@\u201d to determine if it is a person\u2019s first name or first and last name.\n\t1.\tIf you are extremely confident it is a real first name (e.g., \u201cjohn\u201d), output the first name only (e.g., \u201cJohn\u201d).\n\t2.\tIf you are extremely confident it is a real first and last name (e.g., \u201cjohn.smith\u201d), output both (e.g., \u201cJohn Smith\u201d).\n\t3.\tIf there is any doubt\u2014meaning it looks like a role, department, organization, or anything that is not clearly a person\u2019s name (e.g., \u201csupport,\u201d \u201cadmin,\u201d \u201cinfo,\u201d etc.)\u2014output the word there as the Name and First Name\n\nCapitalize names properly. Output only the name or there, with no additional text or punctuation.\n\nOutput the following:\n- Name\n- First Name\n- Last Name"
            },
            {
              "content": "=Email: {{ $item(\"0\").$node[\"Webhook\"].json[\"email\"] }}"
            }
          ]
        },
        "jsonOutput": true
      },
      "credentials": {
        "openAiApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1.8
    },
    {
      "id": "7f9f9d1c-1c04-4d46-afb4-403b5c7b8938",
      "name": "Wait2",
      "type": "n8n-nodes-base.wait",
      "position": [
        -1280,
        1200
      ],
      "parameters": {
        "unit": "minutes"
      },
      "typeVersion": 1.1
    },
    {
      "id": "fdcd7556-4857-4b4f-a4d4-715b2f79d41c",
      "name": "High Value Trials (70-89)",
      "type": "n8n-nodes-base.if",
      "position": [
        -2336,
        1200
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 2,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "c27a01a4-5ae8-4870-9024-5d427cb38b69",
              "operator": {
                "type": "number",
                "operation": "gte"
              },
              "leftValue": "={{ $json.output.scores.finalScore }}",
              "rightValue": 70
            },
            {
              "id": "c56c2b84-b4c9-4710-aa3e-c3da6fe4cd39",
              "operator": {
                "type": "number",
                "operation": "lte"
              },
              "leftValue": "={{ $json.output.scores.finalScore }}",
              "rightValue": 89
            }
          ]
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "5a0bde80-7bb5-4a13-a27c-2807500a1614",
      "name": "Very High Value Trials (90-100)",
      "type": "n8n-nodes-base.if",
      "position": [
        -2336,
        992
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 2,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "c27a01a4-5ae8-4870-9024-5d427cb38b69",
              "operator": {
                "type": "number",
                "operation": "gte"
              },
              "leftValue": "={{ $json.output.scores.finalScore }}",
              "rightValue": 90
            }
          ]
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "ba99157b-07de-4114-b34c-c7f1a16186c5",
      "name": "Extract Name From Email3",
      "type": "@n8n/n8n-nodes-langchain.openAi",
      "position": [
        -1648,
        1408
      ],
      "parameters": {
        "modelId": {
          "__rl": true,
          "mode": "list",
          "value": "gpt-4.1-nano",
          "cachedResultName": "GPT-4.1-NANO"
        },
        "options": {},
        "messages": {
          "values": [
            {
              "role": "system",
              "content": "You are an AI assistant that extracts names from email addresses. You receive an email address and should look at the part before the \u201c@\u201d to determine if it is a person\u2019s first name or first and last name.\n\t1.\tIf you are extremely confident it is a real first name (e.g., \u201cjohn\u201d), output the first name only (e.g., \u201cJohn\u201d).\n\t2.\tIf you are extremely confident it is a real first and last name (e.g., \u201cjohn.smith\u201d), output both (e.g., \u201cJohn Smith\u201d).\n\t3.\tIf there is any doubt\u2014meaning it looks like a role, department, organization, or anything that is not clearly a person\u2019s name (e.g., \u201csupport,\u201d \u201cadmin,\u201d \u201cinfo,\u201d etc.)\u2014output the word there as the Name and First Name\n\nCapitalize names properly. Output only the name or there, with no additional text or punctuation.\n\nOutput the following:\n- Name\n- First Name\n- Last Name"
            },
            {
              "content": "=Email: {{ $item(\"0\").$node[\"Webhook\"].json[\"email\"] }}"
            }
          ]
        },
        "jsonOutput": true
      },
      "credentials": {
        "openAiApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1.8
    },
    {
      "id": "5139ced2-e900-4503-b469-2f7a90cd82cf",
      "name": "Wait3",
      "type": "n8n-nodes-base.wait",
      "position": [
        -1280,
        1408
      ],
      "parameters": {
        "unit": "minutes"
      },
      "typeVersion": 1.1
    },
    {
      "id": "30002cb6-a5d2-42be-a59c-0f38a870b398",
      "name": "Mid Value Trials (50-69)",
      "type": "n8n-nodes-base.if",
      "position": [
        -2336,
        1408
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 2,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "c27a01a4-5ae8-4870-9024-5d427cb38b69",
              "operator": {
                "type": "number",
                "operation": "gte"
              },
              "leftValue": "={{ $json.output.scores.finalScore }}",
              "rightValue": 50
            },
            {
              "id": "c56c2b84-b4c9-4710-aa3e-c3da6fe4cd39",
              "operator": {
                "type": "number",
                "operation": "lte"
              },
              "leftValue": "={{ $json.output.scores.finalScore }}",
              "rightValue": 69
            }
          ]
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "3a0d54c5-9fa9-46b5-a4e0-36dd36669660",
      "name": "Extract Name From Email4",
      "type": "@n8n/n8n-nodes-langchain.openAi",
      "position": [
        -1648,
        1648
      ],
      "parameters": {
        "modelId": {
          "__rl": true,
          "mode": "list",
          "value": "gpt-4.1-nano",
          "cachedResultName": "GPT-4.1-NANO"
        },
        "options": {},
        "messages": {
          "values": [
            {
              "role": "system",
              "content": "You are an AI assistant that extracts names from email addresses. You receive an email address and should look at the part before the \u201c@\u201d to determine if it is a person\u2019s first name or first and last name.\n\t1.\tIf you are extremely confident it is a real first name (e.g., \u201cjohn\u201d), output the first name only (e.g., \u201cJohn\u201d).\n\t2.\tIf you are extremely confident it is a real first and last name (e.g., \u201cjohn.smith\u201d), output both (e.g., \u201cJohn Smith\u201d).\n\t3.\tIf there is any doubt\u2014meaning it looks like a role, department, organization, or anything that is not clearly a person\u2019s name (e.g., \u201csupport,\u201d \u201cadmin,\u201d \u201cinfo,\u201d etc.)\u2014output the word there as the Name and First Name\n\nCapitalize names properly. Output only the name or there, with no additional text or punctuation.\n\nOutput the following:\n- Name\n- First Name\n- Last Name"
            },
            {
              "content": "=Email: {{ $item(\"0\").$node[\"Webhook\"].json[\"email\"] }}"
            }
          ]
        },
        "jsonOutput": true
      },
      "credentials": {
        "openAiApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1.8
    },
    {
      "id": "9c75d444-52e6-4362-a6c3-62f8c1025408",
      "name": "Wait4",
      "type": "n8n-nodes-base.wait",
      "disabled": true,
      "position": [
        -1280,
        1664
      ],
      "parameters": {
        "unit": "minutes",
        "amount": 20
      },
      "typeVersion": 1.1
    },
    {
      "id": "44b49fca-6879-48d8-b045-98532f2b6aad",
      "name": "Low Value Trials (0-49)",
      "type": "n8n-nodes-base.if",
      "position": [
        -2336,
        1648
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 2,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "c27a01a4-5ae8-4870-9024-5d427cb38b69",
              "operator": {
                "type": "number",
                "operation": "gte"
              },
              "leftValue": "={{ $json.output.scores.finalScore }}",
              "rightValue": 0
            },
            {
              "id": "c56c2b84-b4c9-4710-aa3e-c3da6fe4cd39",
              "operator": {
                "type": "number",
                "operation": "lte"
              },
              "leftValue": "={{ $json.output.scores.finalScore }}",
              "rightValue": 49
            }
          ]
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "7d39e660-97e4-4856-8a25-c83b0cecd437",
      "name": "Blacklist Regex Domains",
      "type": "n8n-nodes-base.if",
      "notes": "This removes trash emails, personal emails, employee emails and only passes through good B2B emails.",
      "position": [
        -6432,
        1008
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 2,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "445af0a5-1a1b-4afe-9730-12af901491ec",
              "operator": {
                "type": "string",
                "operation": "notRegex"
              },
              "leftValue": "={{ $json.root_domain }}",
              "rightValue": "=^(gmail\\.com|yahoo\\.com|ymail\\.com|rocketmail\\.com|hotmail\\.com|aol\\.com|outlook\\.com|icloud\\.com|protonmail\\.com|pm\\.me|proton\\.me|zoho\\.com|yandex\\.com|mail\\.com|gmx\\.com|me\\.com|mac\\.com|sbcglobal\\.net|verizon\\.net|bellsouth\\.net|comcast\\.net|cox\\.net|earthlink\\.net|charter\\.net|att\\.net|frontier\\.com|optonline\\.net|shaw\\.ca|sympatico\\.ca|rogers\\.com|fastmail\\.com|qq\\.com|guerrillamail\\.com|10minutemail\\.com|maildrop\\.cc|anonaddy\\.com|live\\.com|msn\\.com|tutanota\\.com|hushmail\\.com|aol\\.co\\.uk|btinternet\\.com|sky\\.com|temp-mail\\.org|yopmail\\.com|throwawaymail\\.com|sharklasers\\.com|getnada\\.com|dispostable\\.com|trashmail\\.com|mohmal\\.com|inbox\\.com|rediffmail\\.com|naver\\.com|163\\.com|126\\.com|sina\\.com|wp\\.pl|seznam\\.cz|libero\\.it|mail\\.ru|centurylink\\.net|windstream\\.net|suddenlink\\.net|spectrum\\.net|telus\\.net|videotron\\.ca|bigpond\\.com|virginmedia\\.com|talktalk\\.co\\.uk|telenet\\.be|tempmail\\.com|burnermail\\.io|emailondeck\\.com|fakeinbox\\.com|mailinator\\.com|byom\\.de|inbox\\.lv|protonmail\\.ch|grr\\.la|spambox\\.us|.*\\.edu(\\.co)?)$"
            },
            {
              "id": "40046651-a329-464d-ace0-17dde2f4ade3",
              "operator": {
                "name": "filter.operator.equals",
                "type": "string",
                "operation": "equals"
              },
              "leftValue": "",
              "rightValue": ""
            }
          ]
        }
      },
      "notesInFlow": true,
      "typeVersion": 2.2
    },
    {
      "id": "7f567204-a733-4afd-9987-f023c250ff3b",
      "name": "Check LinkedIn is not null",
      "type": "n8n-nodes-base.if",
      "notes": "Checks to make sure the LinkedIn profile is not empty/null.",
      "position": [
        -5120,
        1008
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 2,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "19aa2caa-6e6c-4b99-a41d-1512446dba5f",
              "operator": {
                "type": "string",
                "operation": "exists",
                "singleValue": true
              },
              "leftValue": "={{ $json.debugInfo.cleanLinkedInUrl }}",
              "rightValue": ""
            },
            {
              "id": "fb63985f-9a93-44d9-817d-20575375bd90",
              "operator": {
                "type": "string",
                "operation": "notEquals"
              },
              "leftValue": "={{ $json.debugInfo.cleanLinkedInUrl }}",
              "rightValue": "null"
            },
            {
              "id": "5846c30a-6a75-4e8a-96be-fbbbe634a8cd",
              "operator": {
                "type": "string",
                "operation": "notEmpty",
                "singleValue": true
              },
              "leftValue": "={{ $json.debugInfo.cleanLinkedInUrl }}",
              "rightValue": ""
            }
          ]
        }
      },
      "notesInFlow": true,
      "typeVersion": 2.2
    },
    {
      "id": "f301371e-132f-4df0-be6b-c4496ccd8723",
      "name": "Audit LI Results",
      "type": "n8n-nodes-base.if",
      "notes": "This node audits the LI output from the agent to ensure there is a quality LI company profile (to continue the workflow). \n\nThis implies that any company that has a complete LI profile is likely worth it whereas if they do not have a complete profile they are likely not a good fit. ",
      "position": [
        -4272,
        992
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 2,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "6886a47a-cc73-465e-aae4-e7a808b1ae9f",
              "operator": {
                "type": "string",
                "operation": "exists",
                "singleValue": true
              },
              "leftValue": "={{ $json.output.company_name }}",
              "rightValue": ""
            },
            {
              "id": "94a016a0-a9a2-4991-8fb5-7986be143042",
              "operator": {
                "type": "number",
                "operation": "exists",
                "singleValue": true
              },
              "leftValue": "={{ $json.output.followers }}",
              "rightValue": ""
            },
            {
              "id": "bce36df9-ea31-49e2-908b-5975cdef410f",
              "operator": {
                "type": "number",
                "operation": "exists",
                "singleValue": true
              },
              "leftValue": "={{ $json.output.employee_count }}",
              "rightValue": ""
            },
            {
              "id": "65251c39-1576-4302-ae17-e6c07d8f8bfb",
              "operator": {
                "type": "string",
                "operation": "exists",
                "singleValue": true
              },
              "leftValue": "={{ $json.output.headquarters_location }}",
              "rightValue": ""
            }
          ]
        }
      },
      "notesInFlow": true,
      "typeVersion": 2.2
    },
    {
      "id": "64331374-3051-4f88-b124-a9341d3193e3",
      "name": "Firecrawl Scrape",
      "type": "@mendable/n8n-nodes-firecrawl.firecrawl",
      "notes": "This uses Firecrawl to scrape the website and extract LinkedIn URL. Typically websites will have their social profiles on their website (namely the footer) and this will extract the LinkedIn. Can also be used to extract other channels (such as YouTube, \ud835\udd4f, etc.). ",
      "position": [
        -5568,
        992
      ],
      "parameters": {
        "url": "={{ $('Extract Email Root Domain').item.json.root_domain }}",
        "operation": "scrape",
        "scrapeOptions": {
          "options": {
            "formats": {
              "format": [
                {
                  "type": "links"
                },
                {
                  "type": "json",
                  "prompt": "Find the LinkedIn company profile URL for this organization. Look everywhere on the page, including the footer, header, about us section, and contact us page. Return only the complete LinkedIn URL."
                }
              ]
            },
            "headers": {},
            "onlyMainContent": false
          }
        },
        "requestOptions": {}
      },
      "credentials": {
        "firecrawlApi": {
          "name": "<your credential>"
        }
      },
      "notesInFlow": true,
      "typeVersion": 1
    },
    {
      "id": "6a2758d6-9ce5-4fa1-b09d-ab9834145086",
      "name": "website_tool",
      "type": "n8n-nodes-base.httpRequestTool",
      "notes": "This enriches company LI data.",
      "position": [
        -4560,
        1216
      ],
      "parameters": {
        "url": "https://api.scrapin.io/v1/enrichment/company",
        "options": {},
        "sendQuery": true,
        "authentication": "genericCredentialType",
        "genericAuthType": "httpQueryAuth",
        "queryParameters": {
          "parameters": [
            {
              "name": "linkedInUrl",
              "value": "={{ /*n8n-auto-generated-fromAI-override*/ $fromAI('parameters0_Value', ``, 'string') }}"
            },
            {
              "name": "cacheDuration",
              "value": "4h"
            }
          ]
        },
        "toolDescription": "Use this tool to get LinkedIn company information"
      },
      "credentials": {
        "httpQueryAuth": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 4.4
    },
    {
      "id": "25313025-7254-451f-bb73-61056cd973ff",
      "name": "Consider adding to your CRM",
      "type": "n8n-nodes-base.noOp",
      "position": [
        -2336,
        320
      ],
      "parameters": {},
      "typeVersion": 1
    },
    {
      "id": "d9504d0e-a964-4873-bd45-b4db2ed65e0b",
      "name": "Sticky Note2",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -6704,
        864
      ],
      "parameters": {
        "width": 624,
        "height": 320,
        "content": "## Email handling\n### This extracts the root domain and omits personal/trash emails"
      },
      "typeVersion": 1
    },
    {
      "id": "6c0a5144-3084-45f4-8278-c4352375555d",
      "name": "Sticky Note3",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -6064,
        864
      ],
      "parameters": {
        "color": 4,
        "width": 640,
        "height": 320,
        "content": "## Check if website exists and scrape\n### This does a HEAD request to check if successful. If successful then scrapes"
      },
      "typeVersion": 1
    },
    {
      "id": "457131a6-5955-4d71-b252-05f48fe7e71f",
      "name": "Sticky Note4",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -5408,
        864
      ],
      "parameters": {
        "color": 6,
        "width": 1280,
        "height": 560,
        "content": "## Extract company LinkedIn URL & enrich company info\nThis extracts the LI URL from the footer of the site, omits personal LI profiles and enriches with followers, headcount, HQ location, description.\nUses scrapin.io as an enrichment tool for the AI Agent."
      },
      "typeVersion": 1
    },
    {
      "id": "1766dc8b-c6a5-43ab-9288-494154d8bcea",
      "name": "Sticky Note5",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -4112,
        864
      ],
      "parameters": {
        "color": 2,
        "width": 1440,
        "height": 336,
        "content": "## Sanitize data for Slack, run scoring algo checks for qual and quant scores (based on ICP).\nThis ensures that data can be sent to Slack for notification and scores the country, headcount, industry, for a total qual/quant score. "
      },
      "typeVersion": 1
    },
    {
      "id": "e9f6f511-00e7-4036-9427-d27206dff609",
      "name": "Send to slack",
      "type": "n8n-nodes-base.slack",
      "notes": "Sends to new-trials channel",
      "position": [
        -2352,
        576
      ],
      "parameters": {
        "text": "=New Free Trial Lead!",
        "select": "channel",
        "blocksUi": "={\n  \"blocks\": [\n    {\n      \"type\": \"section\",\n      \"text\": {\n        \"type\": \"mrkdwn\",\n        \"text\": \":zap: *New Scored Lead!*\"\n      }\n    },\n    {\n      \"type\": \"context\",\n      \"elements\": [\n        {\n          \"type\": \"mrkdwn\",\n          \"text\": \"This report provides insights into the company's online presence.\"\n        }\n      ]\n    },\n    {\n      \"type\": \"divider\"\n    },\n    {\n      \"type\": \"section\",\n      \"text\": {\n        \"type\": \"mrkdwn\",\n        \"text\": \"*Email:* {{ $item(\"0\").$node[\"Webhook\"].json[\"email\"] }}\\n*Company Name:* {{ $item(\"0\").$node[\"Sanitize Description\"].json[\"output\"][\"company_name\"] }}\\n*Company Location:* {{ $item(\"0\").$node[\"Sanitize Description\"].json[\"output\"][\"headquarters_location\"] }} ({{ $json.output.scores.location }}/10 points)\\n*Staff Count:* {{ $item(\"0\").$node[\"Sanitize Description\"].json[\"output\"][\"employee_count\"] }} ({{ $json.output.scores.headcount }}/10 points)\\n*Follower Count:* {{ $item(\"0\").$node[\"Sanitize Description\"].json[\"output\"][\"followers\"] }}\\n*Industry:* {{ $('Industry Scoring').item.json.normalizedIndustry }} ({{ $json.output.scores.industry }}/10)\"\n      }\n    },\n    {\n      \"type\": \"divider\"\n    },\n    {\n      \"type\": \"section\",\n      \"text\": {\n        \"type\": \"mrkdwn\",\n        \"text\": \"*Description:* {{ $item(\"0\").$node[\"Sanitize Description\"].json[\"sanitized_description\"] }}\"\n      }\n    },\n    {\n      \"type\": \"section\",\n      \"text\": {\n        \"type\": \"mrkdwn\",\n        \"text\": \"*Lead Score:* {{ $json.output.scores.finalScore }}/100\\n*Rating:* {{ $json.output.scores.rating }}\"\n      }\n    },\n    {\n      \"type\": \"actions\",\n      \"elements\": [\n        {\n          \"type\": \"button\",\n          \"text\": {\n            \"type\": \"plain_text\",\n            \"text\": \"Visit LinkedIn\",\n            \"emoji\": true\n          },\n          \"url\": \"{{ $item(\"0\").$node[\"Extract LinkedIn Url\"].json[\"linkedInUrl\"] }}\"\n        },\n        {\n          \"type\": \"button\",\n          \"text\": {\n            \"type\": \"plain_text\",\n            \"text\": \"Visit Website \ud83c\udf10\",\n            \"emoji\": true\n          },\n          \"url\": \"https://{{ $item(0).$node['Extract Email Root Domain'].json['root_domain'] }}\"\n        }\n      ]\n    }\n  ]\n}",
        "channelId": {
          "__rl": true,
          "mode": "list",
          "value": "C08HPT21153",
          "cachedResultName": "brandon-automation-alerts"
        },
        "messageType": "block",
        "otherOptions": {
          "includeLinkToWorkflow": false
        }
      },
      "credentials": {
        "slackApi": {
          "name": "<your credential>"
        }
      },
      "notesInFlow": true,
      "typeVersion": 2.3
    },
    {
      "id": "b842f55d-1a78-4e24-b476-457e9baceead",
      "name": "Search Instantly Database",
      "type": "CUSTOM.instantly",
      "position": [
        -2080,
        1648
      ],
      "parameters": {
        "filters": {
          "search": "={{ $item(\"0\").$node[\"Webhook\"].json[\"email\"] }}"
        },
        "resource": "lead",
        "operation": "getMany"
      },
      "credentials": {
        "instantlyApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "9be4f1ff-e104-430a-8ae5-1fdbe16e7e1c",
      "name": "Search Instantly Database1",
      "type": "CUSTOM.instantly",
      "position": [
        -2080,
        1408
      ],
      "parameters": {
        "filters": {
          "search": "={{ $item(\"0\").$node[\"Webhook\"].json[\"email\"] }}"
        },
        "resource": "lead",
        "operation": "getMany"
      },
      "credentials": {
        "instantlyApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "4ba1060a-2068-418b-8408-39c43c040075",
      "name": "Search Instantly Database2",
      "type": "CUSTOM.instantly",
      "position": [
        -2080,
        1200
      ],
      "parameters": {
        "filters": {
          "search": "={{ $item(\"0\").$node[\"Webhook\"].json[\"email\"] }}"
        },
        "resource": "lead",
        "operation": "getMany"
      },
      "credentials": {
        "instantlyApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "344273d0-7b5a-4c1c-bb11-40b8fc3ede49",
      "name": "Search Instantly Database3",
      "type": "CUSTOM.instantly",
      "position": [
        -2080,
        976
      ],
      "parameters": {
        "filters": {
          "search": "={{ $item(\"0\").$node[\"Webhook\"].json[\"email\"] }}"
        },
        "resource": "lead",
        "operation": "getMany"
      },
      "credentials": {
        "instantlyApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "81032a55-cd19-4003-b5b5-35b793ca56f7",
      "name": "Check if in db",
      "type": "n8n-nodes-base.if",
      "position": [
        -1872,
        976
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 2,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "c5e43ac5-660f-4e26-8d54-9a4e18f397e4",
              "operator": {
                "type": "array",
                "operation": "empty",
                "singleValue": true
              },
              "leftValue": "={{ $json.items }}",
              "rightValue": ""
            }
          ]
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "920913de-572c-405a-b805-03ffa76edcaf",
      "name": "Check if in db1",
      "type": "n8n-nodes-base.if",
      "position": [
        -1872,
        1200
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 2,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "c5e43ac5-660f-4e26-8d54-9a4e18f397e4",
              "operator": {
                "type": "array",
                "operation": "empty",
                "singleValue": true
              },
              "leftValue": "={{ $json.items }}",
              "rightValue": ""
            }
          ]
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "d325ee41-7766-4313-8010-329cfeedd145",
      "name": "Check if in db2",
      "type": "n8n-nodes-base.if",
      "position": [
        -1872,
        1408
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 2,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "c5e43ac5-660f-4e26-8d54-9a4e18f397e4",
              "operator": {
                "type": "array",
                "operation": "empty",
                "singleValue": true
              },
              "leftValue": "={{ $json.items }}",
              "rightValue": ""
            }
          ]
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "66dbdb48-47a4-488a-ac4d-4f8324200aae",
      "name": "Check if in db3",
      "type": "n8n-nodes-base.if",
      "position": [
        -1872,
        1648
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 2,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "c5e43ac5-660f-4e26-8d54-9a4e18f397e4",
              "operator": {
                "type": "array",
                "operation": "empty",
                "singleValue": true
              },
              "leftValue": "={{ $json.items }}",
              "rightValue": ""
            }
          ]
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "9e30ef1d-56bc-4b4c-a654-e7d58e33b745",
      "name": "Add lead to campaign",
      "type": "CUSTOM.instantly",
      "position": [
        -1056,
        1664
      ],
      "parameters": {
        "email": "={{ $item(\"0\").$node[\"Webhook\"].json[\"email\"] }}",
        "campaign": {
          "__rl": true,
          "mode": "list",
          "value": "29951765-b0e9-4f18-8c8e-cb1c71d656d4",
          "cachedResultName": "TEST"
        },
        "lastName": "={{ $('Extract Name From Email4').item.json.message.content['Last Name'] }}",
        "resource": "lead",
        "firstName": "={{ $('Extract Name From Email4').item.json.message.content['First Name'] }}",
        "operation": "addToCampaign",
        "customFields": {
          "customFieldValues": {
            "field": [
              {
                "key": "website",
                "value": "=https://{{ $item(\"0\").$node[\"Check to make sure email is not null\"].json[\"root_domain\"] }}"
              }
            ]
          }
        }
      },
      "credentials": {
        "instantlyApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "ecf898ff-20b3-4f3e-ba6e-aa3285d2f989",
      "name": "Add lead to campaign1",
      "type": "CUSTOM.instantly",
      "position": [
        -1056,
        976
      ],
      "parameters": {
        "email": "={{ $item(\"0\").$node[\"Webhook\"].json[\"email\"] }}",
        "campaign": {
          "__rl": true,
          "mode": "list",
          "value": "29951765-b0e9-4f18-8c8e-cb1c71d656d4",
          "cachedResultName": "TEST"
        },
        "lastName": "={{ $('Extract Name From Email').item.json.message.content['Last Name'] }}",
        "resource": "lead",
        "firstName": "={{ $('Extract Name From Email').item.json.message.content['First Name'] }}",
        "operation": "addToCampaign",
        "customFields": {
          "customFieldValues": {
            "field": [
              {
                "key": "website",
                "value": "=https://{{ $item(\"0\").$node[\"Check to make sure email is not null\"].json[\"root_domain\"] }}"
              }
            ]
          }
        }
      },
      "credentials": {
        "instantlyApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "d9c3dd3e-56fc-4603-8ef8-71e4a56f8b53",
      "name": "Add lead to campaign2",
      "type": "CUSTOM.instantly",
      "position": [
        -1056,
        1408
      ],
      "parameters": {
        "email": "={{ $item(\"0\").$node[\"Webhook\"].json[\"email\"] }}",
        "campaign": {
          "__rl": true,
          "mode": "list",
          "value": "29951765-b0e9-4f18-8c8e-cb1c71d656d4",
          "cachedResultName": "TEST"
        },
        "lastName": "={{ $('Extract Name From Email3').item.json.message.content['Last Name'] }}",
        "resource": "lead",
        "firstName": "={{ $('Extract Name From Email3').item.json.message.content['First Name'] }}",
        "operation": "addToCampaign",
        "customFields": {
          "customFieldValues": {
            "field": [
              {
                "key": "website",
                "value": "=https://{{ $item(\"0\").$node[\"Check to make sure email is not null\"].json[\"root_domain\"] }}"
              }
            ]
          }
        }
      },
      "credentials": {
        "instantlyApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "b361feef-367e-4c41-9194-f37eaccb8258",
      "name": "Add lead to campaign3",
      "type": "CUSTOM.instantly",
      "position": [
        -1056,
        1200
      ],
      "parameters": {
        "email": "={{ $item(\"0\").$node[\"Webhook\"].json[\"email\"] }}",
        "campaign": {
          "__rl": true,
          "mode": "list",
          "value": "29951765-b0e9-4f18-8c8e-cb1c71d656d4",
          "cachedResultName": "TEST"
        },
        "lastName": "={{ $('Extract Name From Email2').item.json.message.content['Last Name'] }}",
        "resource": "lead",
        "firstName": "={{ $('Extract Name From Email2').item.json.message.content['First Name'] }}",
        "operation": "addToCampaign",
        "customFields": {
          "customFieldValues": {
            "field": [
              {
                "key": "website",
                "value": "=https://{{ $item(\"0\").$node[\"Check to make sure email is not null\"].json[\"root_domain\"] }}"
              }
            ]
          }
        }
      },
      "credentials": {
        "instantlyApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "2007817e-f527-46b1-8c1a-6b77898cfe88",
      "name": "Sticky Note6",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -2416,
        864
      ],
      "parameters": {
        "color": 5,
        "width": 1600,
        "height": 1024,
        "content": "## Segment scores, verify lead is not already in Instantly, then add to campaign\n### Breaks out into scoring ranges (Very High, High, Mid, Low. Then adds them to campaigns"
      },
      "typeVersion": 1
    },
    {
      "id": "d5fe5be4-f3c8-4164-b998-2d0d05dca4bf",
      "name": "Sticky Note",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -7984,
        528
      ],
      "parameters": {
        "color": 7,
        "width": 704,
        "height": 1968,
        "content": "# n8n Lead Qualification & Sales Intelligence Workflow (Top of Funnel)\n\n### Overview / Purpose\nThis workflow automatically processes new free-trial / lead sign-ups in real time:\n\n- Catches a webhook from any source (Webflow form, Intercom, custom agent, etc.)\n- Filters out personal / disposable / .edu emails \u2192 only business emails continue\n- Validates the company website is live\n- Scrapes the website with Firecrawl to find the official LinkedIn company page\n- Enriches the company via AI Agent (Scrapin.io) \u2192 pulls name, follower count, headcount, HQ location, industry, and clean description\n- Runs a multi-factor scoring engine (Location + Headcount + Industry)\n- Sends a beautiful rich Slack notification to the sales team with score, rating, and quick-action buttons\n- Segments the lead by score tier (Very High / High / Mid / Low)\n- Checks if the lead already exists in Instantly\n- Adds qualified leads to the correct Instantly campaign with first/last name extracted from email\n\n### Core Flow (Step-by-Step)\n\n1. **Webhook** \u2013 Trigger (POST /new-lead)\n2. **Extract Email Root Domain** \u2013 Pulls `@company.com` part\n3. **Blacklist Regex Domains** \u2013 Blocks all personal, free, and .edu emails\n4. **Check Email Not Null** \u2013 Safety gate\n5. **Check if Website Exists** \u2013 HEAD request (continues only on 200-399)\n6. **Firecrawl Scrape** \u2013 Scrapes homepage looking for LinkedIn URL in footer/links\n7. **Extract LinkedIn Url** \u2013 Code node that finds and cleans `/company/` URL\n8. **Check LinkedIn Not Null + Is Company Page** \u2013 Rejects personal profiles\n9. **LinkedIn Agent** (AI Agent)\n   - Uses Scrapin.io enrichment tool\n   - Structured output: company_name, followers, employee_count, headquarters_location, industry, description\n10. **Audit LI Results** \u2013 Ensures all key fields returned\n11. **Sanitize Description** \u2013 Cleans Unicode, newlines, caps length for Slack\n12. **Normalize Country** \u2013 GPT-4.1-nano turns \"Sheridan, US\" \u2192 \"United States\"\n13. **Score Country** \u2013 Code node (0\u201310 points based on market tier)\n14. **Score Staff Count** \u2013 Code node (0\u201310 points based on headcount buckets)\n15. **Industry Scoring** \u2013 Code node (normalizes raw industry + 0\u201310 points)\n16. **Algo Score** \u2013 Final score = weighted average \u2192 0\u2013100 + rating label (\"Excellent Fit\", \"Good Fit\", etc.)\n17. **Send to Slack** \u2013 Rich blocks with:\n    - Email, Company Name, Location (+score), Staff Count (+score), Followers, Industry (+score)\n    - Clean description\n    - Final Score + Rating\n    - Buttons: Visit LinkedIn + Visit Website\n18. **Score-Based Routing**\n    - Very High (90\u2013100)\n    - High (70\u201389)\n    - Mid (50\u201369)\n    - Low (0\u201349)\n19. **Instantly Checks** \u2013 For each tier: search if lead already exists\n20. **Extract Name From Email** (GPT) \u2013 Turns `john.smith@company.com` \u2192 First/Last Name (or \"there\")\n21. **Wait** nodes (optional delay before adding)\n22. **Add Lead to Campaign** \u2013 Pushes to Instantly with website custom field\n\n### Scoring Logic (Fully Configurable in Code Nodes)\n\nBased on your ICP, narrow down your scoring based on how you want to segment.\n\n### What Makes This Workflow Powerful\n- Zero manual work after webhook\n- Rich, actionable Slack alerts (sales team loves it)\n- Smart scoring that matches real ICP\n- Built-in deduplication with Instantly\n- Fully modular code nodes (easy to tweak scoring without breaking anything)\n- Heavy error handling and safety gates\n\n---\n\n**This is a complete, production-ready, public template.**  \nYou can import the JSON and it will run immediately after you connect your own:\n- Webhook source\n- Firecrawl API\n- Scrapin.io (or alternative)\n- OpenAI\n- Slack\n- Instantly\n\nAny questions/comments/concerns, please email brandon@topoffunnel.com\nBuilt by Brandon Charleson\nhttps://www.youtube.com/@brandoncharleson\nhttps://www.linkedin.com/in/brandon-charleson \nhttps://x.com/brandon_ai"
      },
      "typeVersion": 1
    }
  ],
  "active": false,
  "settings": {
    "executionOrder": "v1"
  },
  "versionId": "2e10ae45-6406-4ed8-85ce-6df5fb64de25",
  "connections": {
    "Wait": {
      "main": [
        [
          {
            "node": "Add lead to campaign1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Wait2": {
      "main": [
        [
          {
            "node": "Add lead to campaign3",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Wait3": {
      "main": [
        [
          {
            "node": "Add lead to campaign2",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Wait4": {
      "main": [
        [
          {
            "node": "Add lead to campaign",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Webhook": {
      "main": [
        [
          {
            "node": "Extract Email Root Domain",
            "type": "main",
            "index": 0
          },
          {
            "node": "Execution Data",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Algo Score": {
      "main": [
        [
          {
            "node": "Send to slack",
            "type": "main",
            "index": 0
          },
          {
            "node": "Very High Value Trials (90-100)",
            "type": "main",
            "index": 0
          },
          {
            "node": "High Value Trials (70-89)",
            "type": "main",
            "index": 0
          },
          {
            "node": "Mid Value Trials (50-69)",
            "type": "main",
            "index": 0
          },
          {
            "node": "Low Value Trials (0-49)",
            "type": "main",
            "index": 0
          },
          {
            "node": "Consider adding to your CRM",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "website_tool": {
      "ai_tool": [
        [
          {
            "node": "LinkedIn Agent",
            "type": "ai_tool",
            "index": 0
          }
        ]
      ]
    },
    "Score Country": {
      "main": [
        [
          {
            "node": "Score Staff Count",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Check if in db": {
      "main": [
        [
          {
            "node": "Extract Name From Email",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Check root URL": {
      "main": [
        [
          {
            "node": "Firecrawl Scrape",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "LinkedIn Agent": {
      "main": [
        [
          {
            "node": "Audit LI Results",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Check if in db1": {
      "main": [
        [
          {
            "node": "Extract Name From Email2",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Check if in db2": {
      "main": [
        [
          {
            "node": "Extract Name From Email3",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Check if in db3": {
      "main": [
        [
          {
            "node": "Extract Name From Email4",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Audit LI Results": {
      "main": [
        [
          {
            "node": "Sanitize Description",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Firecrawl Scrape": {
      "main": [
        [
          {
            "node": "Extract LinkedIn Url",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Industry Scoring": {
      "main": [
        [
          {
            "node": "Algo Score",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Normalize Country": {
      "main": [
        [
          {
            "node": "Score Country",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Score Staff Count": {
      "main": [
        [
          {
            "node": "Industry Scoring",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "OpenAI Chat Model1": {
      "ai_languageModel": [
        [
          {
            "node": "LinkedIn Agent",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    },
    "Add lead to campaign": {
      "main": [
        []
      ]
    },
    "Extract LinkedIn Url": {
      "main": [
        [
          {
            "node": "Check LinkedIn is not null",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Sanitize Description": {
      "main": [
        [
          {
            "node": "Normalize Country",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Blacklist Regex Domains": {
      "main": [
        [
          {
            "node": "Check to make sure email is not null",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Check if website exists": {
      "main": [
        [
          {
            "node": "Check root URL",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Extract Name From Email": {
      "main": [
        [
          {
            "node": "Wait",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Low Value Trials (0-49)": {
      "main": [
        [
          {
            "node": "Search Instantly Database",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Extract Name From Email2": {
      "main": [
        [
          {
            "node": "Wait2",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Extract Name From Email3": {
      "main": [
        [
          {
            "node": "Wait3",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Extract Name From Email4": {
      "main": [
        [
          {
            "node": "Wait4",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Mid Value Trials (50-69)": {
      "main": [
        [
          {
            "node": "Search Instantly Database1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Extract Email Root Domain": {
      "main": [
        [
          {
            "node": "Blacklist Regex Domains",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "High Value Trials (70-89)": {
      "main": [
        [
          {
            "node": "Search Instantly Database2",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Search Instantly Database": {
      "main": [
        [
          {
            "node": "Check if in db3",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Structured Output Parser1": {
      "ai_outputParser": [
        [
          {
            "node": "LinkedIn Agent",
            "type": "ai_outputParser",
            "index": 0
          }
        ]
      ]
    },
    "Check LinkedIn is not null": {
      "main": [
        [
          {
            "node": "Check if Company or Personal LI Profile",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Search Instantly Database1": {
      "main": [
        [
          {
            "node": "Check if in db2",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Search Instantly Database2": {
      "main": [
        [
          {
            "node": "Check if in db1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Search Instantly Database3": {
      "main": [
        [
          {
            "node": "Check if in db",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Very High Value Trials (90-100)": {
      "main": [
        [
          {
            "node": "Search Instantly Database3",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Check to make sure email is not null": {
      "main": [
        [
          {
            "node": "Check if website exists",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Check if Company or Personal LI Profile": {
      "main": [
        [
          {
            "node": "LinkedIn Agent",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}