{
  "name": "DEMO — Lead intake, score & instant reply (PauSolutions)",
  "nodes": [
    {
      "parameters": {
        "content": "## Lead intake, score & instant reply — DEMO\nFictional interior design firm, no credentials.\n\n1. Click *Test workflow* to run the sample enquiry, or POST JSON {name, email, company, phone, message} to the webhook URL.\n2. Transparent rules classify the enquiry and give it a score with reasons.\n3. Connect your email for the instant reply and the owner alert, and a Sheets/CRM node for the lead list.\n\nInteractive version: pausolutions.com/demos/lead-intake/\n\n_All dates in this demo are examples (sample \"today\": 21 Oct 2026). In production the workflow uses the real current date._",
        "height": 370,
        "width": 480
      },
      "id": "1286c532-bc20-4980-acb1-bc8b28a939be",
      "name": "About this demo",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        -80,
        -400
      ]
    },
    {
      "parameters": {
        "httpMethod": "POST",
        "path": "demo-lead-intake-en",
        "responseMode": "onReceived",
        "options": {}
      },
      "id": "eb44a6a3-843f-4000-a573-df1aea986705",
      "name": "Website form (webhook)",
      "type": "n8n-nodes-base.webhook",
      "typeVersion": 2,
      "position": [
        0,
        100
      ],
      "webhookId": "a4e4f2d0-c3c0-4000-aad3-8b69bb0c8ab0"
    },
    {
      "parameters": {},
      "id": "bf68561d-0e12-4000-a15f-2fa48490663b",
      "name": "Test run",
      "type": "n8n-nodes-base.manualTrigger",
      "typeVersion": 1,
      "position": [
        -240,
        300
      ]
    },
    {
      "parameters": {
        "jsCode": "// DEMO DATA — a fictional enquiry, shaped like the webhook output.\nreturn [{ json: { body: {\n  \"name\": \"Emma Clarke\",\n  \"email\": \"emma.clarke@clarke-dental.example\",\n  \"company\": \"Clarke Dental (demo)\",\n  \"phone\": \"+44 20 0000 0000\",\n  \"message\": \"Hi, could you send me a quote for redesigning our clinic reception? Budget around €15,000. It's fairly urgent, we'd like to start this month.\"\n} } }];"
      },
      "id": "24fa7037-fe02-4500-aacd-098e83100dd1",
      "name": "Sample enquiry (demo data)",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        0,
        300
      ]
    },
    {
      "parameters": {
        "jsCode": "// Same logic as the interactive demo on pausolutions.com/demos/ (rules + templates, no AI).\nconst TEXT = {\n  \"locale\": \"en-GB\",\n  \"firm\": \"Brightwater Interiors (demo)\",\n  \"receivedAt\": \"2026-10-21 09:12\",\n  \"rules\": [\n    {\n      \"key\": \"quote\",\n      \"category\": \"Quote request\",\n      \"words\": [\n        \"quote\",\n        \"price\",\n        \"cost\",\n        \"estimate\",\n        \"how much\"\n      ],\n      \"points\": 30,\n      \"reason\": \"Asks for a quote\"\n    },\n    {\n      \"key\": \"booking\",\n      \"category\": \"Booking\",\n      \"words\": [\n        \"book\",\n        \"appointment\",\n        \"visit\",\n        \"available\"\n      ],\n      \"points\": 20,\n      \"reason\": \"Wants to book a visit\"\n    },\n    {\n      \"key\": \"support\",\n      \"category\": \"Support\",\n      \"words\": [\n        \"problem\",\n        \"not working\",\n        \"broken\",\n        \"error\",\n        \"complaint\"\n      ],\n      \"points\": 5,\n      \"reason\": \"Support request\"\n    }\n  ],\n  \"general\": \"General\",\n  \"urgentWords\": [\n    \"urgent\",\n    \"asap\",\n    \"as soon as possible\",\n    \"this week\",\n    \"today\"\n  ],\n  \"budgetWords\": [\n    \"budget\",\n    \"€\",\n    \"eur\",\n    \"euro\",\n    \"£\",\n    \"$\"\n  ],\n  \"freeMailDomains\": [\n    \"gmail.com\",\n    \"googlemail.com\",\n    \"outlook.com\",\n    \"hotmail.com\",\n    \"live.com\",\n    \"yahoo.com\",\n    \"icloud.com\",\n    \"aol.com\",\n    \"proton.me\",\n    \"libero.it\"\n  ],\n  \"points\": {\n    \"urgent\": 20,\n    \"budget\": 15,\n    \"company\": 10,\n    \"businessEmail\": 10,\n    \"phone\": 10,\n    \"detailed\": 10,\n    \"invalidEmail\": -20\n  },\n  \"reasons\": {\n    \"urgent\": \"Urgent wording\",\n    \"budget\": \"Mentions a budget\",\n    \"company\": \"Company name given\",\n    \"businessEmail\": \"Business email address\",\n    \"phone\": \"Phone number given\",\n    \"detailed\": \"Detailed message\",\n    \"invalidEmail\": \"Email address looks invalid\",\n    \"generalCategory\": \"No clear request type\"\n  },\n  \"detailedWords\": 15,\n  \"bands\": {\n    \"hot\": \"Hot\",\n    \"warm\": \"Warm\",\n    \"cold\": \"Cold\"\n  },\n  \"priorities\": {\n    \"high\": \"high\",\n    \"normal\": \"normal\"\n  },\n  \"ackSubject\": \"We received your enquiry\",\n  \"ackBodies\": {\n    \"quote\": \"Hi {first},\\n\\nthanks for getting in touch with Brightwater Interiors. We've received your quote request and will reply with next steps within one working day.\\n\\nThe Brightwater team (demo)\",\n    \"booking\": \"Hi {first},\\n\\nthanks for getting in touch with Brightwater Interiors. We'll confirm a time for your visit within one working day.\\n\\nThe Brightwater team (demo)\",\n    \"support\": \"Hi {first},\\n\\nthanks for letting us know. We're looking into it and will reply within one working day.\\n\\nThe Brightwater team (demo)\",\n    \"general\": \"Hi {first},\\n\\nthanks for getting in touch with Brightwater Interiors. We'll reply within one working day.\\n\\nThe Brightwater team (demo)\"\n  },\n  \"ackNoReply\": \"No automatic reply: the email address looks invalid, so the lead is flagged for a manual check.\",\n  \"ownerSubject\": \"New {band} lead ({score}/100): {category} — {name}\",\n  \"ownerBody\": \"{name} ({company}) — {email}, {phone}\\nCategory: {category} · Priority: {priority} · Score: {score}/100\\nWhy: {reasons}\\n\\nMessage:\\n{message}\",\n  \"rowStatus\": \"New\",\n  \"rowKeys\": {\n    \"date\": \"Date\",\n    \"name\": \"Name\",\n    \"email\": \"Email\",\n    \"company\": \"Company\",\n    \"category\": \"Category\",\n    \"score\": \"Score\",\n    \"priority\": \"Priority\",\n    \"status\": \"Status\"\n  }\n};\n\nfunction fill(tpl, vars) {\n  return tpl.replace(/\\{(\\w+)\\}/g, (m, k) => (k in vars ? String(vars[k]) : m));\n}\n\nfunction daysBetween(fromIso, toIso) {\n  return Math.round((Date.parse(toIso + \"T00:00:00Z\") - Date.parse(fromIso + \"T00:00:00Z\")) / 86400000);\n}\n\nfunction addDays(iso, days) {\n  return new Date(Date.parse(iso + \"T00:00:00Z\") + days * 86400000).toISOString().slice(0, 10);\n}\n\nfunction longDate(iso, locale) {\n  return new Intl.DateTimeFormat(locale, { day: \"numeric\", month: \"long\", year: \"numeric\", timeZone: \"UTC\" }).format(new Date(iso + \"T00:00:00Z\"));\n}\n\nfunction money(amount, locale) {\n  return new Intl.NumberFormat(locale, { style: \"currency\", currency: \"EUR\" }).format(amount);\n}\n\nfunction cleanText(value, max, multiline) {\n  const s = String(value ?? \"\").replace(multiline ? /[\\u0000-\\u0009\\u000B-\\u001F\\u007F]/g : /[\\u0000-\\u001F\\u007F]/g, \" \");\n  const collapsed = multiline ? s.replace(/[ \\t]+/g, \" \").replace(/\\n{3,}/g, \"\\n\\n\") : s.replace(/\\s+/g, \" \");\n  return collapsed.trim().slice(0, max);\n}\n\nfunction hasTerm(text, term, whole) {\n  if (!/^[\\p{L}\\p{N}]/u.test(term)) return text.includes(term);\n  const escaped = term.replace(/[.*+?^${}()|[\\]\\\\]/g, \"\\\\$&\");\n  return new RegExp(\"(^|[^\\\\p{L}\\\\p{N}])\" + escaped + (whole ? \"(?![\\\\p{L}\\\\p{N}])\" : \"\"), \"u\").test(text);\n}\n\nfunction scoreLead(raw, text) {\n  const name = cleanText(raw.name, 80, false);\n  const email = cleanText(raw.email, 120, false).toLowerCase();\n  const company = cleanText(raw.company, 100, false);\n  const phone = cleanText(raw.phone, 40, false);\n  const message = cleanText(raw.message, 1500, true);\n  const lower = message.toLowerCase();\n  const emailValid = /^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/.test(email);\n  const domain = email.split(\"@\").pop() || \"\";\n  const freeMail = text.freeMailDomains.some((d) => domain === d || domain.split(\".\")[0] === d.split(\".\")[0]);\n  const rule = text.rules.find((r) => r.words.some((w) => hasTerm(lower, w, false)));\n  const urgent = text.urgentWords.some((w) => hasTerm(lower, w, true));\n  const reasons = [];\n  if (rule) reasons.push({ label: rule.reason, points: rule.points });\n  else reasons.push({ label: text.reasons.generalCategory, points: 0 });\n  if (urgent) reasons.push({ label: text.reasons.urgent, points: text.points.urgent });\n  if (text.budgetWords.some((w) => hasTerm(lower, w, true))) reasons.push({ label: text.reasons.budget, points: text.points.budget });\n  if (company) reasons.push({ label: text.reasons.company, points: text.points.company });\n  if (emailValid && !freeMail) reasons.push({ label: text.reasons.businessEmail, points: text.points.businessEmail });\n  if (phone.replace(/\\D/g, \"\").length >= 6) reasons.push({ label: text.reasons.phone, points: text.points.phone });\n  if (message.split(/\\s+/).filter(Boolean).length >= text.detailedWords) reasons.push({ label: text.reasons.detailed, points: text.points.detailed });\n  if (!emailValid) reasons.push({ label: text.reasons.invalidEmail, points: text.points.invalidEmail });\n  const score = Math.max(0, Math.min(100, reasons.reduce((t, r) => t + r.points, 0)));\n  const band = score >= 60 ? text.bands.hot : score >= 30 ? text.bands.warm : text.bands.cold;\n  const priority = urgent || score >= 60 ? text.priorities.high : text.priorities.normal;\n  const categoryKey = rule ? rule.key : \"general\";\n  const category = rule ? rule.category : text.general;\n  const first = name.split(\" \")[0] || name;\n  const vars = { name: name || \"—\", first, email: email || \"—\", company: company || \"—\", phone: phone || \"—\", category, priority, score, band: band.toLowerCase(), message, reasons: reasons.map((r) => r.label + \" (\" + (r.points >= 0 ? \"+\" : \"\") + r.points + \")\").join(\", \") };\n  const k = text.rowKeys;\n  return {\n    name, email, company, phone, message, emailValid, categoryKey, category, urgent, score, band, priority, reasons,\n    ackSubject: emailValid ? text.ackSubject : null,\n    ackBody: emailValid ? fill(text.ackBodies[categoryKey], vars) : null,\n    ownerSubject: fill(text.ownerSubject, vars),\n    ownerBody: fill(text.ownerBody, vars),\n    row: { [k.date]: text.receivedAt, [k.name]: name, [k.email]: email, [k.company]: company, [k.category]: category, [k.score]: score, [k.priority]: priority, [k.status]: text.rowStatus },\n  };\n}\n\nreturn $input.all().map((item) => {\n  const b = item.json.body ?? item.json;\n  const input = { name: b.name, email: b.email, company: b.company, phone: b.phone, message: b.message };\n  return { json: scoreLead(input, TEXT) };\n});"
      },
      "id": "af4a149c-eb79-4c00-a17d-6d2561a2b584",
      "name": "Classify & score",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        240,
        100
      ]
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "strict",
            "version": 2
          },
          "conditions": [
            {
              "id": "30e6a27d-e11e-4d00-aed1-dbc4d9d60edb",
              "leftValue": "={{ String($json.emailValid) }}",
              "rightValue": "true",
              "operator": {
                "type": "string",
                "operation": "equals"
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "f10fb2a6-ecf4-4800-af38-cb1f7aa1d5aa",
      "name": "Only valid email addresses",
      "type": "n8n-nodes-base.filter",
      "typeVersion": 2.2,
      "position": [
        500,
        -60
      ],
      "notes": "No automatic reply when the email address looks invalid.",
      "notesInFlow": true
    },
    {
      "parameters": {
        "fromEmail": "hello@brightwater.example",
        "toEmail": "={{ $json.email }}",
        "subject": "={{ $json.ackSubject }}",
        "emailFormat": "text",
        "text": "={{ $json.ackBody }}",
        "options": {}
      },
      "id": "5d4185dd-9b0e-4200-a376-fc6479b2287b",
      "name": "Instant reply to the customer",
      "type": "n8n-nodes-base.emailSend",
      "typeVersion": 2.1,
      "position": [
        740,
        -60
      ],
      "disabled": true,
      "notes": "Disabled in the demo. Connect your own SMTP/Gmail/Outlook account, then enable.",
      "notesInFlow": true
    },
    {
      "parameters": {
        "fromEmail": "hello@brightwater.example",
        "toEmail": "hello@brightwater.example",
        "subject": "={{ $json.ownerSubject }}",
        "emailFormat": "text",
        "text": "={{ $json.ownerBody }}",
        "options": {}
      },
      "id": "20245060-e7b8-4200-ae13-29d9516884a0",
      "name": "Alert the owner",
      "type": "n8n-nodes-base.emailSend",
      "typeVersion": 2.1,
      "position": [
        500,
        120
      ],
      "disabled": true,
      "notes": "Disabled in the demo. Connect your own SMTP/Gmail/Outlook account, then enable.",
      "notesInFlow": true
    },
    {
      "parameters": {
        "jsCode": "// Row for your lead list (demo: returned as data; in production connect a\n// Google Sheets \"Append row\" or CRM node here).\nreturn $input.all().map((i) => ({ json: i.json.row }));"
      },
      "id": "91757df6-fe9f-4c00-af42-044fc3fbe3da",
      "name": "Row for the lead list",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        500,
        300
      ]
    }
  ],
  "pinData": {
    "Website form (webhook)": [
      {
        "json": {
          "body": {
            "name": "Emma Clarke",
            "email": "emma.clarke@clarke-dental.example",
            "company": "Clarke Dental (demo)",
            "phone": "+44 20 0000 0000",
            "message": "Hi, could you send me a quote for redesigning our clinic reception? Budget around €15,000. It's fairly urgent, we'd like to start this month."
          }
        }
      }
    ]
  },
  "connections": {
    "Test run": {
      "main": [
        [
          {
            "node": "Sample enquiry (demo data)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Sample enquiry (demo data)": {
      "main": [
        [
          {
            "node": "Classify & score",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Website form (webhook)": {
      "main": [
        [
          {
            "node": "Classify & score",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Classify & score": {
      "main": [
        [
          {
            "node": "Only valid email addresses",
            "type": "main",
            "index": 0
          },
          {
            "node": "Alert the owner",
            "type": "main",
            "index": 0
          },
          {
            "node": "Row for the lead list",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Only valid email addresses": {
      "main": [
        [
          {
            "node": "Instant reply to the customer",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "active": false,
  "settings": {
    "executionOrder": "v1"
  },
  "versionId": "ba172833-b8f7-4800-a420-518a45c91cf5",
  "meta": {
    "templateCredsSetupCompleted": false
  },
  "tags": [],
  "_demo": {
    "by": "PauSolutions",
    "note": "DEMO with fictional data. No credentials included. Email nodes are disabled until you connect your own account.",
    "tags": [
      "demo",
      "leads"
    ]
  }
}
