{
  "name": "Instant Lead Response Engine (60 Second Reply)",
  "nodes": [
    {
      "id": "58283fb6-0f4c-4fae-8799-f83a72945f78",
      "name": "Inbound Lead Webhook",
      "type": "n8n-nodes-base.webhook",
      "typeVersion": 2,
      "position": [
        0,
        0
      ],
      "webhookId": "35ff7f73-e85b-4443-b080-2cb472b92b47",
      "parameters": {
        "httpMethod": "POST",
        "path": "inbound-lead",
        "responseMode": "onReceived",
        "options": {}
      }
    },
    {
      "id": "13a04d3a-6186-4581-9e13-a0085a220541",
      "name": "Normalize Multi-Channel Lead",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        260,
        0
      ],
      "parameters": {
        "mode": "runOnceForAllItems",
        "jsCode": "// Normalizes website form / LinkedIn DM / WhatsApp payloads into ONE contract.\n// Every channel adapter posts to this webhook with a \"source\" field.\nconst CALENDAR_LINK = 'YOUR_CALENDAR_LINK_HERE';\nconst FREE_DOMAINS = ['gmail.com','yahoo.com','hotmail.com','outlook.com','rediffmail.com','icloud.com','proton.me'];\n\nconst out = [];\n\nfor (const item of $input.all()) {\n  const b = item.json.body || item.json;\n  const source = String(b.source || 'website').toLowerCase();\n\n  let name = '', email = '', phone = '', company = '', jobTitle = '', profileUrl = '', message = '';\n\n  if (source === 'whatsapp') {\n    name       = b.name || b.profile_name || '';\n    phone      = b.phone || b.from || b.wa_id || '';\n    message    = b.message || b.text || '';\n  } else if (source === 'linkedin') {\n    name       = b.name || b.sender_name || '';\n    profileUrl = b.profile_url || b.sender_profile || '';\n    company    = b.company || '';\n    jobTitle   = b.title || b.headline || '';\n    message    = b.message || b.text || '';\n  } else {\n    name       = b.name || b.full_name || '';\n    email      = b.email || '';\n    phone      = b.phone || b.mobile || '';\n    company    = b.company || '';\n    jobTitle   = b.title || '';\n    message    = [b.message, b.requirement, b.notes].filter(Boolean).join(' ');\n  }\n\n  email = String(email).trim().toLowerCase();\n  const domain = email.includes('@') ? email.split('@')[1] : '';\n\n  out.push({\n    json: {\n      source,\n      name: String(name).trim(),\n      email,\n      phone: String(phone).replace(/[^0-9+]/g, ''),\n      company: String(company).trim(),\n      jobTitle: String(jobTitle).trim(),\n      profileUrl,\n      rawMessage: String(message).trim().slice(0, 2000),\n      companyDomain: FREE_DOMAINS.includes(domain) ? '' : domain,\n      calendarLink: CALENDAR_LINK,\n      receivedAt: new Date().toISOString()\n    }\n  });\n}\n\nreturn out;\n"
      }
    },
    {
      "id": "a6875e79-4e5e-49e6-bc5a-91802ae7f992",
      "name": "Extract Intent From Message",
      "type": "@n8n/n8n-nodes-langchain.informationExtractor",
      "typeVersion": 1,
      "position": [
        520,
        0
      ],
      "onError": "continueRegularOutput",
      "parameters": {
        "text": "={{ $json.rawMessage }}",
        "schemaType": "fromAttributes",
        "attributes": {
          "attributes": [
            {
              "name": "jobTitle",
              "type": "string",
              "description": "The sender's job title or role if stated or implied. Empty string if unknown.",
              "required": false
            },
            {
              "name": "companyName",
              "type": "string",
              "description": "The company the sender works at. Empty string if unknown.",
              "required": false
            },
            {
              "name": "serviceInterest",
              "type": "string",
              "description": "In under 10 words, what the lead actually wants. Empty string if unclear.",
              "required": false
            },
            {
              "name": "timeline",
              "type": "string",
              "description": "Any timing signal such as this week, next month, urgent, asap. Empty string if none.",
              "required": false
            },
            {
              "name": "budgetMentioned",
              "type": "boolean",
              "description": "True only if the message mentions budget, price, cost or a quote.",
              "required": false
            },
            {
              "name": "summary",
              "type": "string",
              "description": "One plain sentence summarising the request.",
              "required": false
            }
          ]
        },
        "options": {
          "systemPromptTemplate": "Extract only what is explicitly present in the message. Never guess or invent. If a field is not stated, return an empty string or false."
        }
      }
    },
    {
      "id": "6b8cf661-44eb-4473-8bd8-1343fac02504",
      "name": "Extraction Model",
      "type": "@n8n/n8n-nodes-langchain.lmChatOpenAi",
      "typeVersion": 1.2,
      "position": [
        520,
        240
      ],
      "parameters": {
        "model": {
          "__rl": true,
          "value": "gpt-4o-mini",
          "mode": "list",
          "cachedResultName": "gpt-4o-mini"
        },
        "options": {
          "temperature": 0
        }
      }
    },
    {
      "id": "7829709f-c2a6-41c7-8277-d11312d2dfc2",
      "name": "Score And Tier Lead",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        780,
        0
      ],
      "parameters": {
        "mode": "runOnceForAllItems",
        "jsCode": "// Deterministic scoring. The AI only EXTRACTS, it never decides the tier.\n// This is what keeps accuracy high and behaviour explainable to the client.\nconst HOT_AT = 7;\nconst WARM_AT = 4;\n\nconst SENIOR = ['founder','ceo','co-founder','cofounder','owner','director','head','vp','vice president','chief','partner','manager','proprietor'];\nconst INTENT_WORDS = ['budget','timeline','quote','pricing','price','proposal','urgent','asap','this week','demo','trial','onboard','get started','contract','invoice','kitna','kab'];\n\nconst items = $input.all();\nconst base = $('Normalize Multi-Channel Lead').all();\nconst out = [];\n\nfor (let i = 0; i < items.length; i++) {\n  const ai = items[i].json.output || {};\n  const lead = (base[i] || items[i]).json;\n\n  const title = String(ai.jobTitle || lead.jobTitle || '').toLowerCase();\n  const msg = String(lead.rawMessage || '').toLowerCase();\n  const timeline = String(ai.timeline || '').toLowerCase();\n\n  let score = 0;\n  if (SENIOR.some(w => title.includes(w))) score += 3;\n  if (lead.companyDomain) score += 2;\n\n  const hits = INTENT_WORDS.filter(w => msg.includes(w));\n  score += Math.min(hits.length * 1.5, 4);\n\n  if (/week|immediate|asap|urgent|this month/.test(timeline)) score += 2;\n  if (ai.budgetMentioned === true || String(ai.budgetMentioned).toLowerCase() === 'true') score += 2;\n  if (lead.source === 'website') score += 1;\n\n  score = Math.max(0, Math.min(Math.round(score), 10));\n  const tier = score >= HOT_AT ? 'hot' : (score >= WARM_AT ? 'warm' : 'cold');\n\n  // The 20% human-review guardrail from the newsletter.\n  const hasContact = Boolean(lead.email || (lead.phone && lead.phone.length >= 8));\n  const parsedOk = Boolean(ai.serviceInterest || ai.summary || msg.length > 15);\n  const dataComplete = hasContact && Boolean(lead.name) && parsedOk;\n\n  const replyChannel = (lead.source === 'whatsapp' && lead.phone)\n    ? 'whatsapp'\n    : (lead.email ? 'email' : 'manual');\n\n  out.push({\n    json: {\n      receivedAt: lead.receivedAt,\n      source: lead.source,\n      name: lead.name,\n      email: lead.email,\n      phone: lead.phone,\n      company: ai.companyName || lead.company,\n      jobTitle: ai.jobTitle || lead.jobTitle,\n      companyDomain: lead.companyDomain,\n      serviceInterest: ai.serviceInterest || '',\n      timeline: ai.timeline || '',\n      budgetMentioned: Boolean(ai.budgetMentioned),\n      summary: ai.summary || '',\n      rawMessage: lead.rawMessage,\n      matchedKeywords: hits.join(', '),\n      score,\n      tier,\n      dataComplete,\n      replyChannel,\n      calendarLink: lead.calendarLink,\n      profileUrl: lead.profileUrl,\n      reviewReason: dataComplete ? '' : (!hasContact ? 'no usable email or phone' : (!lead.name ? 'missing name' : 'message too thin to parse'))\n    }\n  });\n}\n\nreturn out;\n"
      }
    },
    {
      "id": "30910f8e-734f-4fa1-bfb2-385a525bedaa",
      "name": "Log Lead To Sheet",
      "type": "n8n-nodes-base.googleSheets",
      "typeVersion": 4.5,
      "position": [
        1040,
        0
      ],
      "retryOnFail": true,
      "maxTries": 3,
      "waitBetweenTries": 2000,
      "onError": "continueRegularOutput",
      "parameters": {
        "operation": "append",
        "documentId": {
          "__rl": true,
          "value": "YOUR_SHEET_ID_HERE",
          "mode": "id"
        },
        "sheetName": {
          "__rl": true,
          "value": "All Leads",
          "mode": "name"
        },
        "columns": {
          "mappingMode": "autoMapInputData",
          "value": {},
          "matchingColumns": []
        },
        "options": {}
      }
    },
    {
      "id": "dbf26ef1-6c20-4309-adf1-73bf3dd5e092",
      "name": "Is Lead Data Complete?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        1300,
        0
      ],
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "strict",
            "version": 2
          },
          "conditions": [
            {
              "id": "1b365069-df2f-4d94-8770-b47d39d2a0db",
              "leftValue": "={{ $json.dataComplete }}",
              "rightValue": "",
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      }
    },
    {
      "id": "7df0380a-c40b-4c62-b473-f7d4da4145ae",
      "name": "Route By Lead Tier",
      "type": "n8n-nodes-base.switch",
      "typeVersion": 3.2,
      "position": [
        1560,
        -140
      ],
      "parameters": {
        "rules": {
          "values": [
            {
              "conditions": {
                "options": {
                  "caseSensitive": true,
                  "leftValue": "",
                  "typeValidation": "strict",
                  "version": 2
                },
                "conditions": [
                  {
                    "id": "a1b458d7-fbbc-4f53-a3c0-eaf3e4b3937c",
                    "leftValue": "={{ $json.tier }}",
                    "rightValue": "hot",
                    "operator": {
                      "type": "string",
                      "operation": "equals"
                    }
                  }
                ],
                "combinator": "and"
              },
              "renameOutput": true,
              "outputKey": "Hot"
            },
            {
              "conditions": {
                "options": {
                  "caseSensitive": true,
                  "leftValue": "",
                  "typeValidation": "strict",
                  "version": 2
                },
                "conditions": [
                  {
                    "id": "ed72da79-4498-4db2-a294-80aff2ceaa27",
                    "leftValue": "={{ $json.tier }}",
                    "rightValue": "warm",
                    "operator": {
                      "type": "string",
                      "operation": "equals"
                    }
                  }
                ],
                "combinator": "and"
              },
              "renameOutput": true,
              "outputKey": "Warm"
            }
          ]
        },
        "options": {
          "fallbackOutput": "extra"
        }
      }
    },
    {
      "id": "955f8d7e-cea7-47fb-b247-2f2dda0ab27d",
      "name": "Queue For Manual Review",
      "type": "n8n-nodes-base.googleSheets",
      "typeVersion": 4.5,
      "position": [
        1560,
        200
      ],
      "retryOnFail": true,
      "maxTries": 3,
      "waitBetweenTries": 2000,
      "parameters": {
        "operation": "append",
        "documentId": {
          "__rl": true,
          "value": "YOUR_SHEET_ID_HERE",
          "mode": "id"
        },
        "sheetName": {
          "__rl": true,
          "value": "Manual Review",
          "mode": "name"
        },
        "columns": {
          "mappingMode": "autoMapInputData",
          "value": {},
          "matchingColumns": []
        },
        "options": {}
      }
    },
    {
      "id": "46e7f149-f493-4889-9575-7207e67b8046",
      "name": "Draft Personalised Reply",
      "type": "@n8n/n8n-nodes-langchain.chainLlm",
      "typeVersion": 1.4,
      "position": [
        1860,
        -280
      ],
      "retryOnFail": true,
      "maxTries": 2,
      "parameters": {
        "promptType": "define",
        "text": "=You write the FIRST reply to an inbound B2B lead. Goal: get a call booked. Write like a real person, not a marketing bot.\n\nLead details:\n- Name: {{ $json.name }}\n- Company: {{ $json.company }}\n- Role: {{ $json.jobTitle }}\n- Channel: {{ $json.source }}\n- What they asked for: {{ $json.serviceInterest }}\n- Timeline they mentioned: {{ $json.timeline }}\n- Their original message: {{ $json.rawMessage }}\n\nRules:\n1. Open by referencing their actual message in a specific way. No generic thank-you lines.\n2. Give one concrete sentence on how this problem is usually solved, so they see competence.\n3. Ask for a 15-minute call. Do NOT paste a link, a link is appended automatically after your text.\n4. Max 90 words. Short paragraphs. No emojis. No em dashes. No jargon.\n5. If the channel is whatsapp, be even shorter and more casual.\n6. Output only the message body. No subject line, no signature block."
      }
    },
    {
      "id": "882699e7-f5c8-435a-a0ad-8e0ea4ec19c9",
      "name": "Reply Writer Model",
      "type": "@n8n/n8n-nodes-langchain.lmChatOpenAi",
      "typeVersion": 1.2,
      "position": [
        1860,
        -60
      ],
      "parameters": {
        "model": {
          "__rl": true,
          "value": "gpt-4o-mini",
          "mode": "list",
          "cachedResultName": "gpt-4o-mini"
        },
        "options": {
          "temperature": 0.4
        }
      }
    },
    {
      "id": "fdbf3f5e-f97e-4382-b2b6-91452c57d0d5",
      "name": "Reply On WhatsApp?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        2120,
        -280
      ],
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "strict",
            "version": 2
          },
          "conditions": [
            {
              "id": "8f6b0a09-3e0c-4b81-b25f-558a2cbec0c9",
              "leftValue": "={{ $('Score And Tier Lead').item.json.replyChannel }}",
              "rightValue": "whatsapp",
              "operator": {
                "type": "string",
                "operation": "equals"
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      }
    },
    {
      "id": "52b22d62-0db8-4355-b645-6b64070bfcb3",
      "name": "Send WhatsApp Reply",
      "type": "n8n-nodes-base.whatsApp",
      "typeVersion": 1.1,
      "position": [
        2380,
        -400
      ],
      "retryOnFail": true,
      "maxTries": 3,
      "waitBetweenTries": 2000,
      "parameters": {
        "operation": "send",
        "phoneNumberId": "YOUR_WHATSAPP_PHONE_NUMBER_ID",
        "recipientPhoneNumber": "={{ $('Score And Tier Lead').item.json.phone }}",
        "messageType": "text",
        "textBody": "={{ $json.text || $json.output }}\n\nBook a 15 min slot here: {{ $('Score And Tier Lead').item.json.calendarLink }}",
        "additionalFields": {}
      }
    },
    {
      "id": "d85ae5cf-9621-4daa-b932-d2cb75a95294",
      "name": "Send Email Reply",
      "type": "n8n-nodes-base.gmail",
      "typeVersion": 2.1,
      "position": [
        2380,
        -160
      ],
      "retryOnFail": true,
      "maxTries": 3,
      "waitBetweenTries": 2000,
      "parameters": {
        "sendTo": "={{ $('Score And Tier Lead').item.json.email }}",
        "subject": "=Re: your enquiry, {{ $('Score And Tier Lead').item.json.name }}",
        "emailType": "text",
        "message": "={{ $json.text || $json.output }}\n\nBook a 15 min slot here: {{ $('Score And Tier Lead').item.json.calendarLink }}",
        "options": {}
      }
    },
    {
      "id": "3a2183a7-76c3-4a00-90de-1cf6102408bd",
      "name": "Alert Sales Team",
      "type": "n8n-nodes-base.slack",
      "typeVersion": 2.2,
      "position": [
        2640,
        -280
      ],
      "retryOnFail": true,
      "maxTries": 2,
      "onError": "continueRegularOutput",
      "parameters": {
        "select": "channel",
        "channelId": {
          "__rl": true,
          "value": "YOUR_SLACK_CHANNEL_ID",
          "mode": "id"
        },
        "text": "=HOT LEAD replied in under 60s\n\nName: {{ $('Score And Tier Lead').item.json.name }}\nCompany: {{ $('Score And Tier Lead').item.json.company }}\nRole: {{ $('Score And Tier Lead').item.json.jobTitle }}\nScore: {{ $('Score And Tier Lead').item.json.score }}/10\nChannel: {{ $('Score And Tier Lead').item.json.source }}\nWants: {{ $('Score And Tier Lead').item.json.serviceInterest }}\n\nTheir message: {{ $('Score And Tier Lead').item.json.rawMessage }}",
        "otherOptions": {}
      }
    },
    {
      "id": "70b88b91-7054-4312-a80e-d9c15d456a74",
      "name": "Add To Nurture Sequence",
      "type": "n8n-nodes-base.googleSheets",
      "typeVersion": 4.5,
      "position": [
        1860,
        140
      ],
      "retryOnFail": true,
      "maxTries": 3,
      "waitBetweenTries": 2000,
      "parameters": {
        "operation": "append",
        "documentId": {
          "__rl": true,
          "value": "YOUR_SHEET_ID_HERE",
          "mode": "id"
        },
        "sheetName": {
          "__rl": true,
          "value": "Nurture",
          "mode": "name"
        },
        "columns": {
          "mappingMode": "autoMapInputData",
          "value": {},
          "matchingColumns": []
        },
        "options": {}
      }
    },
    {
      "id": "63bc5865-43aa-4fed-ae3b-512622047e6b",
      "name": "Section: Intake",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        -120,
        -220
      ],
      "parameters": {
        "content": "## 1. Multi-channel intake\nOne webhook, three sources. Website form, LinkedIn DM forwarder and WhatsApp all POST here with a `source` field.\n\nThe Code node flattens every payload shape into one contract so nothing downstream has to care where the lead came from.",
        "width": 500,
        "height": 400
      }
    },
    {
      "id": "2b1485d0-810f-4942-9e6d-3cb4e3c840e1",
      "name": "Section: Extract And Score",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        420,
        -220
      ],
      "parameters": {
        "content": "## 2. AI extracts, code decides\nThe LLM only pulls facts out of unstructured text. Scoring stays in plain JavaScript so the tier is explainable and never drifts.\n\nRole seniority + business domain + intent keywords + timeline + budget mention = score out of 10.\n\nExtraction failures do not stop the run. They fall through as incomplete and get caught by the next node.",
        "width": 760,
        "height": 400
      }
    },
    {
      "id": "9bf189f1-8255-426a-8a85-311144883d28",
      "name": "Section: Guardrail",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        1240,
        -320
      ],
      "parameters": {
        "content": "## 3. The 80/20 guardrail\nAnything without a usable contact, a name, or a parseable message goes to the Manual Review tab instead of getting a bad auto-reply.\n\nThis is the single change that took accuracy past 92%.",
        "width": 460,
        "height": 700
      }
    },
    {
      "id": "ce63c43b-2512-4a50-bddf-a58c8bcf0d92",
      "name": "Section: Instant Reply",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        1780,
        -520
      ],
      "parameters": {
        "content": "## 4. Under 60 seconds\nHot leads only. The model writes the body, the calendar link is appended in code so it can never be hallucinated or mangled.\n\nWhatsApp leads get a WhatsApp reply, everyone else gets email. Sales gets a Slack ping after the reply is already out.",
        "width": 1100,
        "height": 560
      }
    }
  ],
  "connections": {
    "Inbound Lead Webhook": {
      "main": [
        [
          {
            "node": "Normalize Multi-Channel Lead",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Normalize Multi-Channel Lead": {
      "main": [
        [
          {
            "node": "Extract Intent From Message",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Extract Intent From Message": {
      "main": [
        [
          {
            "node": "Score And Tier Lead",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Extraction Model": {
      "ai_languageModel": [
        [
          {
            "node": "Extract Intent From Message",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    },
    "Score And Tier Lead": {
      "main": [
        [
          {
            "node": "Log Lead To Sheet",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Log Lead To Sheet": {
      "main": [
        [
          {
            "node": "Is Lead Data Complete?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Is Lead Data Complete?": {
      "main": [
        [
          {
            "node": "Route By Lead Tier",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Queue For Manual Review",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Route By Lead Tier": {
      "main": [
        [
          {
            "node": "Draft Personalised Reply",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Add To Nurture Sequence",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Add To Nurture Sequence",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Reply Writer Model": {
      "ai_languageModel": [
        [
          {
            "node": "Draft Personalised Reply",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    },
    "Draft Personalised Reply": {
      "main": [
        [
          {
            "node": "Reply On WhatsApp?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Reply On WhatsApp?": {
      "main": [
        [
          {
            "node": "Send WhatsApp Reply",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Send Email Reply",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Send WhatsApp Reply": {
      "main": [
        [
          {
            "node": "Alert Sales Team",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Send Email Reply": {
      "main": [
        [
          {
            "node": "Alert Sales Team",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "settings": {
    "executionOrder": "v1"
  },
  "pinData": {}
}