This workflow follows the Emailsend → HTTP Request recipe pattern — see all workflows that pair these two integrations.
The workflow JSON
Copy or download the full n8n JSON below. Paste it into a new n8n workflow, add your credentials, activate. Full import guide →
{
"nodes": [
{
"parameters": {
"httpMethod": "POST",
"path": "webhook/lead-capture",
"responseMode": "responseNode",
"options": {}
},
"id": "0871978b-9eb6-460d-825a-18b60062bb59",
"name": "Lead Capture Webhook",
"type": "n8n-nodes-base.webhook",
"typeVersion": 1,
"position": [
-1008,
128
]
},
{
"parameters": {
"jsCode": "// Extract and normalize lead data from webhook\nconst webhookData = $input.first().json;\nconsole.log('Raw webhook data:', JSON.stringify(webhookData, null, 2));\n\n// Handle different possible data structures\nlet leadData;\nif (webhookData.body) {\n // Data is in body property (common with webhooks)\n leadData = webhookData.body;\n} else if (webhookData.lead_id || webhookData.id) {\n // Data is directly in the root\n leadData = webhookData;\n} else {\n // Fallback - use webhook data as is\n leadData = webhookData;\n}\n\nconsole.log('Extracted lead data:', JSON.stringify(leadData, null, 2));\n\n// Ensure we have all required fields with fallbacks\nconst normalizedLead = {\n lead_id: leadData.lead_id || leadData.id || 'unknown-' + Date.now(),\n name: leadData.name || 'Unknown Lead',\n email: leadData.email || 'unknown@example.com',\n company: leadData.company || '',\n website: leadData.website || '',\n problem_text: leadData.problem_text || 'No problem description provided'\n};\n\nconsole.log('Normalized lead data:', JSON.stringify(normalizedLead, null, 2));\n\nreturn {\n json: normalizedLead\n};"
},
"id": "f0394eac-37fa-4452-bbfb-3d39aec7bd2d",
"name": "Extract Lead Data",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
-784,
128
]
},
{
"parameters": {
"operation": "executeQuery",
"query": "SELECT openai_api_key FROM settings ORDER BY created_at DESC LIMIT 1",
"options": {}
},
"id": "fetch-openai-key",
"name": "Fetch OpenAI Key",
"type": "n8n-nodes-base.postgres",
"typeVersion": 2,
"position": [
-784,
224
],
"credentials": {
"postgres": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"jsCode": "// Merge the original lead data with the OpenAI key from database\nconst originalData = $('Extract Lead Data').item.json;\nconst openaiData = $input.first().json;\n\nreturn [{\n json: {\n ...originalData,\n openai_api_key: openaiData.openai_api_key\n }\n}];"
},
"id": "merge-openai-data-lead",
"name": "Merge Data for OpenAI",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
-576,
224
]
},
{
"parameters": {
"resource": "text",
"operation": "complete",
"model": "gpt-3.5-turbo-instruct",
"prompt": "=Analyze this lead and provide a JSON response with the following structure:\n\nLead Information:\n- Name: {{ $json.name }}\n- Email: {{ $json.email }}\n- Company: {{ $json.company }}\n- Problem: {{ $json.problem_text }}\n\nPlease analyze this lead and return ONLY a JSON object with:\n{\n \"use_case_label\": \"Marketing Automation\" | \"Sales CRM\" | \"Customer Support\" | \"Analytics\" | \"Other\",\n \"fit_score\": 85,\n \"fit_band\": \"High\" | \"Medium\" | \"Low\",\n \"rationale\": \"Brief explanation of why this score was assigned\"\n}\n\nConsider:\n- How well does their problem match our solutions?\n- Company size and industry relevance\n- Urgency and budget indicators\n- Quality of the inquiry\n\nReturn ONLY the JSON, no other text.",
"options": {
"maxTokens": 300,
"temperature": 0.3
},
"requestOptions": {},
"apiKey": "={{ $json.openai_api_key }}"
},
"id": "f33b8706-bbb7-4a42-b4cc-dad86a88d10d",
"name": "AI Lead Scoring",
"type": "n8n-nodes-base.openAi",
"typeVersion": 1,
"position": [
-576,
128
],
"credentials": {
"openAiApi": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"jsCode": "// Parse AI response and extract JSON\nconst aiInput = $input.first().json;\nconst leadData = $('Extract Lead Data').item.json;\n\nconsole.log('AI Input:', JSON.stringify(aiInput, null, 2));\nconsole.log('Lead Data:', JSON.stringify(leadData, null, 2));\n\n// Get AI response text - handle different response structures\nlet aiResponse;\nif (aiInput.choices && aiInput.choices[0] && aiInput.choices[0].message) {\n aiResponse = aiInput.choices[0].message.content;\n} else if (aiInput.text) {\n aiResponse = aiInput.text;\n} else {\n aiResponse = 'No valid response found';\n}\n\nconsole.log('AI Response:', aiResponse);\n\n// Try to extract JSON from the response\nlet parsedData;\ntry {\n // Look for JSON in the response\n const jsonMatch = aiResponse.match(/\\{[\\s\\S]*\\}/);\n if (jsonMatch) {\n parsedData = JSON.parse(jsonMatch[0]);\n console.log('Parsed JSON:', parsedData);\n } else {\n // Fallback if no JSON found - use simple analysis\n parsedData = {\n use_case_label: 'Other',\n fit_score: 50,\n fit_band: 'Medium',\n rationale: 'AI analysis completed - no JSON found in response'\n };\n }\n} catch (error) {\n console.log('Error parsing JSON:', error);\n // Fallback on error\n parsedData = {\n use_case_label: 'Other',\n fit_score: 50,\n fit_band: 'Medium',\n rationale: 'AI analysis completed - parsing error'\n };\n}\n\n// Combine lead data with AI analysis\nconst result = {\n ...leadData,\n ...parsedData,\n original_response: aiResponse\n};\n\nconsole.log('Final result:', JSON.stringify(result, null, 2));\n\nreturn {\n json: result\n};"
},
"id": "f3989ef0-63cd-46ac-b88b-967e1e2f7740",
"name": "Parse AI Response",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
-352,
128
]
},
{
"parameters": {
"operation": "executeQuery",
"query": "INSERT INTO leads (id, name, email, company, website, problem_text, use_case_label, fit_score, fit_band, ai_rationale, status, created_at) VALUES ('{{ $json.lead_id }}', '{{ $json.name }}', '{{ $json.email }}', '{{ $json.company }}', '{{ $json.website }}', '{{ $json.problem_text }}', '{{ $json.use_case_label }}', {{ $json.fit_score }}, '{{ $json.fit_band }}', '{{ $json.rationale.replace(/'/g, \"''\") }}', 'scored', NOW()) ON CONFLICT (id) DO UPDATE SET use_case_label = EXCLUDED.use_case_label, fit_score = EXCLUDED.fit_score, fit_band = EXCLUDED.fit_band, ai_rationale = EXCLUDED.ai_rationale, status = 'scored', updated_at = NOW()",
"options": {}
},
"id": "6e97ed04-a189-492b-bc9e-542195bc364c",
"name": "Upsert Lead to DB",
"type": "n8n-nodes-base.postgres",
"typeVersion": 2,
"position": [
-128,
128
],
"credentials": {
"postgres": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"operation": "executeQuery",
"query": "INSERT INTO events (lead_id, event_type, event_data, created_at) VALUES ('{{ $json.lead_id }}', 'lead_scored', '{{ JSON.stringify({use_case_label: $json.use_case_label, fit_score: $json.fit_score, fit_band: $json.fit_band, rationale: $json.rationale, timestamp: new Date().toISOString()}).replace(/'/g, \"''\") }}', NOW())",
"options": {}
},
"id": "8cd8a253-302c-4cc3-9099-d10088d50062",
"name": "Log Lead Scored Event",
"type": "n8n-nodes-base.postgres",
"typeVersion": 2,
"position": [
-128,
224
],
"credentials": {
"postgres": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"operation": "executeQuery",
"query": "SELECT slack_webhook FROM settings ORDER BY created_at DESC LIMIT 1",
"options": {}
},
"id": "fetch-slack-webhook",
"name": "Fetch Slack Webhook",
"type": "n8n-nodes-base.postgres",
"typeVersion": 2,
"position": [
96,
16
],
"credentials": {
"postgres": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"jsCode": "// Merge the original lead data with the Slack webhook from database\nconst originalData = $('Parse AI Response').item.json;\nconst slackData = $input.first().json;\n\nconsole.log('Slack webhook from database:', slackData.slack_webhook);\n\n// Check if Slack webhook exists\nif (!slackData.slack_webhook || slackData.slack_webhook.trim() === '') {\n console.log('No Slack webhook configured, skipping Slack notification');\n return [];\n}\n\nreturn [{\n json: {\n ...originalData,\n slack_webhook: slackData.slack_webhook\n }\n}];"
},
"id": "merge-slack-data-lead",
"name": "Merge Data for Slack",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
192,
16
]
},
{
"parameters": {
"url": "={{ $json.slack_webhook }}",
"method": "POST",
"sendBody": true,
"specifyBody": "json",
"jsonBody": "={\n \"text\": \"\ud83c\udfaf New Lead: {{ $json.name }}\",\n \"blocks\": [\n {\n \"type\": \"header\",\n \"text\": {\n \"type\": \"plain_text\",\n \"text\": \"\ud83c\udfaf New Lead: {{ $json.name }}\"\n }\n },\n {\n \"type\": \"section\",\n \"fields\": [\n {\n \"type\": \"mrkdwn\",\n \"text\": \"*Email:* {{ $json.email }}\"\n },\n {\n \"type\": \"mrkdwn\",\n \"text\": \"*Company:* {{ $json.company || 'Not provided' }}\"\n },\n {\n \"type\": \"mrkdwn\",\n \"text\": \"*Score:* {{ $json.fit_score }}/100 ({{ $json.fit_band }})\"\n },\n {\n \"type\": \"mrkdwn\",\n \"text\": \"*Use Case:* {{ $json.use_case_label }}\"\n }\n ]\n },\n {\n \"type\": \"section\",\n \"text\": {\n \"type\": \"mrkdwn\",\n \"text\": \"*Message:* {{ $json.problem_text.replace(/\"/g, '\\\\\"').replace(/\\n/g, '\\\\n') }}\"\n }\n },\n {\n \"type\": \"section\",\n \"text\": {\n \"type\": \"mrkdwn\",\n \"text\": \"*AI Rationale:* {{ $json.rationale.replace(/\"/g, '\\\\\"').replace(/\\n/g, '\\\\n') }}\"\n }\n },\n {\n \"type\": \"actions\",\n \"elements\": [\n {\n \"type\": \"button\",\n \"text\": {\n \"type\": \"plain_text\",\n \"text\": \"View in Dashboard\"\n },\n \"url\": \"{{ $env.FRONTEND_URL || 'http://localhost:5173' }}/dashboard\",\n \"style\": \"primary\"\n }\n ]\n }\n ]\n}",
"options": {}
},
"id": "d0cc030f-c187-4e0f-9398-a2ebe525ba0a",
"name": "Send Slack Notification",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4,
"position": [
320,
16
]
},
{
"parameters": {
"fromEmail": "={{ $env.SMTP_FROM || 'noreply@pulsecapture.com' }}",
"toEmail": "admin@leadcapture.com",
"subject": "New Lead: {{ $json.name }} ({{ $json.fit_band }} Priority)",
"options": {}
},
"id": "a26b5ed6-64c6-4e9d-9b34-3031fa005294",
"name": "Send Email Notification",
"type": "n8n-nodes-base.emailSend",
"typeVersion": 2,
"position": [
544,
224
],
"credentials": {
"smtp": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"respondWith": "json",
"responseBody": "={\n \"success\": true,\n \"message\": \"Lead processed successfully\",\n \"lead_id\": \"{{ $json.lead_id }}\",\n \"fit_score\": {{ $json.fit_score || 50 }},\n \"fit_band\": \"{{ $json.fit_band || 'Medium' }}\"\n}",
"options": {}
},
"id": "ec068781-9b6a-4451-8df0-cc7c410c935d",
"name": "Webhook Response",
"type": "n8n-nodes-base.respondToWebhook",
"typeVersion": 1,
"position": [
752,
128
]
}
],
"connections": {
"Lead Capture Webhook": {
"main": [
[
{
"node": "Extract Lead Data",
"type": "main",
"index": 0
}
]
]
},
"Extract Lead Data": {
"main": [
[
{
"node": "Fetch OpenAI Key",
"type": "main",
"index": 0
}
]
]
},
"Fetch OpenAI Key": {
"main": [
[
{
"node": "Merge Data for OpenAI",
"type": "main",
"index": 0
}
]
]
},
"Merge Data for OpenAI": {
"main": [
[
{
"node": "AI Lead Scoring",
"type": "main",
"index": 0
}
]
]
},
"AI Lead Scoring": {
"main": [
[
{
"node": "Parse AI Response",
"type": "main",
"index": 0
}
]
]
},
"Parse AI Response": {
"main": [
[
{
"node": "Upsert Lead to DB",
"type": "main",
"index": 0
}
]
]
},
"Upsert Lead to DB": {
"main": [
[
{
"node": "Log Lead Scored Event",
"type": "main",
"index": 0
},
{
"node": "Fetch Slack Webhook",
"type": "main",
"index": 0
}
]
]
},
"Fetch Slack Webhook": {
"main": [
[
{
"node": "Merge Data for Slack",
"type": "main",
"index": 0
}
]
]
},
"Merge Data for Slack": {
"main": [
[
{
"node": "Send Slack Notification",
"type": "main",
"index": 0
}
]
]
},
"Send Slack Notification": {
"main": [
[
{
"node": "Send Email Notification",
"type": "main",
"index": 0
}
]
]
},
"Send Email Notification": {
"main": [
[
{
"node": "Webhook Response",
"type": "main",
"index": 0
}
]
]
}
},
"meta": {
"templateCredsSetupCompleted": true
}
}
Credentials you'll need
Each integration node will prompt for credentials when you import. We strip credential IDs before publishing — you'll add your own.
openAiApipostgressmtp
For the full experience including quality scoring and batch install features for each workflow upgrade to Pro
About this workflow
On-New-Lead. Uses postgres, openAi, httpRequest, emailSend. Webhook trigger; 13 nodes.
Source: https://github.com/Japjeet07/pulse-capture-app/blob/7d65fa37ee5bb1a0eed04c150983c53839734a55/n8n-workflows/on-new-lead.json — original creator credit. Request a take-down →
Related workflows
Workflows that share integrations, category, or trigger type with this one. All free to copy and import.
Send-Outreach. Uses postgres, openAi, httpRequest, emailSend. Webhook trigger; 15 nodes.
AI Resume Screening Workflow. Uses openAi, emailSend, httpRequest, postgres. Webhook trigger; 14 nodes.
Eu Clara – Funil Kiwify Completo. Uses postgres, openAi, httpRequest, gmail. Webhook trigger; 70 nodes.
Lua Nova - Sistema Completo. Uses postgres, httpRequest, openAi. Webhook trigger; 55 nodes.
User Signup & Verification: The workflow starts when a user signs up. It generates a verification code and sends it via SMS using Twilio. Code Validation: The user replies with the code. The workflow