{
  "name": "Lead Qualification Pipeline (Community Edition)",
  "nodes": [
    {
      "parameters": {
        "httpMethod": "POST",
        "path": "qualify-lead",
        "responseMode": "responseNode",
        "options": {}
      },
      "id": "webhook-001",
      "name": "Webhook",
      "type": "n8n-nodes-base.webhook",
      "typeVersion": 2,
      "position": [
        0,
        300
      ],
      "webhookId": "qualify-lead"
    },
    {
      "parameters": {
        "jsCode": "const body = $input.first().json.body || $input.first().json;\n\nconst name = (body.name || body.lead_name || '').trim();\nconst phone = (body.phone || body.lead_phone || '').trim();\nconst email = (body.email || body.lead_email || '').trim().toLowerCase();\nconst company = (body.company || body.lead_company || '').trim();\nconst city = (body.city || body.lead_city || '').trim();\n\nif (!name || !email || !company) {\n  throw new Error('Missing required fields. Send JSON with: name, phone, email, company, city');\n}\n\nconst emailDomain = email.includes('@') ? email.split('@')[1] : '';\nconst freemailDomains = ['gmail.com','yahoo.com','hotmail.com','outlook.com','aol.com','icloud.com','mail.com','protonmail.com','zoho.com','yandex.com','live.com','msn.com','comcast.net','att.net','verizon.net','me.com','mac.com','inbox.com','gmx.com','fastmail.com'];\nconst isFreemail = freemailDomains.includes(emailDomain);\nconst companyWebsite = isFreemail ? '' : 'https://' + emailDomain;\nconst phoneDigits = phone.replace(/[^0-9+]/g, '');\n\nfunction md5(string) {\n  function md5cycle(x, k) {\n    var a = x[0], b = x[1], c = x[2], d = x[3];\n    a = ff(a, b, c, d, k[0], 7, -680876936); d = ff(d, a, b, c, k[1], 12, -389564586);\n    c = ff(c, d, a, b, k[2], 17, 606105819); b = ff(b, c, d, a, k[3], 22, -1044525330);\n    a = ff(a, b, c, d, k[4], 7, -176418897); d = ff(d, a, b, c, k[5], 12, 1200080426);\n    c = ff(c, d, a, b, k[6], 17, -1473231341); b = ff(b, c, d, a, k[7], 22, -45705983);\n    a = ff(a, b, c, d, k[8], 7, 1770035416); d = ff(d, a, b, c, k[9], 12, -1958414417);\n    c = ff(c, d, a, b, k[10], 17, -42063); b = ff(b, c, d, a, k[11], 22, -1990404162);\n    a = ff(a, b, c, d, k[12], 7, 1804603682); d = ff(d, a, b, c, k[13], 12, -40341101);\n    c = ff(c, d, a, b, k[14], 17, -1502002290); b = ff(b, c, d, a, k[15], 22, 1236535329);\n    a = gg(a, b, c, d, k[1], 5, -165796510); d = gg(d, a, b, c, k[6], 9, -1069501632);\n    c = gg(c, d, a, b, k[11], 14, 643717713); b = gg(b, c, d, a, k[0], 20, -373897302);\n    a = gg(a, b, c, d, k[5], 5, -701558691); d = gg(d, a, b, c, k[10], 9, 38016083);\n    c = gg(c, d, a, b, k[15], 14, -660478335); b = gg(b, c, d, a, k[4], 20, -405537848);\n    a = gg(a, b, c, d, k[9], 5, 568446438); d = gg(d, a, b, c, k[14], 9, -1019803690);\n    c = gg(c, d, a, b, k[3], 14, -187363961); b = gg(b, c, d, a, k[8], 20, 1163531501);\n    a = gg(a, b, c, d, k[13], 5, -1444681467); d = gg(d, a, b, c, k[2], 9, -51403784);\n    c = gg(c, d, a, b, k[7], 14, 1735328473); b = gg(b, c, d, a, k[12], 20, -1926607734);\n    a = hh(a, b, c, d, k[5], 4, -378558); d = hh(d, a, b, c, k[8], 11, -2022574463);\n    c = hh(c, d, a, b, k[11], 16, 1839030562); b = hh(b, c, d, a, k[14], 23, -35309556);\n    a = hh(a, b, c, d, k[1], 4, -1530992060); d = hh(d, a, b, c, k[4], 11, 1272893353);\n    c = hh(c, d, a, b, k[7], 16, -155497632); b = hh(b, c, d, a, k[10], 23, -1094730640);\n    a = hh(a, b, c, d, k[13], 4, 681279174); d = hh(d, a, b, c, k[0], 11, -358537222);\n    c = hh(c, d, a, b, k[3], 16, -722521979); b = hh(b, c, d, a, k[6], 23, 76029189);\n    a = hh(a, b, c, d, k[9], 4, -640364487); d = hh(d, a, b, c, k[12], 11, -421815835);\n    c = hh(c, d, a, b, k[15], 16, 530742520); b = hh(b, c, d, a, k[2], 23, -995338651);\n    a = ii(a, b, c, d, k[0], 6, -198630844); d = ii(d, a, b, c, k[7], 10, 1126891415);\n    c = ii(c, d, a, b, k[14], 15, -1416354905); b = ii(b, c, d, a, k[5], 21, -57434055);\n    a = ii(a, b, c, d, k[12], 6, 1700485571); d = ii(d, a, b, c, k[3], 10, -1894986606);\n    c = ii(c, d, a, b, k[10], 15, -1051523); b = ii(b, c, d, a, k[1], 21, -2054922799);\n    a = ii(a, b, c, d, k[8], 6, 1873313359); d = ii(d, a, b, c, k[15], 10, -30611744);\n    c = ii(c, d, a, b, k[6], 15, -1560198380); b = ii(b, c, d, a, k[13], 21, 1309151649);\n    a = ii(a, b, c, d, k[4], 6, -145523070); d = ii(d, a, b, c, k[11], 10, -1120210379);\n    c = ii(c, d, a, b, k[2], 15, 718787259); b = ii(b, c, d, a, k[9], 21, -343485551);\n    x[0] = add32(a, x[0]); x[1] = add32(b, x[1]); x[2] = add32(c, x[2]); x[3] = add32(d, x[3]);\n  }\n  function cmn(q, a, b, x, s, t) { a = add32(add32(a, q), add32(x, t)); return add32((a << s) | (a >>> (32 - s)), b); }\n  function ff(a, b, c, d, x, s, t) { return cmn((b & c) | ((~b) & d), a, b, x, s, t); }\n  function gg(a, b, c, d, x, s, t) { return cmn((b & d) | (c & (~d)), a, b, x, s, t); }\n  function hh(a, b, c, d, x, s, t) { return cmn(b ^ c ^ d, a, b, x, s, t); }\n  function ii(a, b, c, d, x, s, t) { return cmn(c ^ (b | (~d)), a, b, x, s, t); }\n  function md51(s) {\n    var n = s.length, state = [1732584193, -271733879, -1732584194, 271733878], i;\n    for (i = 64; i <= n; i += 64) md5cycle(state, md5blk(s.substring(i - 64, i)));\n    s = s.substring(i - 64);\n    var tail = [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0];\n    for (i = 0; i < s.length; i++) tail[i >> 2] |= s.charCodeAt(i) << ((i % 4) << 3);\n    tail[i >> 2] |= 0x80 << ((i % 4) << 3);\n    if (i > 55) { md5cycle(state, tail); tail = [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]; }\n    tail[14] = n * 8; md5cycle(state, tail); return state;\n  }\n  function md5blk(s) { var md5blks = [], i; for (i = 0; i < 64; i += 4) md5blks[i >> 2] = s.charCodeAt(i) + (s.charCodeAt(i+1) << 8) + (s.charCodeAt(i+2) << 16) + (s.charCodeAt(i+3) << 24); return md5blks; }\n  var hex_chr = '0123456789abcdef'.split('');\n  function rhex(n) { var s = '', j = 0; for (; j < 4; j++) s += hex_chr[(n >> (j * 8 + 4)) & 0x0F] + hex_chr[(n >> (j * 8)) & 0x0F]; return s; }\n  function hex(x) { for (var i = 0; i < x.length; i++) x[i] = rhex(x[i]); return x.join(''); }\n  function add32(a, b) { return (a + b) & 0xFFFFFFFF; }\n  return hex(md51(string));\n}\n\nconst emailMd5 = md5(email);\nconst nameParts = name.split(/\\s+/);\n\nreturn [{ json: {\n  lead_name: name, lead_firstName: nameParts[0] || '', lead_lastName: nameParts.slice(1).join(' ') || '',\n  lead_phone: phone, lead_phoneDigits: phoneDigits, lead_email: email,\n  lead_emailDomain: emailDomain, lead_isFreemail: isFreemail,\n  lead_company: company, lead_city: city, lead_companyWebsite: companyWebsite,\n  lead_emailMd5: emailMd5, results: {}\n} }];"
      },
      "id": "code-preprocess-001",
      "name": "1. Pre-Process",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        220,
        300
      ]
    },
    {
      "parameters": {
        "url": "={{ 'http://apilayer.net/api/validate?access_key=YOUR_NUMVERIFY_API_KEY&number=' + $json.lead_phoneDigits + '&country_code=US' }}",
        "options": {
          "timeout": 10000
        }
      },
      "id": "http-phone-001",
      "name": "2. Phone Validation",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        440,
        300
      ],
      "onError": "continueRegularOutput"
    },
    {
      "parameters": {
        "jsCode": "const prev = $('1. Pre-Process').first().json;\nconst p = $input.first().json;\nprev.results.phone = { valid: p.valid || false, localFormat: p.local_format || null, intlFormat: p.international_format || null, country: p.country_name || null, location: p.location || null, carrier: p.carrier || null, lineType: p.line_type || null };\nreturn [{ json: prev }];"
      },
      "id": "code-store-phone",
      "name": "Store Phone",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        660,
        300
      ]
    },
    {
      "parameters": {
        "jsCode": "const prev = $input.first().json;\nconst d = prev.lead_emailDomain;\nconst typoMap = {'gmial.com':'gmail.com','gmai.com':'gmail.com','gamil.com':'gmail.com','yahooo.com':'yahoo.com','yaho.com':'yahoo.com','hotmial.com':'hotmail.com','outlok.com':'outlook.com'};\nprev.results.email = { email: prev.lead_email, domain: d, isFreemail: prev.lead_isFreemail, syntaxValid: /^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/.test(prev.lead_email), hasCommonTypo: !!typoMap[d], suggestedCorrection: typoMap[d] ? prev.lead_email.replace(d, typoMap[d]) : null };\nreturn [{ json: prev }];"
      },
      "id": "code-email-001",
      "name": "3. Email Check",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        880,
        300
      ]
    },
    {
      "parameters": {
        "url": "={{ $json.lead_companyWebsite ? $json.lead_companyWebsite : 'https://www.google.com/search?q=' + encodeURIComponent($json.lead_company + ' ' + $json.lead_city) }}",
        "options": {
          "timeout": 10000,
          "response": {
            "response": {
              "fullResponse": true
            }
          }
        }
      },
      "id": "http-website-001",
      "name": "4. Fetch Website",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        1100,
        300
      ],
      "onError": "continueRegularOutput"
    },
    {
      "parameters": {
        "jsCode": "const prev = $('3. Email Check').first().json;\nconst raw = $input.first().json;\nlet html = '';\nif (typeof raw.data === 'string') html = raw.data;\nelse if (typeof raw.body === 'string') html = raw.body;\nelse if (raw.data && typeof raw.data === 'object') html = JSON.stringify(raw.data);\nconst h = html.toLowerCase();\nprev.results.websiteSignals = {\n  websiteReachable: html.length > 500, htmlLength: html.length,\n  isWordPress: h.includes('/wp-content/') || h.includes('wordpress'),\n  isWix: h.includes('wix.com') || h.includes('wixsite'),\n  isSquarespace: h.includes('squarespace'), isShopify: h.includes('shopify'),\n  hasTeamPage: h.includes('/team') || h.includes('/about-us') || h.includes('/our-team') || h.includes('/staff'),\n  hasAboutPage: h.includes('/about') || h.includes('about us'),\n  hasPhysicalAddress: /\\d{1,5}\\s+[\\w\\s]+(?:st|street|ave|avenue|blvd|boulevard|rd|road|dr|drive|ln|lane|ct|court|way|pl|place)/i.test(html),\n  hasPhoneNumber: /\\(?\\d{3}\\)?[\\s.-]?\\d{3}[\\s.-]?\\d{4}/.test(html),\n  hasContactPage: h.includes('/contact') || h.includes('contact us'),\n  isParkedDomain: h.includes('this domain') || h.includes('parked') || h.includes('for sale') || h.includes('buy this domain'),\n  isUnderConstruction: h.includes('under construction') || h.includes('coming soon'),\n  pageTitle: (html.match(/<title[^>]*>([^<]+)<\\/title>/i) || [null, 'No title found'])[1].trim()\n};\nprev.results.truncatedHtml = html.substring(0, 4000);\nreturn [{ json: prev }];"
      },
      "id": "code-signals-001",
      "name": "5. Extract Signals",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1320,
        300
      ]
    },
    {
      "parameters": {
        "method": "POST",
        "url": "https://openrouter.ai/api/v1/chat/completions",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "Authorization",
              "value": "Bearer YOUR_OPENROUTER_API_KEY"
            },
            {
              "name": "Content-Type",
              "value": "application/json"
            }
          ]
        },
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={{ JSON.stringify({ model: 'anthropic/claude-sonnet-4', messages: [{ role: 'system', content: 'You are a website quality analyst. Analyze HTML signals for a company. Assess: 1) Real business site or stock template/parked? 2) Active? 3) What do they do? 4) Size? 5) Team/about page? Return ONLY JSON: {quality:\"professional\"|\"template\"|\"minimal\"|\"parked\"|\"unreachable\", assessment:\"2-3 sentences\", companyDescription:\"what they do\", estimatedSize:\"estimate\", redFlags:[], greenFlags:[]}' }, { role: 'user', content: 'Company: ' + $json.lead_company + ' (' + $json.lead_city + ')\\nWebsite: ' + $json.lead_companyWebsite + '\\nSignals: ' + JSON.stringify($json.results.websiteSignals) + '\\nHTML snippet: ' + ($json.results.truncatedHtml || 'UNREACHABLE').substring(0, 2500) }], max_tokens: 500 }) }}",
        "options": {
          "timeout": 30000
        }
      },
      "id": "http-ai-website-001",
      "name": "6. AI Website Analysis",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        1540,
        300
      ],
      "onError": "continueRegularOutput"
    },
    {
      "parameters": {
        "jsCode": "const prev = $('5. Extract Signals').first().json;\nconst raw = $input.first().json;\ntry { prev.results.websiteAnalysis = raw.choices[0].message.content; } catch(e) { prev.results.websiteAnalysis = 'Website AI analysis failed'; }\ndelete prev.results.truncatedHtml;\nreturn [{ json: prev }];"
      },
      "id": "code-store-website",
      "name": "Store Website AI",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1760,
        300
      ]
    },
    {
      "parameters": {
        "url": "={{ 'https://maps.googleapis.com/maps/api/place/textsearch/json?query=' + encodeURIComponent($json.lead_company + ' ' + $json.lead_city) + '&key=YOUR_GOOGLE_MAPS_API_KEY' }}",
        "options": {
          "timeout": 10000
        }
      },
      "id": "http-gmaps-001",
      "name": "7. Google Maps",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        1980,
        300
      ],
      "onError": "continueRegularOutput"
    },
    {
      "parameters": {
        "jsCode": "const prev = $('Store Website AI').first().json;\nconst raw = $input.first().json;\nconst results = raw.results || [];\nif (results.length > 0) {\n  const t = results[0];\n  prev.results.googleMaps = { found: true, businessName: t.name || null, address: t.formatted_address || null, rating: t.rating || null, reviewCount: t.user_ratings_total || 0, businessStatus: t.business_status || null, types: t.types || [] };\n} else { prev.results.googleMaps = { found: false }; }\nreturn [{ json: prev }];"
      },
      "id": "code-store-gmaps",
      "name": "Store Maps",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        2200,
        300
      ]
    },
    {
      "parameters": {
        "url": "={{ 'https://www.gravatar.com/avatar/' + $json.lead_emailMd5 + '?d=404' }}",
        "options": {
          "timeout": 5000
        }
      },
      "id": "http-gravatar-001",
      "name": "8. Gravatar",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        2420,
        300
      ],
      "onError": "continueRegularOutput"
    },
    {
      "parameters": {
        "jsCode": "const prev = $('Store Maps').first().json;\nconst raw = $input.first().json;\nprev.results.gravatar = { hasProfile: !(raw.error || raw.statusCode === 404 || raw.statusCode === '404') };\nreturn [{ json: prev }];"
      },
      "id": "code-store-gravatar",
      "name": "Store Gravatar",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        2640,
        300
      ]
    },
    {
      "parameters": {
        "method": "POST",
        "url": "https://openrouter.ai/api/v1/chat/completions",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "Authorization",
              "value": "Bearer YOUR_OPENROUTER_API_KEY"
            },
            {
              "name": "Content-Type",
              "value": "application/json"
            }
          ]
        },
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={{ JSON.stringify({ model: 'anthropic/claude-sonnet-4', messages: [{ role: 'system', content: 'You are a person verification analyst for B2B lead qualification. Based on all data provided, assess this person. Return ONLY JSON: {emailDomainMatch:boolean, confidence:\"verified\"|\"probable\"|\"unverified\"|\"suspicious\", summary:\"2-3 sentences\", greenFlags:[], redFlags:[]}' }, { role: 'user', content: 'Person: ' + $json.lead_name + '\\nCompany: ' + $json.lead_company + ', ' + $json.lead_city + '\\nEmail: ' + $json.lead_email + ' (freemail: ' + $json.lead_isFreemail + ')\\nGravatar: ' + $json.results.gravatar.hasProfile + '\\nGoogle Maps: ' + JSON.stringify($json.results.googleMaps) + '\\nWebsite Analysis: ' + ($json.results.websiteAnalysis || 'unavailable') + '\\nEmail Check: ' + JSON.stringify($json.results.email) + '\\nPhone: ' + JSON.stringify($json.results.phone) }], max_tokens: 500 }) }}",
        "options": {
          "timeout": 30000
        }
      },
      "id": "http-ai-person-001",
      "name": "12. AI Person Analysis",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        4180,
        300
      ],
      "onError": "continueRegularOutput"
    },
    {
      "parameters": {
        "jsCode": "const prev = $('Store Gravatar').first().json;\nconst raw = $input.first().json;\ntry { prev.results.personAnalysis = raw.choices[0].message.content; } catch(e) { prev.results.personAnalysis = 'Person analysis failed'; }\nreturn [{ json: prev }];"
      },
      "id": "code-store-person",
      "name": "Store Person AI",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        4400,
        300
      ]
    },
    {
      "parameters": {
        "method": "POST",
        "url": "https://openrouter.ai/api/v1/chat/completions",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "Authorization",
              "value": "Bearer YOUR_OPENROUTER_API_KEY"
            },
            {
              "name": "Content-Type",
              "value": "application/json"
            }
          ]
        },
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={{ JSON.stringify({ model: 'anthropic/claude-sonnet-4', messages: [{ role: 'system', content: 'You are a lead qualification analyst for a B2B office copier sales team. Synthesize ALL enrichment data into a lead brief.\\n\\nRate these dimensions:\\n1. COMPANY HEALTH: Established / Active / Thin / Nonexistent\\n2. WEBSITE QUALITY: professional / template / minimal / parked / unreachable\\n3. PERSON VERIFICATION: Verified / Probable / Unverified / Suspicious\\n4. CONTACT VALIDITY: Note any flags (VoIP, freemail, disposable, etc.)\\n5. OVERALL SCORE: A (call first) / B (normal priority) / C (caution) / D (consider refund)\\n\\nOutput EXACTLY this format and NOTHING ELSE. Do NOT add any extra sections beyond what is listed here:\\nSCORE: [A/B/C/D]\\nSUMMARY: [2-3 sentences]\\nGREEN FLAGS:\\n- [flag]\\nRED FLAGS:\\n- [flag]\\nCOMPANY HEALTH: [rating] - [1 sentence]\\nPERSON CONFIDENCE: [rating] - [1 sentence]\\nCALL NOTES: [2-3 sentences on recommended approach]\\n\\nDo NOT include sections for LinkedIn, News, Public Records, or any other categories not listed above. Only output the exact sections shown.' }, { role: 'user', content: 'LEAD: ' + $json.lead_name + ' | ' + $json.lead_email + ' | ' + $json.lead_phone + ' | ' + $json.lead_company + ' | ' + $json.lead_city + '\\nFreemail: ' + $json.lead_isFreemail + '\\n\\nENRICHMENT DATA:\\n' + JSON.stringify($json.results, null, 2) }], max_tokens: 1000 }) }}",
        "options": {
          "timeout": 45000
        }
      },
      "id": "http-ai-synthesis-001",
      "name": "14. AI Final Brief",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        5060,
        300
      ],
      "onError": "continueRegularOutput"
    },
    {
      "parameters": {
        "jsCode": "const prev = $('Store Email Draft').first().json;\nconst raw = $input.first().json;\nlet briefText = 'Lead brief generation failed.';\ntry { briefText = raw.choices[0].message.content; } catch(e) { briefText = 'Error: ' + e.message; }\nlet score = 'N/A';\nconst m = briefText.match(/SCORE:\\s*([ABCD])/i);\nif (m) score = m[1].toUpperCase();\n\nreturn [{ json: {\n  success: true, timestamp: new Date().toISOString(),\n  leadName: prev.lead_name, leadEmail: prev.lead_email, leadPhone: prev.lead_phone,\n  leadCompany: prev.lead_company, leadCity: prev.lead_city,\n  score: score, brief: briefText,\n  emailDraft: prev.results.emailDraft || { subject: 'Following up', body: 'Draft unavailable' },\n  enrichmentData: prev.results\n} }];"
      },
      "id": "code-format-001",
      "name": "15. Format Output",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        5280,
        300
      ]
    },
    {
      "parameters": {
        "respondWith": "json",
        "responseBody": "={{ JSON.stringify($json) }}",
        "options": {
          "responseCode": 200,
          "responseHeaders": {
            "entries": [
              {
                "name": "Content-Type",
                "value": "application/json"
              }
            ]
          }
        }
      },
      "id": "respond-001",
      "name": "16. Respond",
      "type": "n8n-nodes-base.respondToWebhook",
      "typeVersion": 1.1,
      "position": [
        5500,
        300
      ]
    },
    {
      "parameters": {
        "method": "POST",
        "url": "https://openrouter.ai/api/v1/chat/completions",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "Authorization",
              "value": "Bearer YOUR_OPENROUTER_API_KEY"
            },
            {
              "name": "Content-Type",
              "value": "application/json"
            }
          ]
        },
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={{ JSON.stringify({ model: 'anthropic/claude-sonnet-4', messages: [{ role: 'system', content: 'You are a sales email copywriter for a B2B office copier and managed print services company. You write \"missed call follow-up\" emails \u2014 the salesperson called, the lead did not answer, and now you are writing the follow-up email.\\n\\nThe email should:\\n1. Open with a brief \"I just tried to reach you\" line\\n2. Mention that you received their request for information about office copiers or managed print solutions\\n3. Reference their company by name and any specific details you know (location, industry, size) to show you did your homework \u2014 but keep it natural, not creepy\\n4. Briefly mention what you can help with (copiers, MFPs, managed print services)\\n5. End with a soft call-to-action (suggest a quick call, or say you will try again)\\n6. Keep it SHORT \u2014 4-6 sentences max in the body. Nobody reads long sales emails.\\n\\nTONE RULES based on lead score:\\n- Score A or B: Casual and friendly. Like a real person reaching out, not a template. Use their first name.\\n- Score C: Professional but warm. Slightly more formal. Still use first name.\\n- Score D: Formal and brief. Use Mr./Ms. Last Name. Keep it very short and factual.\\n\\nDo NOT include a signature \u2014 the salesperson\\'s email client adds one automatically.\\nDo NOT use placeholder brackets like [Your Name].\\nDo NOT include any subject line prefixes like \"Subject:\".\\n\\nReturn ONLY a JSON object with two fields:\\n{\\n  \"subject\": \"the email subject line\",\\n  \"body\": \"the full email body text\"\\n}' }, { role: 'user', content: 'First Name: ' + $json.lead_firstName + '\\nLast Name: ' + $json.lead_lastName + '\\nFull Name: ' + $json.lead_name + '\\nCompany: ' + $json.lead_company + '\\nCity: ' + $json.lead_city + '\\nEmail: ' + $json.lead_email + '\\n\\nWhat we know about the company:\\n' + ($json.results.websiteAnalysis || 'No website data') + '\\n\\nGoogle Maps: ' + JSON.stringify($json.results.googleMaps || {}) + '\\n\\nPerson Analysis: ' + ($json.results.personAnalysis || 'No person data') }], max_tokens: 500 }) }}",
        "options": {
          "timeout": 30000
        }
      },
      "id": "http-ai-email-001",
      "name": "13. AI Email Draft",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        4620,
        300
      ],
      "onError": "continueRegularOutput"
    },
    {
      "parameters": {
        "jsCode": "const prev = $('Store Person AI').first().json;\nconst raw = $input.first().json;\ntry {\n  const content = raw.choices[0].message.content;\n  let parsed;\n  try {\n    const cleaned = content.replace(/```json\\n?/g, '').replace(/```\\n?/g, '').trim();\n    parsed = JSON.parse(cleaned);\n  } catch(e) {\n    parsed = { subject: 'Following up on your copier inquiry', body: content };\n  }\n  prev.results.emailDraft = parsed;\n} catch(e) {\n  prev.results.emailDraft = { subject: 'Following up on your copier inquiry', body: 'Email draft generation failed: ' + e.message };\n}\nreturn [{ json: prev }];"
      },
      "id": "code-store-email",
      "name": "Store Email Draft",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        4840,
        300
      ]
    }
  ],
  "connections": {
    "Webhook": {
      "main": [
        [
          {
            "node": "1. Pre-Process",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "1. Pre-Process": {
      "main": [
        [
          {
            "node": "2. Phone Validation",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "2. Phone Validation": {
      "main": [
        [
          {
            "node": "Store Phone",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Store Phone": {
      "main": [
        [
          {
            "node": "3. Email Check",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "3. Email Check": {
      "main": [
        [
          {
            "node": "4. Fetch Website",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "4. Fetch Website": {
      "main": [
        [
          {
            "node": "5. Extract Signals",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "5. Extract Signals": {
      "main": [
        [
          {
            "node": "6. AI Website Analysis",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "6. AI Website Analysis": {
      "main": [
        [
          {
            "node": "Store Website AI",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Store Website AI": {
      "main": [
        [
          {
            "node": "7. Google Maps",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "7. Google Maps": {
      "main": [
        [
          {
            "node": "Store Maps",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Store Maps": {
      "main": [
        [
          {
            "node": "8. Gravatar",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "8. Gravatar": {
      "main": [
        [
          {
            "node": "Store Gravatar",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Store Gravatar": {
      "main": [
        [
          {
            "node": "12. AI Person Analysis",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "12. AI Person Analysis": {
      "main": [
        [
          {
            "node": "Store Person AI",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Store Person AI": {
      "main": [
        [
          {
            "node": "13. AI Email Draft",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "13. AI Email Draft": {
      "main": [
        [
          {
            "node": "Store Email Draft",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Store Email Draft": {
      "main": [
        [
          {
            "node": "14. AI Final Brief",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "14. AI Final Brief": {
      "main": [
        [
          {
            "node": "15. Format Output",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "15. Format Output": {
      "main": [
        [
          {
            "node": "16. Respond",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "settings": {
    "executionOrder": "v1"
  },
  "staticData": null,
  "tags": [],
  "triggerCount": 1,
  "updatedAt": "2026-03-15T00:00:00.000Z",
  "versionId": "community-1.0"
}