Real Estate AI Lead Qualifier + Voice Agent + Proposal Generator (GPT-4o + VAPI + Gmail + Sheets)
Sharing a workflow I built and debugged this week — an end-to-end pipeline that takes an inbound real estate lead, has GPT-4o read their message and qualify them as Hot/Warm/Cold, then branches into different automated follow-up actions depending on tier, including an optional AI voice call for hot leads.
What it does
A lead submits an inquiry through any webhook-capable form (website contact form, WhatsApp integration, whatever you’ve got) with just a name, email, phone, and a free-text message describing what they want.
GPT-4o reads that message and extracts everything itself — budget, timeline, location, property type, intent — no rigid structured fields required. It scores the lead 1–10 and assigns a tier:
- Hot (8–10): clear budget, fast timeline, specific property — ready to commit
- Warm (5–7): some interest, vague on specifics or further-out timeline
- Cold (1–4): just browsing, little detail
Then it branches:
- Hot → (optional) VAPI places an outbound AI voice call to the lead → waits for the call → pulls the transcript → GPT-4o generates a personalized proposal → formats as HTML → emails it → logs to Sheets
- Warm → automated nurture email → logs to Sheets
- Cold → no email (not worth it) → logs to Sheets for future reference
The bug I hit (and the actual reason I’m posting this)
The template I started from expected the webhook to send pre-structured fields (budget, timeline, location, property_type as separate keys). In practice, almost no real lead source sends data that cleanly — most contact forms and WhatsApp messages come in as one freeform paragraph.
The AI qualifier was happily running, returning valid JSON, scoring everything Cold with reasoning like “no budget specified, no timeline provided” — even when I tested it with a message that clearly stated a budget, timeline, and location. Took a while to realize the prompt was referencing fields that simply didn’t exist in the payload ($json.budget, $json.timeline, etc.), so the AI was correctly looking at undefined and concluding, also correctly, that nothing was specified.
Fix was rewriting both AI prompts (qualifier and VAPI voice agent) to extract those details from the natural-language message field directly, instead of assuming they’d arrive pre-parsed. Worth flagging in case anyone else builds a similar lead-intake flow and assumes structured form data when your actual lead source is freeform text — the failure mode is silent and looks like the AI being wrong, when it’s actually a data-shape mismatch.
Setup
You’ll need your own credentials — none are included in the export:
| Node(s) | Credential |
|---|---|
| AI Lead Qualifier, Generate Proposal | OpenAI API key as Bearer Auth (these are HTTP Request nodes, not the dedicated OpenAI node — paste your raw sk-... key) |
| Send Proposal Email, Send Warm Follow-up Email | Gmail OAuth2 |
| Log Hot/Warm/Cold Lead to Sheets | Google Sheets OAuth2 |
| VAPI: Start Voice Call (optional) | VAPI API key + phoneNumberId from your VAPI dashboard |
Google Sheet needs these headers in row 1:
lead_name | lead_email | lead_phone | qualification | proposal_html | status | logged_at
Testing status (being upfront about this)
Warm and Cold paths are tested end-to-end with real data — confirmed working, including the full qualification → routing → email/logging chain.
The Hot path past VAPI (voice call → transcript → proposal generation → email) is fully built and reviewed but I haven’t connected a VAPI account myself yet, so it’s untested end-to-end on my side. If anyone runs it before me, genuinely curious how it goes.
Known limitations
- No retry/backoff on API calls
- No dedupe — duplicate submissions create duplicate rows
- Cold leads aren’t re-engaged after logging
Parse Qualification’s code references the webhook node by name (New Lead Webhook) to recover lead fields — rename that node and you’ll need to update the reference
JSON attached below. Happy to answer questions or take feedback — this is V1, not claiming it’s perfect.
{
"name": "Real Estate AI Pipeline: Lead Qualifier + Voice Agent + Proposal Generator",
"nodes": \[
{
"parameters": {
"httpMethod": "POST",
"path": "new-lead",
"responseMode": "responseNode",
"options": {}
},
"id": "30bd4f4e-1ae6-4abc-b8cb-2101d1014acc",
"name": "New Lead Webhook",
"type": "n8n-nodes-base.webhook",
"typeVersion": 1,
"position": \[
-768,
48
\],
"webhookId": "062ea3b3-991f-4872-8c99-9c5756550063"
},
{
"parameters": {
"values": {
"string": \[
{
"name": "lead_name",
"value": "={{ $json.body.name }}"
},
{
"name": "lead_email",
"value": "={{ $json.body.email }}"
},
{
"name": "lead_phone",
"value": "={{ $json.body.phone }}"
},
{
"name": "budget",
"value": "={{ $json.body.budget }}"
},
{
"name": "intent",
"value": "={{ $json.body.intent }}"
},
{
"name": "timeline",
"value": "={{ $json.body.timeline }}"
},
{
"name": "location",
"value": "={{ $json.body.location }}"
},
{
"name": "property_type",
"value": "={{ $json.body.property_type }}"
},
{
"name": "notes",
"value": "={{ $json.body.notes ?? '' }}"
}
\]
},
"options": {}
},
"id": "e40105eb-4248-4ae5-a9a8-d869c8fe839a",
"name": "Format Lead Data",
"type": "n8n-nodes-base.set",
"typeVersion": 1,
"position": \[
-544,
48
\]
},
{
"parameters": {
"respondWith": "json",
"responseBody": "{ "status": "success", "message": "Lead received and being processed" }",
"options": {}
},
"id": "f83d66a0-b6b7-41f8-88e4-14d6dba15702",
"name": "Webhook Response",
"type": "n8n-nodes-base.respondToWebhook",
"typeVersion": 1,
"position": \[
-544,
240
\]
},
{
"parameters": {
"method": "POST",
"url": "https://api.openai.com/v1/chat/completions",
"authentication": "genericCredentialType",
"genericAuthType": "httpBearerAuth",
"sendBody": true,
"specifyBody": "json",
"jsonBody": "={{ JSON.stringify({\\n model: 'gpt-4o',\\n temperature: 0.3,\\n response_format: { type: 'json_object' },\\n messages: \[\\n {\\n role: 'system',\\n content: 'You are a real estate lead qualification expert. You will receive a lead\\'s name, contact info, and a free-text message describing what they want. Extract budget, timeline, location, property type, and intent (buy/sell/rent) from the message yourself, then qualify the lead. Return ONLY a JSON object with these fields: tier (exactly Hot, Warm, or Cold), score (integer 1-10), reason (max 2 sentences), key_strengths (array of 2-3 strings), concerns (array of strings, empty array if none). Scoring: Hot=8-10 (budget clearly stated and substantial, timeline under 3 months, specific location and property type, ready to commit). Warm=5-7 (moderate or unclear budget, 3-6 month timeline, somewhat specific). Cold=1-4 (no budget mentioned, vague or no timeline, just browsing, very little detail).'\\n },\\n {\\n role: 'user',\\n content: 'Qualify this lead:\\nName: ' + $json.lead_name + '\\nEmail: ' + $json.lead_email + '\\nPhone: ' + $json.lead_phone + '\\nMessage: ' + $json.message\\n }\\n \]\\n}) }}",
"options": {}
},
"id": "8e155f8e-ee51-4701-9a84-36c3aa0cb55b",
"name": "AI Lead Qualifier (OpenAI)",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.1,
"position": \[
-320,
48
\],
"credentials": {
"httpBearerAuth": {
"id": "YOUR_CREDENTIAL_ID",
"name": "YOUR_HTTPBEARERAUTH_CREDENTIAL"
}
}
},
{
"parameters": {
"jsCode": "// Parse OpenAI qualification response and merge with lead data\\nconst response = $input.first().json;\\nconst content = response.choices\[0\].message.content;\\n\\n// Get the original lead data from the webhook, further upstream\\nconst leadData = $('New Lead Webhook').first().json.body || $('New Lead Webhook').first().json;\\n\\nlet qualification;\\ntry {\\n qualification = JSON.parse(content);\\n} catch(e) {\\n qualification = {\\n tier: 'Warm',\\n score: 5,\\n reason: 'Could not parse AI response. Defaulted to Warm for manual review.',\\n key_strengths: \['Submitted inquiry form'\],\\n concerns: \['AI parse error - manual review recommended'\]\\n };\\n}\\n\\nreturn {\\n lead_name: leadData.lead_name,\\n lead_email: leadData.lead_email,\\n lead_phone: leadData.lead_phone,\\n message: leadData.message,\\n qualification_tier: qualification.tier,\\n qualification_score: qualification.score,\\n qualification_reason: qualification.reason,\\n key_strengths: qualification.key_strengths,\\n concerns: qualification.concerns,\\n qualified_at: new Date().toISOString(),\\n call_status: 'Not Called',\\n call_transcript: 'N/A',\\n proposal_sent: 'No',\\n pipeline_status: qualification.tier + ' - Logged Only'\\n};"
},
"id": "f7765730-edd5-4935-bce6-33e55c0834bf",
"name": "Parse Qualification",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": \[
-112,
48
\]
},
{
"parameters": {
"conditions": {
"string": \[
{
"value1": "={{ $json.qualification_tier }}",
"value2": "Hot"
}
\]
}
},
"id": "c5f4763b-9e77-4a84-93ab-e4a323b76666",
"name": "Is Hot Lead?",
"type": "n8n-nodes-base.if",
"typeVersion": 1,
"position": \[
128,
48
\]
},
{
"parameters": {
"method": "POST",
"url": "https://api.vapi.ai/call/phone",
"authentication": "genericCredentialType",
"genericAuthType": "httpBearerAuth",
"sendBody": true,
"specifyBody": "json",
"jsonBody": "={{ JSON.stringify({\\n assistant: {\\n model: {\\n provider: 'openai',\\n model: 'gpt-4o',\\n messages: \[\\n {\\n role: 'system',\\n content: 'You are Alex, a friendly and professional real estate consultant at Prime Properties. You are calling ' + $json.lead_name + ' who submitted this inquiry: "' + $json.message + '". Our AI qualification assessed them as a ' + $json.qualification_tier + ' lead (reason: ' + $json.qualification_reason + '). Your objectives: (1) Warm greeting and confirm you are speaking to the right person. (2) Confirm their property interest based on what they wrote in. (3) Ask: What are your must-have features? (4) Ask: Have you been pre-approved for financing or are you a cash buyer? (5) Ask: Are you working with any other agents? (6) Let them know you will send a personalized property proposal by email within the hour. (7) End the call professionally. Stay conversational, friendly, and under 3 minutes. If they do not answer, leave a brief voicemail.'\\n }\\n \],\\n temperature: 0.7\\n },\\n voice: {\\n provider: '11labs',\\n voiceId: '21m00Tcm4TlvDq8ikWAM'\\n },\\n firstMessage: 'Hi, could I please speak with ' + $json.lead_name + '? This is Alex calling from Prime Properties about your recent property inquiry. Do you have a couple of minutes to chat?',\\n endCallFunctionEnabled: true,\\n serverUrl: $execution.resumeUrl\\n },\\n phoneNumberId: 'YOUR_VAPI_PHONE_NUMBER_ID',\\n customer: {\\n number: $json.lead_phone,\\n name: $json.lead_name\\n }\\n}) }}",
"options": {}
},
"id": "ee75e7cc-e86e-48e1-afb2-317813ea866e",
"name": "VAPI: Start Voice Call",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.1,
"position": \[
336,
-112
\]
},
{
"parameters": {
"resume": "webhook",
"options": {
"webhookSuffix": "/vapi-done"
}
},
"id": "c5033cd5-4e91-49c9-bbce-a9898886fb54",
"name": "Wait for VAPI Callback",
"type": "n8n-nodes-base.wait",
"typeVersion": 1,
"position": \[
560,
-112
\],
"webhookId": "vapi-wait-abc123"
},
{
"parameters": {
"jsCode": "// Extract call transcript from VAPI webhook callback\\nconst vapiData = $input.first().json;\\n\\nconst transcript =\\n vapiData?.message?.transcript ||\\n vapiData?.transcript ||\\n vapiData?.body?.message?.transcript ||\\n vapiData?.body?.transcript ||\\n 'No transcript received from this call.';\\n\\nconst callStatus =\\n vapiData?.message?.endedReason ||\\n vapiData?.endedReason ||\\n 'completed';\\n\\nconst leadData = $('Format Lead Data').first().json;\\nconst qualification = $('Parse Qualification').first().json;\\n\\nreturn {\\n ...leadData,\\n qualification_tier: qualification.qualification_tier,\\n qualification_score: qualification.qualification_score,\\n qualification_reason: qualification.qualification_reason,\\n key_strengths: qualification.key_strengths,\\n concerns: qualification.concerns,\\n qualified_at: qualification.qualified_at,\\n call_transcript: transcript,\\n call_status: callStatus,\\n call_completed_at: new Date().toISOString()\\n};"
},
"id": "f26173ca-5b03-4e9e-803c-29df1c605d6f",
"name": "Extract Call Transcript",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": \[
784,
-112
\]
},
{
"parameters": {
"method": "POST",
"url": "https://api.openai.com/v1/chat/completions",
"authentication": "genericCredentialType",
"genericAuthType": "httpBearerAuth",
"sendBody": true,
"specifyBody": "json",
"jsonBody": "={{ JSON.stringify({\\n model: 'gpt-4o',\\n temperature: 0.7,\\n max_tokens: 4096,\\n messages: \[\\n {\\n role: 'system',\\n content: 'You are a senior real estate consultant at Prime Properties creating a stunning, personalized property proposal in full HTML with inline CSS only. The proposal must include: (1) HEADER with company name Prime Properties \\u2014 background #1a3a5c, text #d4af37. (2) Personalized greeting using client first name. (3) Section: We Understand Your Vision \\u2014 bullet list of their requirements. (4) Section: Current Market Insights \\u2014 3 short relevant market facts for their area. (5) Section: Handpicked For You \\u2014 3 realistic fictional property listings each with address, price within their budget, beds/baths/sqft, 4 key features, and a Why This Fits You note. (6) Section: The Prime Properties Difference \\u2014 4 bullet point value propositions. (7) Section: Your Next Steps \\u2014 3 numbered steps with a gold CTA button labeled Schedule Your Free Consultation. (8) FOOTER with phone +1 555 123 4567, email hello@primeproperties.com, website www.primeproperties.com. Use ONLY inline CSS. Colors: navy #1a3a5c, gold #d4af37, white #ffffff, light grey #f8f8f8. Make it impressive, warm, and feel 100% personalized.'\\n },\\n {\\n role: 'user',\\n content: 'Generate the proposal for:\\nName: ' + $json.lead_name + '\\nEmail: ' + $json.lead_email + '\\nIntent: ' + $json.intent + '\\nBudget: ' + $json.budget + '\\nTimeline: ' + $json.timeline + '\\nLocation: ' + $json.location + '\\nProperty Type: ' + $json.property_type + '\\nLead Score: ' + $json.qualification_score + '/10\\nKey Strengths: ' + $json.key_strengths + '\\n\\nCall Transcript:\\n' + $json.call_transcript\\n }\\n \]\\n}) }}",
"options": {}
},
"id": "295942ea-b05a-47d3-848f-dcb98c603da4",
"name": "Generate Proposal (OpenAI)",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.1,
"position": \[
1008,
-112
\],
"credentials": {
"httpBearerAuth": {
"id": "YOUR_CREDENTIAL_ID",
"name": "YOUR_HTTPBEARERAUTH_CREDENTIAL"
}
}
},
{
"parameters": {
"jsCode": "// Extract and clean the proposal HTML from OpenAI response\\nconst response = $input.first().json;\\nconst rawHtml = response.choices\[0\].message.content;\\n\\nconst cleanHtml = rawHtml\\n .replace(/^\`\`\`html\\n?/i, '')\\n .replace(/^\`\`\`\\n?/, '')\\n .replace(/\\n?\`\`\`$/m, '')\\n .trim();\\n\\nconst leadData = $('Extract Call Transcript').first().json;\\n\\nreturn {\\n ...leadData,\\n proposal_html: cleanHtml,\\n proposal_generated_at: new Date().toISOString()\\n};"
},
"id": "1cfc4210-ab35-4129-a9c9-ced284a6e5d5",
"name": "Prepare Proposal HTML",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": \[
1216,
-112
\]
},
{
"parameters": {
"sendTo": "={{ $json.lead_email }}",
"subject": "Your Exclusive Property Proposal | Prime Properties",
"message": "={{ $json.proposal_html }}",
"options": {
"appendAttribution": false
}
},
"id": "4cb8039a-51ee-47fd-a700-4bf50bd5e122",
"name": "Send Proposal Email",
"type": "n8n-nodes-base.gmail",
"typeVersion": 2.1,
"position": \[
1440,
-112
\],
"webhookId": "e6248746-9c68-4beb-b9f3-ce1272f3e809",
"credentials": {
"gmailOAuth2": {
"id": "YOUR_CREDENTIAL_ID",
"name": "YOUR_GMAILOAUTH2_CREDENTIAL"
}
}
},
{
"parameters": {
"operation": "append",
"documentId": {
"\__rl": true,
"value": "https://docs.google.com/spreadsheets/d/YOUR_GOOGLE_SHEET_ID/edit",
"mode": "url"
},
"sheetName": {
"\__rl": true,
"value": "gid=0",
"mode": "list",
"cachedResultName": "Sheet1",
"cachedResultUrl": "https://docs.google.com/spreadsheets/d/YOUR_GOOGLE_SHEET_ID/edit#gid=0"
},
"columns": {
"mappingMode": "defineBelow",
"value": {
"lead_name": "={{ $json.lead_name }}",
"lead_email": "={{ $json.lead_email }}",
"lead_phone": "={{ $json.lead_phone }}",
"qualification": "={{ $json.qualification }}",
"proposal_html": "={{ $json.proposal_html }}",
"status": "hot",
"logged_at": "={{ $now.toISO() }}"
},
"matchingColumns": [ ],
"schema": \[
{
"id": "lead_name",
"displayName": "lead_name",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true
},
{
"id": "lead_email",
"displayName": "lead_email",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true
},
{
"id": "lead_phone",
"displayName": "lead_phone",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true
},
{
"id": "qualification",
"displayName": "qualification",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true
},
{
"id": "proposal_html",
"displayName": "proposal_html",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true
},
{
"id": "status",
"displayName": "status",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true
},
{
"id": "logged_at",
"displayName": "logged_at",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true
}
\],
"attemptToConvertTypes": false,
"convertFieldsToString": false
},
"options": {}
},
"id": "ea66580d-7afe-40e8-b205-d48d96c9fbeb",
"name": "Log Hot Lead to Sheets",
"type": "n8n-nodes-base.googleSheets",
"typeVersion": 4,
"position": \[
1664,
-112
\],
"credentials": {
"googleSheetsOAuth2Api": {
"id": "YOUR_CREDENTIAL_ID",
"name": "YOUR_GOOGLESHEETSOAUTH2API_CREDENTIAL"
}
}
},
{
"parameters": {
"conditions": {
"string": \[
{
"value1": "={{ $json.qualification_tier }}",
"value2": "Warm"
}
\]
}
},
"id": "17729d16-4bbb-4370-acb1-23ece0b5633c",
"name": "Is Warm Lead?",
"type": "n8n-nodes-base.if",
"typeVersion": 1,
"position": \[
336,
176
\]
},
{
"parameters": {
"sendTo": "={{ $json.lead_email }}",
"subject": "={{ 'We\\'d Love to Help You Find Your Perfect Property, ' + $json.lead_name }}",
"message": "=<body style="font-family:Arial,sans-serif;max-width:600px;margin:0 auto;background:#f4f4f4;"><div style="background:#1a3a5c;padding:30px;text-align:center;border-radius:10px 10px 0 0;"><h1 style="color:#d4af37;margin:0;font-size:26px;">Prime Properties<p style="color:#ccc;margin:6px 0 0;font-size:13px;">Your Trusted Real Estate Partner<div style="background:#fff;padding:35px;border-radius:0 0 10px 10px;"><p style="font-size:16px;color:#333;">Hi **{{ $json.lead_name }}**,<p style="color:#555;line-height:1.7;">Thank you for your interest in {{ $json.intent }}ing a **{{ $json.property_type }}** in **{{ $json.location }}**. We have noted your budget of **{{ $json.budget }}** and timeline of **{{ $json.timeline }}**.<p style="color:#555;line-height:1.7;">Our team is already reviewing listings that match your needs. We would love to schedule a quick 15-minute call to share some exclusive options with you and answer any questions.<div style="background:#f8f8f8;border-left:4px solid #d4af37;padding:15px 20px;margin:25px 0;border-radius:0 5px 5px 0;"><p style="margin:0 0 8px;color:#333;font-weight:bold;">What we will cover on our call:<ul style="color:#555;margin:0;padding-left:18px;line-height:1.9;">3-5 hand-picked listings matching your criteriaCurrent market conditions in {{ $json.location }}Step-by-step guidance on the process<div style="text-align:center;margin:30px 0;"><a href="https://calendly.com/prime-properties/consultation\" style="background:#d4af37;color:#1a3a5c;padding:15px 38px;text-decoration:none;border-radius:6px;font-weight:bold;font-size:15px;display:inline-block;">Book Your Free Consultation<p style="color:#888;font-size:13px;line-height:1.7;">Best regards,
<strong style="color:#333;">The Prime Properties Team
+1 (555) 123-4567 | hello@primeproperties.com<div style="text-align:center;padding:14px;"><p style="color:#bbb;font-size:11px;margin:0;">Prime Properties © 2025. All rights reserved.",
"options": {
"appendAttribution": false
}
},
"id": "4f002d6c-925e-4537-a23b-95ca5f6b1eea",
"name": "Send Warm Follow-up Email",
"type": "n8n-nodes-base.gmail",
"typeVersion": 2.1,
"position": \[
560,
80
\],
"webhookId": "1cc6a4bb-0cb6-433e-99cb-9c58f79603f6",
"credentials": {
"gmailOAuth2": {
"id": "YOUR_CREDENTIAL_ID",
"name": "YOUR_GMAILOAUTH2_CREDENTIAL"
}
}
},
{
"parameters": {
"jsCode": "const leadData = $('Parse Qualification').first().json;\\nreturn {\\n ...leadData,\\n call_status: 'Not Called',\\n call_transcript: 'N/A',\\n proposal_sent: 'No',\\n pipeline_status: 'Warm - Follow-up Email Sent'\\n};"
},
"id": "07d33122-c81b-4686-8790-88f1fd35614c",
"name": "Add Warm Status",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": \[
784,
80
\]
},
{
"parameters": {
"operation": "append",
"documentId": {
"\__rl": true,
"value": "https://docs.google.com/spreadsheets/d/YOUR_GOOGLE_SHEET_ID/edit",
"mode": "url"
},
"sheetName": {
"\__rl": true,
"value": "gid=0",
"mode": "list",
"cachedResultName": "Sheet1",
"cachedResultUrl": "https://docs.google.com/spreadsheets/d/YOUR_GOOGLE_SHEET_ID/edit#gid=0"
},
"columns": {
"mappingMode": "defineBelow",
"value": {
"lead_name": "={{ $json.lead_name }}",
"lead_email": "={{ $json.lead_email }}",
"lead_phone": "={{ $json.lead_phone }}",
"qualification": "={{ $json.qualification }}",
"status": "warm",
"logged_at": "={{ $now.toISO() }}"
},
"matchingColumns": [ ],
"schema": \[
{
"id": "lead_name",
"displayName": "lead_name",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true
},
{
"id": "lead_email",
"displayName": "lead_email",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true
},
{
"id": "lead_phone",
"displayName": "lead_phone",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true
},
{
"id": "qualification",
"displayName": "qualification",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true
},
{
"id": "proposal_html",
"displayName": "proposal_html",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true
},
{
"id": "status",
"displayName": "status",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true
},
{
"id": "logged_at",
"displayName": "logged_at",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true
}
\],
"attemptToConvertTypes": false,
"convertFieldsToString": false
},
"options": {}
},
"id": "98b4aefc-ef4d-4349-96e9-d101e4ed20c4",
"name": "Log Warm Lead to Sheets",
"type": "n8n-nodes-base.googleSheets",
"typeVersion": 4,
"position": \[
1008,
80
\],
"credentials": {
"googleSheetsOAuth2Api": {
"id": "YOUR_CREDENTIAL_ID",
"name": "YOUR_GOOGLESHEETSOAUTH2API_CREDENTIAL"
}
}
},
{
"parameters": {
"jsCode": "const leadData = $('Parse Qualification').first().json;\\nreturn {\\n ...leadData,\\n call_status: 'Not Called',\\n call_transcript: 'N/A',\\n proposal_sent: 'No',\\n pipeline_status: 'Cold - Logged Only'\\n};"
},
"id": "c92bb496-574d-4941-886e-ff03dcf2e7ba",
"name": "Add Cold Status",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": \[
560,
320
\]
},
{
"parameters": {
"operation": "append",
"documentId": {
"\__rl": true,
"value": "https://docs.google.com/spreadsheets/d/YOUR_GOOGLE_SHEET_ID/edit",
"mode": "url"
},
"sheetName": {
"\__rl": true,
"value": "gid=0",
"mode": "list",
"cachedResultName": "Sheet1",
"cachedResultUrl": "https://docs.google.com/spreadsheets/d/YOUR_GOOGLE_SHEET_ID/edit#gid=0"
},
"columns": {
"mappingMode": "defineBelow",
"value": {
"lead_name": "={{ $json.lead_name }}",
"lead_email": "={{ $json.lead_email }}",
"lead_phone": "={{ $json.lead_phone }}",
"qualification": "={{ $json.qualification }}",
"status": "cold",
"logged_at": "={{ $now.toISO() }}"
},
"matchingColumns": [ ],
"schema": \[
{
"id": "lead_name",
"displayName": "lead_name",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true
},
{
"id": "lead_email",
"displayName": "lead_email",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true
},
{
"id": "lead_phone",
"displayName": "lead_phone",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true
},
{
"id": "qualification",
"displayName": "qualification",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true
},
{
"id": "proposal_html",
"displayName": "proposal_html",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true
},
{
"id": "status",
"displayName": "status",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true
},
{
"id": "logged_at",
"displayName": "logged_at",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true
}
\],
"attemptToConvertTypes": false,
"convertFieldsToString": false
},
"options": {}
},
"id": "732f95db-49b0-44ac-a9e9-495604ce76e8",
"name": "Log Cold Lead to Sheets",
"type": "n8n-nodes-base.googleSheets",
"typeVersion": 4,
"position": \[
784,
320
\],
"credentials": {
"googleSheetsOAuth2Api": {
"id": "YOUR_CREDENTIAL_ID",
"name": "YOUR_GOOGLESHEETSOAUTH2API_CREDENTIAL"
}
}
}
\],
"pinData": {},
"connections": {
"New Lead Webhook": {
"main": \[
\[
{
"node": "Format Lead Data",
"type": "main",
"index": 0
}
\]
\]
},
"Format Lead Data": {
"main": \[
\[
{
"node": "Webhook Response",
"type": "main",
"index": 0
},
{
"node": "AI Lead Qualifier (OpenAI)",
"type": "main",
"index": 0
}
\]
\]
},
"AI Lead Qualifier (OpenAI)": {
"main": \[
\[
{
"node": "Parse Qualification",
"type": "main",
"index": 0
}
\]
\]
},
"Parse Qualification": {
"main": \[
\[
{
"node": "Is Hot Lead?",
"type": "main",
"index": 0
}
\]
\]
},
"Is Hot Lead?": {
"main": \[
\[
{
"node": "VAPI: Start Voice Call",
"type": "main",
"index": 0
}
\],
\[
{
"node": "Is Warm Lead?",
"type": "main",
"index": 0
}
\]
\]
},
"VAPI: Start Voice Call": {
"main": \[
\[
{
"node": "Wait for VAPI Callback",
"type": "main",
"index": 0
}
\]
\]
},
"Wait for VAPI Callback": {
"main": \[
\[
{
"node": "Extract Call Transcript",
"type": "main",
"index": 0
}
\]
\]
},
"Extract Call Transcript": {
"main": \[
\[
{
"node": "Generate Proposal (OpenAI)",
"type": "main",
"index": 0
}
\]
\]
},
"Generate Proposal (OpenAI)": {
"main": \[
\[
{
"node": "Prepare Proposal HTML",
"type": "main",
"index": 0
}
\]
\]
},
"Prepare Proposal HTML": {
"main": \[
\[
{
"node": "Send Proposal Email",
"type": "main",
"index": 0
}
\]
\]
},
"Send Proposal Email": {
"main": \[
\[
{
"node": "Log Hot Lead to Sheets",
"type": "main",
"index": 0
}
\]
\]
},
"Is Warm Lead?": {
"main": \[
\[
{
"node": "Send Warm Follow-up Email",
"type": "main",
"index": 0
}
\],
\[
{
"node": "Add Cold Status",
"type": "main",
"index": 0
}
\]
\]
},
"Send Warm Follow-up Email": {
"main": \[
\[
{
"node": "Add Warm Status",
"type": "main",
"index": 0
}
\]
\]
},
"Add Warm Status": {
"main": \[
\[
{
"node": "Log Warm Lead to Sheets",
"type": "main",
"index": 0
}
\]
\]
},
"Add Cold Status": {
"main": \[
\[
{
"node": "Log Cold Lead to Sheets",
"type": "main",
"index": 0
}
\]
\]
}
},
"active": false,
"settings": {
"executionOrder": "v1",
"binaryMode": "separate",
"availableInMCP": false
},
"meta": {
"templateCredsSetupCompleted": false
},
"tags": [ ]
}