This workflow follows the Error Trigger → Gmail 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 →
{
"name": "Personalized Outreach & Follow-Up - Phase 2",
"nodes": [
{
"parameters": {
"content": "## LEAD PREPARATION & PERSONALIZATION",
"height": 320,
"width": 2272
},
"type": "n8n-nodes-base.stickyNote",
"typeVersion": 1,
"position": [
-2016,
-48
],
"id": "edb4c661-b2c6-413e-a23a-919a756865fb",
"name": "Sticky Note"
},
{
"parameters": {
"rule": {
"interval": [
{
"triggerAtHour": 9
}
]
}
},
"type": "n8n-nodes-base.scheduleTrigger",
"typeVersion": 1.3,
"position": [
-1968,
80
],
"id": "392a5c52-7365-4738-94e0-d9f8919e690b",
"name": "Daily 9AM Campaign"
},
{
"parameters": {
"documentId": {
"__rl": true,
"value": "1xZWORo7S2IjqXRqf3hw2H17mm6qUgIC7lcX2tmiq6Q0",
"mode": "list",
"cachedResultName": "Qualified Leads",
"cachedResultUrl": "https://docs.google.com/spreadsheets/d/YOUR_GOOGLE_SHEET_ID/edit"
},
"sheetName": {
"__rl": true,
"value": "gid=0",
"mode": "list",
"cachedResultName": "Leads sheet",
"cachedResultUrl": "https://docs.google.com/spreadsheets/d/YOUR_GOOGLE_SHEET_ID/edit"
},
"filtersUI": {
"values": [
{
"lookupColumn": "Contacted",
"lookupValue": "No"
}
]
},
"options": {}
},
"type": "n8n-nodes-base.googleSheets",
"typeVersion": 4.7,
"position": [
-1456,
80
],
"id": "91ad2d31-4213-4f85-ad35-52b9f8c6f3bd",
"name": "Fetch All Leads",
"credentials": {
"googleSheetsOAuth2Api": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"maxItems": 10
},
"type": "n8n-nodes-base.limit",
"typeVersion": 1,
"position": [
-1248,
80
],
"id": "12705805-1ced-4bc8-be3e-4d52d67bcc50",
"name": "Cap at 10 Daily"
},
{
"parameters": {
"jsCode": "// Randomize the order of leads (looks more natural)\nconst items = $input.all();\n\n// Shuffle array\nfor (let i = items.length - 1; i > 0; i--) {\n const j = Math.floor(Math.random() * (i + 1));\n [items[i], items[j]] = [items[j], items[i]];\n}\n\nreturn items;\n"
},
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
-1040,
80
],
"id": "5a501a92-476a-4555-b65d-d4933e524bb9",
"name": "Randomize Lead Order"
},
{
"parameters": {
"jsCode": "const items = $input.all();\nconst results = [];\n\nfor (const item of items) {\n const lead = item.json;\n \n // Prepare data for AI personalization\n const preparedData = {\n // Lead information\n company_name: lead['Company name'] || '',\n contact_name: lead['Contact person full name'] || '',\n first_name: (lead['Contact person full name'] || '').split(' ')[0],\n job_title: lead['Job title'] || '',\n industry: lead['Industry / niche'] || '',\n business_email: lead['Business email '] || lead['Business email'] || '',\n \n // Context for personalization\n qualification_reasoning: lead['Score reasoning'] || '',\n num_employees: lead['Number of employees'] || '',\n country: lead['Country'] || '',\n \n // Tracking\n row_number: item.pairedItem?.item || 0\n };\n \n results.push({ json: preparedData });\n}\n\nreturn results;"
},
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
-576,
96
],
"id": "1ecc3fcb-0d59-44dd-8bff-8d7c6af0cdac",
"name": "Extract & Prepare Lead Data"
},
{
"parameters": {
"modelId": {
"__rl": true,
"value": "gpt-4.1-mini",
"mode": "list",
"cachedResultName": "GPT-4.1-MINI"
},
"responses": {
"values": [
{
"role": "system",
"content": "=You are a B2B cold email expert writing for Michael, an Automation Consultant at ObiunDigital.\n\nMichael's positioning: He helps companies IDENTIFY where time, cost, and errors are leaking in their operations. he's a consultant who finds problems first, then designs solutions (whether through automation, process redesign, or a combination of tools and specialists).\n\nWrite a personalized cold email that:\n- Positions Michael as a consultant who helps identify operational inefficiencies, not someone pitching automation\n- Focuses on WHERE friction likely exists in their operations.\n- Mentions the types of operational bottlenecks commonly found in similar companies\n- Keeps it under 150 words\n- Has a clear, low-pressure CTA focused on \"identifying\" problems, not selling solutions\n- Uses proper paragraph breaks (empty lines between paragraphs)\n\nIMPORTANT: Format the email body with proper line breaks:\n- Each paragraph should be separated by a blank line (\\n\\n)\n- Bullet points should each be on a new line\n\nOutput ONLY valid JSON with this structure:\n{\n \"subject\": \"Email subject line (max 50 chars)\",\n \"body\": \"Full email body with proper \\n\\n paragraph breaks and \\n bullet formatting\",\n \"relevant_process\": \"The specific operational area mentioned (e.g., order handling, internal coordination)\"\n}\n"
},
{
"content": "=Write Email 1 for this lead:\n\nCompany: {{ $json.company_name }}\nContact: {{ $json.first_name }} {{ $json.job_title }}\nIndustry: {{ $json.industry }}\nEmployees: {{ $json.num_employees }}\nCountry: {{ $json.country }}\n\nWhy they're qualified: {{ $json.qualification_reasoning }}\n\nUse this template structure but personalize heavily based on their industry and operational context:\n\nSubject: Reducing manual work at {Company Name}\n\nHi {First Name},\n\nI came across {Company Name} while looking into companies active in {industry}.\n\nIn similar organizations, we often find that 20\u201340% of daily operational work is still manual, usually spread across {relevant operational areas: e.g., order handling, inventory updates, internal coordination between systems and teams}.\n\nMy role is to help companies identify where time, cost, and errors are leaking in their operations, and then design the most effective way to eliminate that friction, whether through automation, process redesign, or a combination of tools and specialists.\n\nThis typically results in:\n\u2022 30\u201360% reduction in manual work\n\u2022 Faster turnaround times (hours reduced to minutes)\n\u2022 Fewer errors and less dependency on individual staff members\n\u2022 Structural cost savings, not short-term optimizations\n\nNo heavy IT projects or system replacements, we automate on top of existing tools.\n\nWould it be worth a 15-minute conversation to identify where {Company Name} could save the most time and cost?\n\nBest regards,\n\nMichael\nAutomation Consultant\nObiunDigital\n\nIMPORTANT: Make sure to identify specific operational areas (not just \"processes\") where friction likely exists based on their industry.\n"
}
]
},
"builtInTools": {},
"options": {}
},
"type": "@n8n/n8n-nodes-langchain.openAi",
"typeVersion": 2.1,
"position": [
-240,
96
],
"id": "9eab7d10-53bf-4edd-b977-20e1e1aec4d8",
"name": "Generate Personalized Email 1",
"credentials": {
"openAiApi": {
"name": "<your credential>"
}
},
"onError": "continueRegularOutput"
},
{
"parameters": {
"amount": "={{ Math.floor(Math.random() * 6) + 5 }}",
"unit": "minutes"
},
"type": "n8n-nodes-base.wait",
"typeVersion": 1.1,
"position": [
560,
160
],
"id": "fe8d8ec4-eeda-41ad-9387-7c1cf5be6071",
"name": "Random Delay (5-10 minutes)"
},
{
"parameters": {
"sendTo": "={{ $('Extract & Prepare Lead Data').item.json.business_email }}",
"subject": "={{ $json.email_subject }}",
"emailType": "text",
"message": "={{ $json.email_body }}",
"options": {
"appendAttribution": false,
"senderName": "Michael",
"replyTo": "user@example.com"
}
},
"type": "n8n-nodes-base.gmail",
"typeVersion": 2.2,
"position": [
928,
160
],
"id": "ad5f6af0-5f1b-4256-8b7e-df1075cd8a92",
"name": "Send Email 1",
"executeOnce": false,
"credentials": {
"gmailOAuth2": {
"name": "<your credential>"
}
},
"onError": "continueRegularOutput"
},
{
"parameters": {
"jsCode": "const items = $input.all();\nconst results = [];\n\nfor (const item of items) {\n const now = new Date();\n \n // Format date as YYYY-MM-DD HH:MM:SS\n const sentDate = now.toISOString().replace('T', ' ').substring(0, 19);\n \n results.push({\n json: {\n business_email: item.json.business_email,\n contacted: 'Yes',\n email_1_sent_date: sentDate,\n company_name: item.json.company_name,\n sent_timestamp: now.getTime()\n }\n });\n}\n\nconsole.log(`\ud83d\udcca Preparing to update ${results.length} rows in Google Sheets`);\nreturn results;\n"
},
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
1168,
80
],
"id": "6a03e6c2-9342-48da-879d-a53b5e187963",
"name": "Prepare Sheet Update Data"
},
{
"parameters": {
"operation": "update",
"documentId": {
"__rl": true,
"value": "1xZWORo7S2IjqXRqf3hw2H17mm6qUgIC7lcX2tmiq6Q0",
"mode": "list",
"cachedResultName": "Qualified Leads",
"cachedResultUrl": "https://docs.google.com/spreadsheets/d/YOUR_GOOGLE_SHEET_ID/edit"
},
"sheetName": {
"__rl": true,
"value": "gid=0",
"mode": "list",
"cachedResultName": "Leads sheet",
"cachedResultUrl": "https://docs.google.com/spreadsheets/d/YOUR_GOOGLE_SHEET_ID/edit"
},
"columns": {
"mappingMode": "defineBelow",
"value": {
"Business email ": "={{ $('Extract & Prepare Lead Data').item.json.business_email }}",
"Contacted": "={{ $json.contacted }}",
"Email 1 Sent Date": "={{ $json.email_1_sent_date }}"
},
"matchingColumns": [
"Business email "
],
"schema": [
{
"id": "Company name",
"displayName": "Company name",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true,
"removed": true
},
{
"id": "Company website",
"displayName": "Company website",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true,
"removed": true
},
{
"id": "Industry / niche",
"displayName": "Industry / niche",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true,
"removed": true
},
{
"id": "Number of employees",
"displayName": "Number of employees",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true,
"removed": true
},
{
"id": "Country",
"displayName": "Country",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true,
"removed": true
},
{
"id": "Contact person full name",
"displayName": "Contact person full name",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true,
"removed": true
},
{
"id": "Job title",
"displayName": "Job title",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true,
"removed": true
},
{
"id": "Business email ",
"displayName": "Business email ",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true,
"removed": false
},
{
"id": "LinkedIn profile URL ",
"displayName": "LinkedIn profile URL ",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true,
"removed": true
},
{
"id": "LinkedIn company page URL",
"displayName": "LinkedIn company page URL",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true,
"removed": true
},
{
"id": "Qualification score",
"displayName": "Qualification score",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true,
"removed": true
},
{
"id": "Score reasoning",
"displayName": "Score reasoning",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true,
"removed": true
},
{
"id": "Contacted",
"displayName": "Contacted",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true,
"removed": false
},
{
"id": "Email 1 Sent Date",
"displayName": "Email 1 Sent Date",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true
},
{
"id": "Email 2 Sent Date",
"displayName": "Email 2 Sent Date",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true,
"removed": true
},
{
"id": "Email 3 Sent Date",
"displayName": "Email 3 Sent Date",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true,
"removed": true
},
{
"id": "Reply Date",
"displayName": "Reply Date",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true,
"removed": true
},
{
"id": "row_number",
"displayName": "row_number",
"required": false,
"defaultMatch": false,
"display": true,
"type": "number",
"canBeUsedToMatch": true,
"readOnly": true,
"removed": true
}
],
"attemptToConvertTypes": false,
"convertFieldsToString": false
},
"options": {}
},
"type": "n8n-nodes-base.googleSheets",
"typeVersion": 4.7,
"position": [
1360,
80
],
"id": "9c3af513-7cf1-4bae-ab7d-4ae37fe60cfd",
"name": "Update Contacted Status",
"credentials": {
"googleSheetsOAuth2Api": {
"name": "<your credential>"
}
},
"onError": "continueRegularOutput"
},
{
"parameters": {
"jsCode": "const items = $input.all();\n\n// Calculate stats\nconst stats = {\n date: new Date().toISOString().split('T')[0],\n emails_sent: items.length,\n campaign: 'Email 1 - Initial Outreach',\n status: 'Completed',\n leads_contacted: items.map(i => i.json.company_name).join(', ')\n};\n\nconsole.log('\ud83d\udcc8 DAILY STATS:');\nconsole.log(`Date: ${stats.date}`);\nconsole.log(`Emails Sent: ${stats.emails_sent}`);\nconsole.log(`Companies: ${stats.leads_contacted}`);\n\nreturn [{ json: stats }];"
},
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
1552,
80
],
"id": "44864935-a778-46fc-8d6c-be0ba348ae20",
"name": "Daily Stats Logger"
},
{
"parameters": {
"jsCode": "const items = $input.all();\nconst results = [];\n\nfor (const item of items) {\n const leadData = item.json;\n \n // Extract AI output\n let aiOutput;\n if (leadData.output && Array.isArray(leadData.output)) {\n if (leadData.output[0]?.content?.[0]?.text) {\n aiOutput = leadData.output[0].content[0].text;\n } else if (leadData.output[0]?.text) {\n aiOutput = leadData.output[0].text;\n }\n } else if (leadData.text) {\n aiOutput = leadData.text;\n }\n \n // Clean JSON\n let cleanJson = aiOutput\n .replace(/```json\\n?/gi, '')\n .replace(/```\\n?/g, '')\n .replace(/^[^{]*/g, '')\n .replace(/[^}]*$/g, '')\n .trim();\n \n // Parse AI response\n let aiData;\n try {\n aiData = JSON.parse(cleanJson);\n } catch (e) {\n console.log('AI parsing failed, using fallback');\n aiData = {\n subject: `Quick question about ${leadData.company_name}`,\n body: `Hi ${leadData.first_name},\\n\\nI came across ${leadData.company_name} while researching companies in ${leadData.industry}.\\n\\nWe help teams automate manual processes, typically reducing operational work by 30-60%.\\n\\nWould a brief conversation be valuable?\\n\\nBest regards,\\nMichael`,\n relevant_process: 'operational processes'\n };\n }\n \n // NO FOOTER HERE - will be added in next node\n results.push({\n json: {\n ...leadData,\n email_subject: aiData.subject,\n email_body: aiData.body,\n relevant_process: aiData.relevant_process\n }\n });\n}\n\nreturn results;"
},
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
112,
96
],
"id": "a7b21c1a-c62d-4e5e-9bd6-956a0529ed13",
"name": "Parse AI"
},
{
"parameters": {
"content": "## EMAIL SENDING & TRACKING",
"height": 336,
"width": 1408,
"color": 2
},
"type": "n8n-nodes-base.stickyNote",
"typeVersion": 1,
"position": [
304,
-48
],
"id": "063d4609-7dac-4e1f-9df2-61e760d8566d",
"name": "Sticky Note1"
},
{
"parameters": {
"rule": {
"interval": [
{
"triggerAtHour": 10
}
]
}
},
"type": "n8n-nodes-base.scheduleTrigger",
"typeVersion": 1.3,
"position": [
-1600,
496
],
"id": "5c2bb1c6-a32a-498a-86bb-c0fe92001174",
"name": "Daily 10AM Follow-up Check"
},
{
"parameters": {
"documentId": {
"__rl": true,
"value": "1xZWORo7S2IjqXRqf3hw2H17mm6qUgIC7lcX2tmiq6Q0",
"mode": "list",
"cachedResultName": "Qualified Leads",
"cachedResultUrl": "https://docs.google.com/spreadsheets/d/YOUR_GOOGLE_SHEET_ID/edit"
},
"sheetName": {
"__rl": true,
"value": "gid=0",
"mode": "list",
"cachedResultName": "Leads sheet",
"cachedResultUrl": "https://docs.google.com/spreadsheets/d/YOUR_GOOGLE_SHEET_ID/edit"
},
"options": {}
},
"type": "n8n-nodes-base.googleSheets",
"typeVersion": 4.7,
"position": [
-1120,
496
],
"id": "f952f604-87cf-4581-8149-c723412ceec5",
"name": "Fetch All Contacted Leads",
"credentials": {
"googleSheetsOAuth2Api": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"jsCode": "const items = $input.all();\nconst results = [];\n\n// Get current date\nconst now = new Date();\n\nfor (const item of items) {\n const lead = item.json;\n \n // Get the date columns (handle different possible names)\n const email1Date = lead['Email_1_Sent_Date'] || lead['Email 1 Sent Date'] || lead['Email 1 Sent Date'];\n const email2Date = lead['Email_2_Sent_Date'] || lead['Email 2 Sent Date'];\n const replyDate = lead['Reply_Date'] || lead['Reply Date'];\n const replySentiment = lead['Reply_Sentiment'] || lead['Reply Sentiment'] || '';\n \n // \u2705 SKIP IF UNSUBSCRIBED\n if (replySentiment === 'UNSUBSCRIBE') {\n console.log(`\ud83d\udeab ${lead['Company name']} requested unsubscribe - skipping Email 2`);\n continue;\n }\n \n // Skip if:\n // - Email 1 was never sent\n // - Email 2 was already sent\n // - They already replied\n if (!email1Date || email2Date || replyDate) {\n continue;\n }\n \n // Parse Email 1 sent date\n const sentDate = new Date(email1Date);\n \n // Calculate days since Email 1\n const daysDiff = Math.floor((now - sentDate) / (1000 * 60 * 60 * 24));\n \n // Send Email 2 exactly 5 days after Email 1 (Michael's requirement)\n if (daysDiff === 5) {\n console.log(`\u2705 ${lead['Company name']} needs Email 2 (Email 1 sent ${daysDiff} days ago)`);\n results.push(item);\n }\n}\n\nconsole.log(`\ud83d\udce7 Total leads needing Email 2 today: ${results.length}`);\nreturn results;"
},
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
-912,
496
],
"id": "515bff2a-9000-42ca-8d18-3ec5ccc74c71",
"name": "Check Who Needs Email 2"
},
{
"parameters": {
"modelId": {
"__rl": true,
"value": "gpt-4.1-mini",
"mode": "list",
"cachedResultName": "GPT-4.1-MINI"
},
"responses": {
"values": [
{
"role": "system",
"content": "=You are a B2B cold email expert writing follow-up Email 2 for Michael, an Automation Consultant at ObiunDigital.\n\nThis is a soft follow-up to check if the first email reached the right person.\n\nWrite a personalized follow-up that:\n- Is brief and respectful (under 100 words)\n- Acknowledges they might be busy\n- Gives them an easy out (\"not relevant\" is fine)\n- Offers one concrete example if they're interested\n- Uses proper paragraph breaks (empty lines between paragraphs)\n\nIMPORTANT: Format with proper line breaks (\\n\\n between paragraphs).\n\n\nOutput ONLY valid JSON with this structure:\n{\n \"subject\": \"Email subject line (max 50 chars)\",\n \"body\": \"Full email body with proper line breaks\"\n}"
},
{
"content": "=Write Email 2 (follow-up) for this lead:\n\nCompany: {{ $json['Company name'] }}\nContact: {{ $json['Contact person full name'] }} {{ $json['Job title'] }}\nIndustry: {{ $json['Industry / niche'] }}\nEmployees: {{ $json['Number of employees'] }}\n\nUse this template structure but personalize:\n\nSubject: Quick check \u2014 automation at {Company Name}\n\nHi {First Name},\n\nJust checking in \u2014 not sure if this is relevant for you, or if I should reach out to someone else at {Company Name}.\n\nThe reason I reached out is that, in similar teams, we often uncover small operational bottlenecks that quietly cost hours each week, without being visible in reports or KPIs.\n\nIf automation isn't a priority right now, no problem at all \u2014 a short reply like \"not relevant\" is perfectly fine.\n\nOtherwise, I'm happy to share one concrete example of where companies typically save time and cost with minimal effort.\n\nBest regards,\n\nMichael\nAutomation Consultant\nObiunDigital\n"
}
]
},
"builtInTools": {},
"options": {}
},
"type": "@n8n/n8n-nodes-langchain.openAi",
"typeVersion": 2.1,
"position": [
-704,
496
],
"id": "fe050d70-a48e-4bfb-80f9-c902772c425f",
"name": "Generate Personalized Email 2",
"credentials": {
"openAiApi": {
"name": "<your credential>"
}
},
"onError": "continueRegularOutput"
},
{
"parameters": {
"jsCode": "const items = $input.all();\nconst results = [];\n\nfor (const item of items) {\n const leadData = item.json;\n \n // Extract AI output\n let aiOutput;\n if (leadData.output && Array.isArray(leadData.output)) {\n if (leadData.output[0]?.content?.[0]?.text) {\n aiOutput = leadData.output[0].content[0].text;\n } else if (leadData.output[0]?.text) {\n aiOutput = leadData.output[0].text;\n }\n } else if (leadData.text) {\n aiOutput = leadData.text;\n }\n \n // Clean JSON markers\n let cleanJson = aiOutput\n .replace(/```json\\n?/gi, '')\n .replace(/```\\n?/g, '')\n .replace(/^[^{]*/g, '')\n .replace(/[^}]*$/g, '')\n .trim();\n \n // Parse AI response\n let aiData;\n try {\n aiData = JSON.parse(cleanJson);\n } catch (e) {\n console.log(`\u26a0\ufe0f AI parsing failed for ${leadData.company_name}, using fallback`);\n aiData = {\n subject: `Quick check \u2014 ${leadData.company_name}`,\n body: `Hi ${leadData.first_name},\\n\\nJust checking in \u2014 not sure if this is relevant for you.\\n\\nIf automation isn't a priority right now, no problem at all.\\n\\nOtherwise, I'm happy to share a quick example of where similar companies save time.\\n\\nBest regards,\\n\\nMichael\\nAutomation Consultant\\nObiunDigital`\n };\n }\n \n // NO FOOTER HERE - will be added in next node\n results.push({\n json: {\n ...leadData,\n email_subject: aiData.subject,\n email_body: aiData.body\n }\n });\n}\n\nconsole.log(`\u2705 Parsed ${results.length} Email 2 messages`);\nreturn results;"
},
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
-352,
496
],
"id": "d57220cd-e392-4064-89d5-51c45595a7d2",
"name": "Parse AI Email 2 Response"
},
{
"parameters": {
"jsCode": "const items = $input.all();\nconst results = [];\n\nfor (const item of items) {\n const now = new Date();\n \n // Format date as YYYY-MM-DD HH:MM:SS\n const sentDate = now.toISOString().replace('T', ' ').substring(0, 19);\n \n results.push({\n json: {\n business_email: item.json.business_email,\n email_2_sent_date: sentDate,\n company_name: item.json.company_name\n }\n });\n}\n\nconsole.log(`\ud83d\udcca Updating ${results.length} Email 2 records in Google Sheets`);\nreturn results;"
},
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
656,
496
],
"id": "8f1be71d-f297-47b4-b50d-eb3f6b62ba25",
"name": "Prepare Email 2 Sheet Update"
},
{
"parameters": {
"content": "## FOLLOW-UP SEQUENCE MANAGER",
"height": 560,
"width": 3168,
"color": 5
},
"type": "n8n-nodes-base.stickyNote",
"typeVersion": 1,
"position": [
-1632,
336
],
"id": "7aabe8d8-7fca-4f97-bf8f-940f97fbdc47",
"name": "Sticky Note2"
},
{
"parameters": {
"jsCode": "const items = $input.all();\nconst results = [];\n\n// Get current date\nconst now = new Date();\n\nfor (const item of items) {\n const lead = item.json;\n \n // Get the date columns\n const email2Date = lead['Email_2_Sent_Date'] || lead['Email 2 Sent Date'];\n const email3Date = lead['Email_3_Sent_Date'] || lead['Email 3 Sent Date'];\n const replyDate = lead['Reply_Date'] || lead['Reply Date'];\n const replySentiment = lead['Reply_Sentiment'] || lead['Reply Sentiment'] || '';\n \n // \u2705 SKIP IF UNSUBSCRIBED\n if (replySentiment === 'UNSUBSCRIBE') {\n console.log(`\ud83d\udeab ${lead['Company name']} requested unsubscribe - skipping Email 3`);\n continue;\n }\n \n // Skip if:\n // - Email 2 was never sent\n // - Email 3 was already sent\n // - They already replied\n if (!email2Date || email3Date || replyDate) {\n continue;\n }\n \n // Parse Email 2 sent date\n const sentDate = new Date(email2Date);\n \n // Calculate days since Email 2\n const daysDiff = Math.floor((now - sentDate) / (1000 * 60 * 60 * 24));\n \n // Send Email 3 exactly 5 days after Email 2 (which is 10 days after Email 1)\n if (daysDiff === 5) {\n console.log(`\u2705 ${lead['Company name']} needs Email 3 (Email 2 sent ${daysDiff} days ago)`);\n results.push(item);\n }\n}\n\nconsole.log(`\ud83d\udce7 Total leads needing Email 3 today: ${results.length}`);\nreturn results;"
},
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
-912,
704
],
"id": "37decb90-b69e-4503-88d3-ac04eb860515",
"name": "Check Who Needs Email 3"
},
{
"parameters": {
"jsCode": "const items = $input.all();\nconst results = [];\n\nfor (const item of items) {\n const lead = item.json;\n \n // Extract and prepare lead data for Email 3 personalization\n const preparedData = {\n // Lead information\n company_name: lead['Company name'] || '',\n contact_name: lead['Contact person full name'] || '',\n first_name: (lead['Contact person full name'] || '').split(' ')[0],\n job_title: lead['Job title'] || '',\n industry: lead['Industry / niche'] || '',\n business_email: lead['Business email '] || lead['Business email'] || '',\n \n // Context for personalization\n qualification_reasoning: lead['Score reasoning'] || '',\n num_employees: lead['Number of employees'] || '',\n country: lead['Country'] || ''\n };\n \n results.push({ json: preparedData });\n}\n\nconsole.log(`\ud83d\udccb Prepared ${results.length} leads for Email 3 personalization`);\nreturn results;"
},
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
-704,
704
],
"id": "626f6646-5e33-4a28-ac81-a6d6fbf208fd",
"name": "Prepare Email 3 Data"
},
{
"parameters": {
"jsCode": "const items = $input.all();\nconst results = [];\n\nfor (const item of items) {\n const leadData = item.json;\n \n // Extract AI output\n let aiOutput;\n if (leadData.output && Array.isArray(leadData.output)) {\n if (leadData.output[0]?.content?.[0]?.text) {\n aiOutput = leadData.output[0].content[0].text;\n } else if (leadData.output[0]?.text) {\n aiOutput = leadData.output[0].text;\n }\n } else if (leadData.text) {\n aiOutput = leadData.text;\n }\n \n // Clean JSON markers\n let cleanJson = aiOutput\n .replace(/```json\\n?/gi, '')\n .replace(/```\\n?/g, '')\n .replace(/^[^{]*/g, '')\n .replace(/[^}]*$/g, '')\n .trim();\n \n // Parse AI response\n let aiData;\n try {\n aiData = JSON.parse(cleanJson);\n } catch (e) {\n console.log(`\u26a0\ufe0f AI parsing failed for ${leadData.company_name}, using fallback`);\n aiData = {\n subject: `One example for ${leadData.company_name}`,\n body: `Hi ${leadData.first_name},\\n\\nI didn't want to follow up without adding value.\\n\\nIn teams similar to ${leadData.company_name}, we often see automation wins around operational processes that remove several hours of manual work per week.\\n\\nThis may or may not be relevant \u2014 but if it is, I'm happy to share the specifics.\\n\\nWould you like me to share the example, or leave it here for now?\\n\\nBest regards,\\n\\nMichael\\nAutomation Consultant\\nObiunDigital`\n };\n }\n \n // NO FOOTER HERE - will be added in next node\n results.push({\n json: {\n ...leadData,\n email_subject: aiData.subject,\n email_body: aiData.body\n }\n });\n}\n\nconsole.log(`\u2705 Parsed ${results.length} Email 3 messages`);\nreturn results;"
},
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
-144,
704
],
"id": "ff9874a2-c456-4bcc-9628-cdf7dd5b7bb3",
"name": "Parse AI Email 3 Response"
},
{
"parameters": {
"amount": "={{ Math.floor(Math.random() * 6) + 5 }}",
"unit": "minutes"
},
"type": "n8n-nodes-base.wait",
"typeVersion": 1.1,
"position": [
304,
704
],
"id": "4dcf18d1-30f3-48c1-9d93-de70879422e8",
"name": "Random Delay (5-10 minutes)2"
},
{
"parameters": {
"sendTo": "={{ $('Prepare Email 3 Data').item.json.business_email }}",
"subject": "={{ $json.email_subject }}",
"emailType": "text",
"message": "={{ $json.email_body }}",
"options": {
"appendAttribution": false,
"senderName": "Michael",
"replyTo": "user@example.com"
}
},
"type": "n8n-nodes-base.gmail",
"typeVersion": 2.2,
"position": [
720,
704
],
"id": "100ecdc6-13f7-48d7-9f01-0e3ce1c6916a",
"name": "Send Email 3",
"credentials": {
"gmailOAuth2": {
"name": "<your credential>"
}
},
"onError": "continueRegularOutput"
},
{
"parameters": {
"jsCode": "const items = $input.all();\nconst results = [];\n\nfor (const item of items) {\n const now = new Date();\n \n // Format date as YYYY-MM-DD HH:MM:SS\n const sentDate = now.toISOString().replace('T', ' ').substring(0, 19);\n \n results.push({\n json: {\n business_email: item.json.business_email,\n email_3_sent_date: sentDate,\n company_name: item.json.company_name\n }\n });\n}\n\nconsole.log(`\ud83d\udcca Updating ${results.length} Email 3 records in Google Sheets`);\nreturn results;"
},
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
928,
704
],
"id": "98df9da2-d214-406b-803c-574277b2aecd",
"name": "Prepare Email 3 Sheet Update"
},
{
"parameters": {
"operation": "update",
"documentId": {
"__rl": true,
"value": "1xZWORo7S2IjqXRqf3hw2H17mm6qUgIC7lcX2tmiq6Q0",
"mode": "list",
"cachedResultName": "Qualified Leads",
"cachedResultUrl": "https://docs.google.com/spreadsheets/d/YOUR_GOOGLE_SHEET_ID/edit"
},
"sheetName": {
"__rl": true,
"value": "gid=0",
"mode": "list",
"cachedResultName": "Leads sheet",
"cachedResultUrl": "https://docs.google.com/spreadsheets/d/YOUR_GOOGLE_SHEET_ID/edit"
},
"columns": {
"mappingMode": "defineBelow",
"value": {
"Email 3 Sent Date": "={{ $json.email_3_sent_date }}",
"Business email ": "={{ $('Prepare Email 3 Data').item.json.business_email }}"
},
"matchingColumns": [
"Business email "
],
"schema": [
{
"id": "Company name",
"displayName": "Company name",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true,
"removed": true
},
{
"id": "Company website",
"displayName": "Company website",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true,
"removed": true
},
{
"id": "Industry / niche",
"displayName": "Industry / niche",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true,
"removed": true
},
{
"id": "Number of employees",
"displayName": "Number of employees",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true,
"removed": true
},
{
"id": "Country",
"displayName": "Country",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true,
"removed": true
},
{
"id": "Contact person full name",
"displayName": "Contact person full name",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true,
"removed": true
},
{
"id": "Job title",
"displayName": "Job title",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true,
"removed": true
},
{
"id": "Business email ",
"displayName": "Business email ",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true,
"removed": false
},
{
"id": "LinkedIn profile URL ",
"displayName": "LinkedIn profile URL ",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true,
"removed": true
},
{
"id": "LinkedIn company page URL",
"displayName": "LinkedIn company page URL",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true,
"removed": true
},
{
"id": "Qualification score",
"displayName": "Qualification score",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true,
"removed": true
},
{
"id": "Score reasoning",
"displayName": "Score reasoning",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true,
"removed": true
},
{
"id": "Contacted",
"displayName": "Contacted",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true,
"removed": true
},
{
"id": "Email 1 Sent Date",
"displayName": "Email 1 Sent Date",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true,
"removed": true
},
{
"id": "Email 2 Sent Date",
"displayName": "Email 2 Sent Date",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true,
"removed": true
},
{
"id": "Email 3 Sent Date",
"displayName": "Email 3 Sent Date",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true
},
{
"id": "Reply Date",
"displayName": "Reply Date",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true,
"removed": true
},
{
"id": "row_number",
"displayName": "row_number",
"required": false,
"defaultMatch": false,
"display": true,
"type": "number",
"canBeUsedToMatch": true,
"readOnly": true,
"removed": true
}
],
"attemptToConvertTypes": false,
"convertFieldsToString": false
},
"options": {}
},
"type": "n8n-nodes-base.googleSheets",
"typeVersion": 4.7,
"position": [
1136,
704
],
"id": "9eaec72d-fef4-4801-abc6-7d7607f5d03b",
"name": "Update Email 3 Sent Date",
"credentials": {
"googleSheetsOAuth2Api": {
"name": "<your credential>"
}
},
"onError": "continueRegularOutput"
},
{
"parameters": {
"operation": "update",
"documentId": {
"__rl": true,
"value": "1xZWORo7S2IjqXRqf3hw2H17mm6qUgIC7lcX2tmiq6Q0",
"mode": "list",
"cachedResultName": "Qualified Leads",
"cachedResultUrl": "https://docs.google.com/spreadsheets/d/YOUR_GOOGLE_SHEET_ID/edit"
},
"sheetName": {
"__rl": true,
"value": "gid=0",
"mode": "list",
"cachedResultName": "Leads sheet",
"cachedResultUrl": "https://docs.google.com/spreadsheets/d/YOUR_GOOGLE_SHEET_ID/edit"
},
"columns": {
"mappingMode": "defineBelow",
"value": {
"Email 2 Sent Date": "={{ $json.email_2_sent_date }}",
"Business email ": "={{ $('Check Who Needs Email 2').item.json[\"Business email \"] }}"
},
"matchingColumns": [
"Business email "
],
"schema": [
{
"id": "Company name",
"displayName": "Company name",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true,
"removed": true
},
{
"id": "Company website",
"displayName": "Company website",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true,
"removed": true
},
{
"id": "Industry / niche",
"displayName": "Industry / niche",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true,
"removed": true
},
{
"id": "Number of employees",
"displayName": "Number of employees",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true,
"removed": true
},
{
"id": "Country",
"displayName": "Country",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true,
"removed": true
},
{
"id": "Contact person full name",
"displayName": "Contact person full name",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true,
"removed": true
},
{
"id": "Job title",
"displayName": "Job title",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true,
"removed": true
},
{
"id": "Business email ",
"displayName": "Business email ",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true,
"removed": false
},
{
"id": "LinkedIn profile URL ",
"displayName": "LinkedIn profile URL ",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true,
"removed": true
},
{
"id": "LinkedIn company page URL",
"displayName": "LinkedIn company page URL",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true,
"removed": true
},
{
"id": "Qualification score",
"displayName": "Qualification score",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true,
"removed": true
},
{
"id": "Score reasoning",
"displayName": "Score reasoning",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true,
"removed": true
},
{
"id": "Contacted",
"displayName": "Contacted",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true,
"removed": true
},
{
"id": "Email 1 Sent Date",
"displayName": "Email 1 Sent Date",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true,
"removed": true
},
{
"id": "Email 2 Sent Date",
"displayName": "Email 2 Sent Date",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true
},
{
"id": "Email 3 Sent Date",
"displayName": "Email 3 Sent Date",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true,
"removed": true
},
{
"id": "Reply Date",
"displayName": "Reply Date",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true,
"removed": true
},
{
"id": "row_number",
"displayName": "row_number",
"required": false,
"defaultMatch": false,
"display": true,
"type": "number",
"canBeUsedToMatch": true,
"readOnly": true,
"removed": true
}
],
"attemptToConvertTypes": false,
"convertFieldsToString": false
},
"options": {}
},
"type": "n8n-nodes-base.googleSheets",
"typeVersion": 4.7,
"position": [
864,
496
],
"id": "58ed6989-3772-4514-8bdb-f4304123bef8",
"name": "Update Email 2 Sent Date",
"credentials": {
"googleSheetsOAuth2Api": {
"name": "<your credential>"
}
},
"onError": "continueRegularOutput"
},
{
"parameters": {
"sendTo": "={{ $('Check Who Needs Email 2').item.json[\"Business email \"] }}",
"subject": "={{ $json.email_subject }}",
"emailType": "text",
"message": "={{ $json.email_body }}",
"options": {
"appendAttribution": false,
"senderName": "Michael",
"replyTo": "user@example.com"
}
},
"type": "n8n-nodes-base.gmail",
"typeVersion": 2.2,
"position": [
480,
496
],
"id": "bbb4de58-7d60-4a40-8412-fa3b7ae78b85",
"name": "Send Email 2",
"credentials": {
"gmailOAuth2": {
"name": "<your credential>"
}
},
"onError": "continueRegularOutput"
},
{
"parameters": {
"jsCode": "const items = $input.all();\n\n// Calculate stats\nconst stats = {\n date: new Date().toISOString().split('T')[0],\n emails_sent: items.length,\n campaign: 'Email 3 - Final Follow-up',\n status: 'Completed',\n leads_contacted: items.map(i => i.json.company_name).join(', ')\n};\n\nconsole.log('\ud83d\udcc8 EMAIL 3 STATS:');\nconsole.log(`Date: ${stats.date}`);\nconsole.log(`Emails Sent: ${stats.emails_sent}`);\nconsole.log(`Companies: ${stats.leads_contacted}`);\n\nreturn [{ json: stats }];"
},
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
1344,
704
],
"id": "c04d9c06-a87b-4bba-9936-79efdc00b506",
"name": "Email 3 Stats Logger"
},
{
"parameters": {
"modelId": {
"__rl": true,
"value": "gpt-4.1-mini",
"mode": "list",
"cachedResultName": "GPT-4.1-MINI"
},
"responses": {
"values": [
{
"role": "system",
"content": "=You are a B2B cold email expert writing follow-up Email 3 (final follow-up) for Michael, an Automation Consultant at ObiunDigital.\n\nThis is the final follow-up that adds value by sharing a specific example.\n\nWrite a personalized final follow-up that:\n- Is brief and value-focused (under 120 words)\n- Shares a specific, relevant automation example for their industry\n- Gives them clear next steps (share example OR leave it)\n- Respects their time and decision\n- Uses proper paragraph breaks (empty lines between paragraphs)\n\nIMPORTANT: Format with proper line breaks (\\n\\n between paragraphs).\n\n\nOutput ONLY valid JSON with this structure:\n{\n \"subject\": \"Email subject line (max 50 chars)\",\n \"body\": \"Full email body with proper line breaks\"\n}"
},
{
"content": "=Write Email 3 (final follow-up) for this lead:\n\nCompany: {{ $json.company_name }}\nContact: {{ $json.first_name }} {{ $json.job_title }}\nIndustry: {{ $json.industry }}\nEmployees: {{ $json.num_employees }}\n\nWhy they're qualified: {{ $json.qualification_reasoning }}\n\nUse this template structure but personalize heavily:\n\nSubject: One example that might be relevant for {Company Name}\n\nHi {First Name},\n\nI didn't want to follow up without adding value, so I'll keep this brief.\n\nIn teams similar to {Company Name}, one of the most common automation wins we see is around {insert highly relevant process based on their industry: e.g. lead handling, internal approvals, reporting, handovers}.\n\nThis typically removes several hours of manual work per week, without changing existing tools or workflows.\n\nThis may or may not be relevant for you \u2014 but if it is, I'm happy to walk you through the logic behind it so you can decide for yourself whether it's worth exploring.\n\nWould you like me to:\n\u2022 share the example, or\n\u2022 leave it here for now?\n\nBest regards,\n\nMichael\nAutomation Consultant\nObiunDigital\n"
}
]
},
"builtInTools": {},
"options": {}
},
"type": "@n8n/n8n-nodes-langchain.openAi",
"typeVersion": 2.1,
"position": [
-496,
704
],
"id": "5c15e73f-3694-454b-bfd9-58ffab913e01",
"name": "Generate Personalized Email 3",
"credentials": {
"openAiApi": {
"name": "<your credential>"
}
},
"onError": "continueRegularOutput"
},
{
"parameters": {
"jsCode": "const items = $input.all();\nconst results = [];\n\nfor (const item of items) {\n const now = new Date();\n \n // Get current hour in CET/CEST (Amsterdam timezone)\n const options = { timeZone: 'Europe/Amsterdam', hour: 'numeric', hour12: false };\n const hour = parseInt(new Intl.DateTimeFormat('en-US', options).format(now));\n \n // Only send between 9 AM and 4 PM CET/CEST\n if (hour >= 9 && hour < 16) {\n console.log(`\u2705 Within business hours (${hour}:00 CET). Sending Email 1 to ${item.json.company_name}`);\n results.push(item);\n } else {\n console.log(`\u23f0 Outside business hours (${hour}:00 CET). Skipping Email 1 to ${item.json.company_name}`);\n }\n}\n\nreturn results;"
},
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
752,
160
],
"id": "74dd14a5-ee2e-4176-814f-ed33c82f2cd9",
"name": "Validate Business Hours - Email 1"
},
{
"parameters": {
"jsCode": "const items = $input.all();\nconst results = [];\n\nfor (const item of items) {\n const now = new Date();\n \n // Get current hour in CET/CEST (Amsterdam timezone)\n const options = { timeZone: 'Europe/Amsterdam', hour: 'numeric', hour12: false };\n const hour = parseInt(new Intl.DateTimeFormat('en-US', options).format(now));\n \n // Only send between 9 AM and 4 PM CET/CEST\n if (hour >= 9 && hour < 16) {\n console.log(`\u2705 Within business hours (${hour}:00 CET). Sending Email 2 to ${item.json['Company name']}`);\n results.push(item);\n } else {\n console.log(`\u23f0 Outside business hours (${hour}:00 CET). Skipping Email 2 to ${item.json['Company name']}`);\n }\n}\n\nreturn results;"
},
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
256,
496
],
"id": "e05b0fd9-1e1e-4c88-acf8-88e2337a9a94",
"name": "Validate Business Hours - Email 2"
},
{
"parameters": {
"jsCode": "const items = $input.all();\nconst results = [];\n\nfor (const item of items) {\n const now = new Date();\n \n // Get current hour in CET/CEST (Amsterdam timezone)\n const options = { timeZone: 'Europe/Amsterdam', hour: 'numeric', hour12: false };\n const hour = parseInt(new Intl.DateTimeFormat('en-US', options).format(now));\n \n // Only send between 9 AM and 4 PM CET/CEST\n if (hour >= 9 && hour < 16) {\n console.log(`\u2705 Within business hours (${hour}:00 CET). Sending Email 3 to ${item.json['Company name']}`);\n results.push(item);\n } else {\n console.log(`\u23f0 Outside business hours (${hour}:00 CET). Skipping Email 3 to ${item.json['Company name']}`);\n }\n}\n\nreturn results;"
},
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
512,
704
],
"id": "8bf7755f-ae4d-4496-ae0b-24bb77d1101f",
"name": "Validate Business Hours - Email 3"
},
{
"parameters": {
"pollTimes": {
"item": [
{
"mode": "everyMinute"
}
]
},
"simple": false,
"filters": {},
"options": {}
},
"type": "n8n-nodes-base.gmailTrigger",
"typeVersion": 1.3,
"position": [
-1264,
1312
],
"id": "474a4f55-b6f5-4c2d-9268-1c1cb0c6ca08",
"name": "New Email Received",
"credentials": {
"gmailOAuth2": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"jsCode": "const items = $input.all();\nconst results = [];\n\nfor (const item of items) {\n const email = item.json;\n \n // When \"Simplify\" is disabled, we get these fields\n const fromData = email.from || {};\n const toData = email.to || {};\n \n // Extract sender email and name\n let senderEmail = '';\n let senderName = '';\n \n if (fromData.value && Array.isArray(fromData.value) && fromData.value.length > 0) {\n senderEmail = (fromData.value[0].address || '').toLowerCase();\n senderName = fromData.value[0].name || '';\n }\n \n // Extract subject\n const subject = email.subject || '';\n \n // FULL EMAIL BODY - this is the key field!\n const fullBody = email.text || email.textPlain || '';\n \n // HTML version if needed\n const htmlBody = email.textAsHtml || '';\n \n // Date\n const receivedDate = email.date || new Date().toISOString();\n \n // Message ID for threading\n const messageId = email.messageId || email.id || '';\n \n console.log(`\ud83d\udce7 ============= NEW REPLY RECEIVED =============`);\n console.log(`\ud83d\udce7 From: ${senderName} <${senderEmail}>`);\n console.log(`\ud83d\udce7 Subject: ${subject}`);\n console.log(`\ud83d\udce7 Body Length: ${fullBody.length} characters`);\n console.log(`\ud83d\udce7 Preview: ${fullBody.substring(0, 200)}...`);\n console.log(`\ud83d\udce7 ===============================================`);\n \n results.push({\n json: {\n sender_email: senderEmail,\n sender_name: senderName,\n subject: subject,\n body: fullBody, // FULL BODY - no snippet!\n body_html: htmlBody,\n received_date: receivedDate,\n message_id: messageId,\n thread_id: email.threadId || '',\n \n // Keep original from/to for reference\n original_from: fromData.text || '',\n original_to: toData.text || ''\n }\n });\n}\n\nconsole.log(`\u2705 Processed ${results.length} email(s)`);\nreturn results;"
},
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
-1040,
1312
],
"id": "7c0f2ddc-0a96-4946-8cf1-081f4e8532ad",
"name": "Extract Reply Details"
},
{
"parameters": {
"conditions": {
"options": {
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.
gmailOAuth2googleSheetsOAuth2ApiopenAiApi
For the full experience including quality scoring and batch install features for each workflow upgrade to Pro
How this works
This workflow automates the creation and sending of personalised follow-up emails to leads, ensuring each message feels tailored and timely to boost response rates and nurture relationships effectively. It's designed for sales or marketing professionals managing outreach campaigns who want to scale efforts without sacrificing authenticity. The key step involves using OpenAI to generate custom email content based on lead data pulled from Google Sheets, followed by dispatching via Gmail with built-in random delays to mimic natural communication patterns.
Use this when running daily nurture sequences for cold leads, especially in B2B scenarios where consistent follow-ups convert better than sporadic ones. Avoid it for high-volume blasts needing immediate delivery, as the capping at 10 leads per day prevents spam flags and maintains quality. Common variations include adjusting the lead limit for smaller teams or integrating additional data sources like CRM exports for richer personalisation.
About this workflow
Personalized Outreach & Follow-Up - Phase 2. Uses googleSheets, openAi, gmail, gmailTrigger. Scheduled trigger; 59 nodes.
Source: https://github.com/tabii-dev/n8n-Portfolio/blob/main/personalized-email-outreach/workflow.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.
Complete AI-powered sales system Automates lead capture, qualification, and follow-up from multiple channels. AI INTELLIGENCE:
This advanced workflow automates brand monitoring and media coverage tracking for musicians, bands, and music labels. The system uses multiple search queries (dorky) to discover mentions across the we
Stop wasting billable hours on manual time-tracking. AutoTimesheet Pro uses AI to collect emails, meetings, and GitHub work, then writes a clean timesheet straight into Google Sheets. Perfect for deve
This workflow finds local business leads on Google Maps, enriches them with website and AI analysis, and sends personalized cold emails to qualified prospects automatically.
Imagine a dedicated financial expert tirelessly working behind the scenes, sifting through every transaction, every investment move, and every accounting entry. That's exactly what this automated syst