{
"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"
}
}