Content is user-generated and unverified.
{ "name": "Lead Capture - Save, Notify & Follow-Up", "nodes": [ { "parameters": { "httpMethod": "POST", "path": "new-lead", "responseMode": "responseNode", "options": {} }, "id": "webhook-trigger", "name": "Webhook - New Lead", "type": "n8n-nodes-base.webhook", "typeVersion": 2, "position": [200, 300] }, { "parameters": { "jsCode": "// Validate and normalise incoming lead data\n// Webhook body is under $json.body per n8n-skills expression guide\nconst body = $json.body || $json;\n\nconst name = (body.name || '').trim();\nconst email = (body.email || '').trim().toLowerCase();\nconst phone = (body.phone || '').trim();\nconst company = (body.company || '').trim();\nconst source = (body.source || 'webhook').trim();\n\n// Basic email validation\nconst emailRegex = /^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/;\nif (!email || !emailRegex.test(email)) {\n throw new Error('Invalid or missing email: ' + email);\n}\nif (!name) {\n throw new Error('Missing required field: name');\n}\n\nconst now = new Date();\n\nreturn [{\n json: {\n name,\n email,\n phone,\n company,\n source,\n submittedAt: now.toISOString(),\n dateOnly: now.toISOString().split('T')[0],\n followUpDue: new Date(now.getTime() + 48 * 60 * 60 * 1000).toISOString().split('T')[0]\n }\n}];" }, "id": "code-validate", "name": "Code - Validate & Normalise", "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [420, 300] }, { "parameters": { "operation": "append", "documentId": { "__rl": true, "value": "YOUR_GOOGLE_SHEET_ID", "mode": "id" }, "sheetName": { "__rl": true, "value": "Leads", "mode": "name" }, "columns": { "mappingMode": "defineBelow", "value": { "Name": "={{ $json.name }}", "Email": "={{ $json.email }}", "Phone": "={{ $json.phone }}", "Company": "={{ $json.company }}", "Source": "={{ $json.source }}", "Submitted At": "={{ $json.submittedAt }}", "Booked Call": "No", "Follow Up Due": "={{ $json.followUpDue }}" } }, "options": { "skipEmptyLines": true } }, "id": "sheets-append", "name": "Google Sheets - Save Lead", "type": "n8n-nodes-base.googleSheets", "typeVersion": 4, "position": [640, 200] }, { "parameters": { "fromEmail": "you@yourdomain.com", "toEmail": "={{ $json.email }}", "subject": "Thanks for reaching out, {{ $json.name }}!", "emailType": "html", "message": "<p>Hi {{ $json.name }},</p>\n<p>Thanks for getting in touch! I've received your details and will follow up within 24 hours.</p>\n<p>If you'd like to skip the wait, you can <a href=\"https://cal.com/YOUR_LINK\">book a call directly here</a>.</p>\n<p>Talk soon,<br/>Your Name</p>" }, "id": "email-lead-confirm", "name": "Email - Confirmation to Lead", "type": "n8n-nodes-base.emailSend", "typeVersion": 2, "position": [640, 360] }, { "parameters": { "authentication": "oAuth2", "channel": "#new-leads", "text": ":rocket: *New Lead Captured!*\n\n*Name:* {{ $json.name }}\n*Email:* {{ $json.email }}\n*Company:* {{ $json.company || 'N/A' }}\n*Phone:* {{ $json.phone || 'N/A' }}\n*Source:* {{ $json.source }}\n*Time:* {{ $json.submittedAt }}\n\n_Reply-to follow up, or set Booked Call = Yes in Sheets._", "otherOptions": {} }, "id": "slack-notify", "name": "Slack - Notify Me", "type": "n8n-nodes-base.slack", "typeVersion": 2, "position": [640, 520] }, { "parameters": { "unit": "days", "amount": 2 }, "id": "wait-48h", "name": "Wait - 48 Hours", "type": "n8n-nodes-base.wait", "typeVersion": 1, "position": [860, 300], "webhookId": "wait-48h-resume" }, { "parameters": { "operation": "lookup", "documentId": { "__rl": true, "value": "YOUR_GOOGLE_SHEET_ID", "mode": "id" }, "sheetName": { "__rl": true, "value": "Leads", "mode": "name" }, "lookupColumn": "Email", "lookupValue": "={{ $json.email }}", "options": {} }, "id": "sheets-check-booked", "name": "Google Sheets - Check Booked Call", "type": "n8n-nodes-base.googleSheets", "typeVersion": 4, "position": [1080, 300] }, { "parameters": { "conditions": { "options": { "caseSensitive": false }, "conditions": [ { "id": "check-booked", "leftValue": "={{ $json['Booked Call'] }}", "rightValue": "Yes", "operator": { "type": "string", "operation": "notEquals" } } ], "combinator": "and" } }, "id": "if-not-booked", "name": "IF - Not Booked Yet?", "type": "n8n-nodes-base.if", "typeVersion": 2, "position": [1300, 300] }, { "parameters": { "fromEmail": "you@yourdomain.com", "toEmail": "={{ $json.Email }}", "subject": "Still thinking it over, {{ $json.Name }}?", "emailType": "html", "message": "<p>Hi {{ $json.Name }},</p>\n<p>Just circling back - did you get a chance to look at the calendar link?</p>\n<p>A quick 15-min call could be a great starting point. <a href=\"https://cal.com/YOUR_LINK\">Book here when you're ready.</a></p>\n<p>No pressure - happy to answer questions over email too.</p>\n<p>Cheers,<br/>Your Name</p>" }, "id": "email-followup", "name": "Email - Follow-Up to Lead", "type": "n8n-nodes-base.emailSend", "typeVersion": 2, "position": [1520, 200] }, { "parameters": { "authentication": "oAuth2", "channel": "#new-leads", "text": ":memo: *Follow-up sent* to {{ $json.Name }} ({{ $json.Email }}) - they haven't booked a call yet.", "otherOptions": {} }, "id": "slack-followup-log", "name": "Slack - Log Follow-Up Sent", "type": "n8n-nodes-base.slack", "typeVersion": 2, "position": [1520, 360] }, { "parameters": { "respondWith": "json", "responseBody": "={ \"status\": \"ok\", \"message\": \"Lead received, thank you!\" }" }, "id": "respond-webhook", "name": "Respond to Webhook", "type": "n8n-nodes-base.respondToWebhook", "typeVersion": 1, "position": [860, 500] }, { "parameters": { "operation": "append", "documentId": { "__rl": true, "value": "YOUR_GOOGLE_SHEET_ID", "mode": "id" }, "sheetName": { "__rl": true, "value": "Error Log", "mode": "name" }, "columns": { "mappingMode": "defineBelow", "value": { "Timestamp": "={{ $now.toISO() }}", "Error": "={{ $json.message || 'Unknown error' }}", "Node": "={{ $execution.id }}", "Input Data": "={{ JSON.stringify($input.all()) }}" } }, "options": {} }, "id": "sheets-error-log", "name": "Google Sheets - Error Log", "type": "n8n-nodes-base.googleSheets", "typeVersion": 4, "position": [640, 680] }, { "parameters": { "authentication": "oAuth2", "channel": "#alerts", "text": ":red_circle: *Workflow Error - Lead Capture*\n\n*Error:* {{ $json.message || 'Unknown error' }}\n*Time:* {{ $now.toISO() }}\n*Execution:* {{ $execution.id }}\n\nCheck the Error Log sheet for details.", "otherOptions": {} }, "id": "slack-error-notify", "name": "Slack - Error Alert", "type": "n8n-nodes-base.slack", "typeVersion": 2, "position": [860, 680] } ], "connections": { "Webhook - New Lead": { "main": [ [ { "node": "Code - Validate & Normalise", "type": "main", "index": 0 } ] ] }, "Code - Validate & Normalise": { "main": [ [ { "node": "Google Sheets - Save Lead", "type": "main", "index": 0 }, { "node": "Email - Confirmation to Lead", "type": "main", "index": 0 }, { "node": "Slack - Notify Me", "type": "main", "index": 0 } ] ] }, "Google Sheets - Save Lead": { "main": [ [ { "node": "Wait - 48 Hours", "type": "main", "index": 0 } ] ] }, "Email - Confirmation to Lead": { "main": [ [ { "node": "Respond to Webhook", "type": "main", "index": 0 } ] ] }, "Wait - 48 Hours": { "main": [ [ { "node": "Google Sheets - Check Booked Call", "type": "main", "index": 0 } ] ] }, "Google Sheets - Check Booked Call": { "main": [ [ { "node": "IF - Not Booked Yet?", "type": "main", "index": 0 } ] ] }, "IF - Not Booked Yet?": { "main": [ [ { "node": "Email - Follow-Up to Lead", "type": "main", "index": 0 }, { "node": "Slack - Log Follow-Up Sent", "type": "main", "index": 0 } ], [] ] } }, "settings": { "executionOrder": "v1", "saveManualExecutions": true, "callerPolicy": "workflowsFromSameOwner", "errorWorkflow": "", "saveDataSuccessExecution": "all", "saveDataErrorExecution": "all", "executionTimeout": 3600 }, "staticData": null, "tags": ["lead-capture", "crm", "beginner"], "pinData": {}, "meta": { "templateCredsSetupCompleted": false, "n8n_version": "1.0.0" } }
Content is user-generated and unverified.