This workflow corresponds to n8n.io template #16077 — we link there as the canonical source.
This workflow follows the Gmail → Google Sheets 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 →
{
"id": "Ig4YNqfSqRV6bj8A",
"meta": {
"templateCredsSetupCompleted": true
},
"name": "AI-Powered Deal Stall Detector & Re-Engagement Sequence",
"tags": [
{
"id": "4Of2LE0HSt4qmmUG",
"name": "Ansh",
"createdAt": "2026-04-20T10:32:01.726Z",
"updatedAt": "2026-04-20T10:32:01.726Z"
}
],
"nodes": [
{
"id": "8c67a66f-735e-4b0b-ae04-eacdb0d242c9",
"name": "Main Overview Note",
"type": "n8n-nodes-base.stickyNote",
"position": [
960,
1520
],
"parameters": {
"width": 564,
"height": 708,
"content": "## AI-Powered Deal Stall Detector & Re-Engagement Sequence\nThis workflow runs daily and automatically finds deals in your HubSpot pipeline that have had no activity for 7 or more days. It uses AI to write a personalised re-engagement email for each stalled deal based on the contact's history and deal stage, sends it via Gmail, and alerts the assigned sales rep in Slack \u2014 so no deal ever silently slips away.\n\n### How it works\n\n\t\u2022\tRuns automatically every morning on a schedule.\n\t\u2022\tFetches all open deals from HubSpot and checks last activity date.\n\t\u2022\tFilters deals with no activity for 7+ days.\n\t\u2022\tSkips deals with no associated contact or missing email \u2014 logs them separately.\n\t\u2022\tAI generates a personalised re-engagement email for each stalled deal.\n\t\u2022\tSends the email to the contact via Gmail automatically.\n\t\u2022\tUpdates the HubSpot deal with a note recording the re-engagement action.\n\t\u2022\tPosts a Slack alert to the sales rep with deal context and action summary.\n\t\u2022\tLogs all re-engagement actions to a Google Sheet for tracking.\n\t\u2022\tSends a single end-of-day digest to Slack summarising all deals processed.\n\t\u2022\tCatches any errors and routes them to a separate Slack error channel.\n\n### Setup Steps\n\n\t1.\tConnect your HubSpot account in n8n credentials.\n\t2.\tConnect your Gmail account in n8n credentials.\n\t3.\tConnect your Slack account and set your sales channel name and error channel name.\n\t4.\tCreate a Google Sheet with columns: Date, Deal Name, Contact Email, Stage, Days Stalled, Email Sent, Slack Notified, Status.\n\t5.\tTurn on the workflow."
},
"typeVersion": 1
},
{
"id": "55807099-b90c-468e-8139-b4258e800c09",
"name": "Step 1 Note",
"type": "n8n-nodes-base.stickyNote",
"position": [
1552,
1520
],
"parameters": {
"color": 7,
"width": 580,
"height": 704,
"content": "## Step 1: Trigger & Fetch Stalled Deals\nEvery morning on weekdays, the workflow wakes up and pulls all open deals from HubSpot. It then checks each deal's last activity date and keeps only the ones that haven't had any contact for 7 days or more."
},
"typeVersion": 1
},
{
"id": "c146848b-810f-4ddf-bca9-9eae97759fb1",
"name": "Step 2 Validation Note",
"type": "n8n-nodes-base.stickyNote",
"position": [
2160,
1520
],
"parameters": {
"color": 7,
"width": 788,
"height": 704,
"content": "## Step 2: Contact Validation\nBefore doing any AI work or sending emails, this section checks that each stalled deal actually has a contact associated \u2014 and that the contact has a valid email address. Deals that fail either check are routed to the Skipped log so nothing crashes and nothing gets lost silently."
},
"typeVersion": 1
},
{
"id": "39b9e330-c990-4d52-8914-ec371fa07a75",
"name": "Step 3 Note",
"type": "n8n-nodes-base.stickyNote",
"position": [
2976,
1520
],
"parameters": {
"color": 7,
"width": 852,
"height": 704,
"content": "## Step 3: AI Email Generation & Sending\nFor each valid stalled deal, AI reads the contact name, company, deal stage, and deal value to write a natural, personalised re-engagement email. The email is sent directly to the contact via Gmail. The deal in HubSpot is then updated with a note recording this outreach so the team always has a full activity log in CRM."
},
"typeVersion": 1
},
{
"id": "18f7dbfa-20f7-499a-8de1-19bc866477f4",
"name": "Step 4 Note",
"type": "n8n-nodes-base.stickyNote",
"position": [
3856,
1520
],
"parameters": {
"color": 7,
"width": 452,
"height": 704,
"content": "## Step 4: Slack Alert & Activity Log\nOnce the email is sent and HubSpot is updated, a Slack message is posted to the sales channel with the deal name, contact, stage, and what action was taken. Everything is also logged in a Google Sheet so the team has a full record of all re-engagement activity."
},
"typeVersion": 1
},
{
"id": "7a45da03-ae3a-48d2-8f1b-59763ce82e00",
"name": "Step 5 Digest Note",
"type": "n8n-nodes-base.stickyNote",
"position": [
4336,
1520
],
"parameters": {
"color": 7,
"width": 532,
"height": 704,
"content": "## Step 5: End-of-Run Digest\nAfter all deals are processed, the workflow compiles a single summary message and posts it to Slack. This gives the sales manager one clean overview of everything that happened in today's run \u2014 how many deals were re-engaged, how many were skipped, and any errors \u2014 without flooding the channel with individual messages."
},
"typeVersion": 1
},
{
"id": "810a02f2-21b6-49e7-bfe1-11f35106b2b1",
"name": "Error Handler Note",
"type": "n8n-nodes-base.stickyNote",
"position": [
4896,
1520
],
"parameters": {
"color": 7,
"width": 532,
"height": 708,
"content": "## \u26a0\ufe0f Error Handling\nIf any node in this workflow fails \u2014 HubSpot API error, Gmail send failure, OpenAI timeout \u2014 the error is caught here and a detailed alert is sent to a dedicated Slack error channel. This means failures are visible immediately and don't go unnoticed until someone manually checks the n8n logs."
},
"typeVersion": 1
},
{
"id": "f5799974-c953-480f-b6a8-3c247e44cfcf",
"name": "Schedule Trigger",
"type": "n8n-nodes-base.scheduleTrigger",
"position": [
1584,
1920
],
"parameters": {
"rule": {
"interval": [
{
"field": "cronExpression",
"expression": "0 8 * * 1-5"
}
]
}
},
"typeVersion": 1.2
},
{
"id": "772dde2c-5418-4f2f-8a26-47c78fad6b38",
"name": "Get All Open Deals",
"type": "n8n-nodes-base.hubspot",
"position": [
1792,
1920
],
"parameters": {
"filters": {},
"resource": "deal",
"operation": "getAll",
"returnAll": true
},
"typeVersion": 2
},
{
"id": "7758fdc1-440b-416c-8ee0-c1f5c0082c7e",
"name": "Filter Stalled Deals",
"type": "n8n-nodes-base.code",
"position": [
2000,
1920
],
"parameters": {
"jsCode": "const now = new Date();\nconst STALL_DAYS = 7;\nconst stalledDeals = [];\n\nfor (const item of $input.all()) {\n const deal = item.json;\n const lastModified = deal.hs_lastmodifieddate || deal.notes_last_updated;\n\n if (!lastModified) {\n item.json.days_stalled = 999;\n stalledDeals.push(item);\n continue;\n }\n\n const lastActivity = new Date(lastModified);\n const diffMs = now - lastActivity;\n const diffDays = diffMs / (1000 * 60 * 60 * 24);\n\n if (diffDays >= STALL_DAYS) {\n item.json.days_stalled = Math.floor(diffDays);\n stalledDeals.push(item);\n }\n}\n\nif (stalledDeals.length === 0) {\n return [{ json: { _skip: true, message: 'No stalled deals today.', count: 0 } }];\n}\n\nreturn stalledDeals;"
},
"typeVersion": 2
},
{
"id": "22c67f66-2a37-41b1-a541-3ae77676b832",
"name": "Has Associated Contact?",
"type": "n8n-nodes-base.if",
"position": [
2192,
1920
],
"parameters": {
"options": {},
"conditions": {
"options": {
"leftValue": "",
"caseSensitive": false,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "check-contact-association",
"operator": {
"type": "number",
"operation": "gt"
},
"leftValue": "={{ $json.associations?.contacts?.results?.length ?? 0 }}",
"rightValue": 0
}
]
}
},
"typeVersion": 2.2
},
{
"id": "f65f7c7c-bca0-4377-b46c-deadcc90a46c",
"name": "Log Skipped \u2014 No Contact",
"type": "n8n-nodes-base.googleSheets",
"position": [
2528,
2048
],
"parameters": {
"columns": {
"value": {
"Date": "={{ $now.toISO() }}",
"Stage": "={{ $json.dealstage }}",
"Status": "Skipped \u2014 no contact",
"Deal Name": "={{ $json.dealname }}",
"Email Sent": "No",
"Days Stalled": "={{ $json.days_stalled }}",
"Contact Email": "N/A \u2014 no contact associated",
"Slack Notified": "No"
},
"mappingMode": "defineBelow"
},
"options": {},
"operation": "append",
"sheetName": {
"__rl": true,
"mode": "name",
"value": "Sheet1"
},
"documentId": {
"__rl": true,
"mode": "id",
"value": "REPLACE_WITH_YOUR_SHEET_ID"
}
},
"typeVersion": 4.5
},
{
"id": "40955196-7c6b-477b-b3a8-541ce5f55d1d",
"name": "Get Deal Contact",
"type": "n8n-nodes-base.hubspot",
"position": [
2416,
1840
],
"parameters": {
"contactId": "={{ $json.associations.contacts.results[0].id }}",
"operation": "get",
"additionalFields": {}
},
"typeVersion": 2
},
{
"id": "48cd5896-d4cb-48cc-b0ec-32176bfb44be",
"name": "Has Valid Email?",
"type": "n8n-nodes-base.if",
"position": [
2608,
1840
],
"parameters": {
"options": {},
"conditions": {
"options": {
"leftValue": "",
"caseSensitive": false,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "check-email-exists",
"operator": {
"type": "string",
"operation": "notEmpty"
},
"leftValue": "={{ $json.email }}",
"rightValue": ""
}
]
}
},
"typeVersion": 2.2
},
{
"id": "c580ac40-3464-42a7-af91-710dc223b8bb",
"name": "Log Skipped \u2014 No Email",
"type": "n8n-nodes-base.googleSheets",
"position": [
2800,
1904
],
"parameters": {
"columns": {
"value": {
"Date": "={{ $now.toISO() }}",
"Stage": "={{ $('Filter Stalled Deals').item.json.dealstage }}",
"Status": "Skipped \u2014 no email address",
"Deal Name": "={{ $('Filter Stalled Deals').item.json.dealname }}",
"Email Sent": "No",
"Days Stalled": "={{ $('Filter Stalled Deals').item.json.days_stalled }}",
"Contact Email": "N/A \u2014 email missing",
"Slack Notified": "No"
},
"mappingMode": "defineBelow"
},
"options": {},
"operation": "append",
"sheetName": {
"__rl": true,
"mode": "name",
"value": "Sheet1"
},
"documentId": {
"__rl": true,
"mode": "id",
"value": "REPLACE_WITH_YOUR_SHEET_ID"
}
},
"typeVersion": 4.5
},
{
"id": "f2aec0ea-6609-47c1-85cc-5148861b0cd6",
"name": "Generate Re-Engagement Email",
"type": "@n8n/n8n-nodes-langchain.openAi",
"position": [
3008,
1712
],
"parameters": {
"modelId": {
"__rl": true,
"mode": "list",
"value": ""
},
"options": {},
"messages": {
"values": [
{
"role": "system",
"content": "You are a professional B2B sales assistant. Write concise, natural re-engagement emails that don't feel automated. Keep them under 120 words. Never mention that this is an automated email. Use a warm, helpful tone. Return ONLY a JSON object with keys: subject (string) and body (string, plain text, no HTML)."
},
{
"content": "=Write a re-engagement email for this stalled deal:\n\nContact: {{ $json.firstname }} {{ $json.lastname }}\nCompany: {{ $json.company }}\nJob Title: {{ $json.jobtitle }}\nDeal Name: {{ $('Filter Stalled Deals').item.json.dealname }}\nDeal Stage: {{ $('Filter Stalled Deals').item.json.dealstage }}\nDeal Value: ${{ $('Filter Stalled Deals').item.json.amount }}\nDays Since Last Activity: {{ $('Filter Stalled Deals').item.json.days_stalled }}\n\nWrite a subject line and email body that re-opens the conversation naturally. Reference the deal stage to give context."
}
]
}
},
"typeVersion": 1.4
},
{
"id": "cba2fd7d-c2e8-4a55-9a9c-cb5e3043ed84",
"name": "Parse AI Email",
"type": "n8n-nodes-base.code",
"position": [
3312,
1712
],
"parameters": {
"jsCode": "const raw = $input.item.json.message?.content || $input.item.json.choices?.[0]?.message?.content || '';\nlet cleaned = raw.replace(/```json\\n?/g, '').replace(/```/g, '').trim();\ntry {\n const parsed = JSON.parse(cleaned);\n return [{ json: { subject: parsed.subject, body: parsed.body } }];\n} catch(e) {\n return [{ json: { subject: 'Following up on our conversation', body: cleaned } }];\n}"
},
"typeVersion": 2
},
{
"id": "08552481-3a31-42f2-ae81-a984e02cffe3",
"name": "Send Re-Engagement Email",
"type": "n8n-nodes-base.gmail",
"position": [
3504,
1712
],
"parameters": {
"sendTo": "={{ $('Get Deal Contact').item.json.email }}",
"message": "={{ $json.body }}",
"options": {},
"subject": "={{ $json.subject }}"
},
"typeVersion": 2.1
},
{
"id": "0d4b3d53-edc6-482d-9adc-7373d673fa1e",
"name": "Log Note on HubSpot Deal",
"type": "n8n-nodes-base.hubspot",
"position": [
3696,
1712
],
"parameters": {
"type": "NOTE",
"resource": "engagement",
"additionalFields": {}
},
"typeVersion": 2
},
{
"id": "ecf6a6e7-f2f1-4121-aaa2-4dd7981c216f",
"name": "Slack Alert to Sales Team",
"type": "n8n-nodes-base.slack",
"position": [
3904,
1712
],
"parameters": {
"text": "=\ud83d\udea8 *Stalled Deal Re-Engaged*\n\n*Deal:* {{ $('Filter Stalled Deals').item.json.dealname }}\n*Contact:* {{ $('Get Deal Contact').item.json.firstname }} {{ $('Get Deal Contact').item.json.lastname }} ({{ $('Get Deal Contact').item.json.email }})\n*Company:* {{ $('Get Deal Contact').item.json.company }}\n*Stage:* {{ $('Filter Stalled Deals').item.json.dealstage }}\n*Value:* ${{ $('Filter Stalled Deals').item.json.amount }}\n*Days Stalled:* {{ $('Filter Stalled Deals').item.json.days_stalled }} days\n\n\u2705 Re-engagement email sent. HubSpot note updated. Please follow up if you receive a reply.",
"select": "channel",
"channelId": {
"__rl": true,
"mode": "name",
"value": "sales-team"
},
"otherOptions": {}
},
"typeVersion": 2.3
},
{
"id": "bd53ecba-86e9-4714-ae19-b04755d557e8",
"name": "Log to Google Sheet",
"type": "n8n-nodes-base.googleSheets",
"position": [
4144,
1712
],
"parameters": {
"columns": {
"value": {
"Date": "={{ $now.toISO() }}",
"Stage": "={{ $('Filter Stalled Deals').item.json.dealstage }}",
"Status": "Re-engaged",
"Deal Name": "={{ $('Filter Stalled Deals').item.json.dealname }}",
"Email Sent": "Yes",
"Days Stalled": "={{ $('Filter Stalled Deals').item.json.days_stalled }}",
"Contact Email": "={{ $('Get Deal Contact').item.json.email }}",
"Slack Notified": "Yes"
},
"mappingMode": "defineBelow"
},
"options": {},
"operation": "append",
"sheetName": {
"__rl": true,
"mode": "name",
"value": "Sheet1"
},
"documentId": {
"__rl": true,
"mode": "id",
"value": "REPLACE_WITH_YOUR_SHEET_ID"
}
},
"typeVersion": 4.5
},
{
"id": "8c09766f-b345-4cd1-8dfe-acb656d5b6d1",
"name": "Build Daily Digest",
"type": "n8n-nodes-base.code",
"position": [
4416,
1888
],
"parameters": {
"jsCode": "const allItems = $input.all();\nconst reEngaged = allItems.filter(i => i.json.Status === 'Re-engaged').length;\nconst skipped = allItems.filter(i => i.json.Status && i.json.Status.startsWith('Skipped')).length;\nconst today = new Date().toISOString().split('T')[0];\n\nreturn [{\n json: {\n summary: `\ud83d\udcca *Daily Deal Stall Detector \u2014 Run Summary (${today})*\\n\\n\u2705 Re-engagement emails sent: ${reEngaged}\\n\u23ed\ufe0f Deals skipped (no contact / no email): ${skipped}\\n\\n_All details logged in the Google Sheet._`\n }\n}];"
},
"typeVersion": 2
},
{
"id": "096cc8e1-8584-4eda-ac32-6b6255165f78",
"name": "Send Daily Digest to Slack",
"type": "n8n-nodes-base.slack",
"position": [
4640,
1888
],
"parameters": {
"text": "={{ $json.summary }}",
"select": "channel",
"channelId": {
"__rl": true,
"mode": "name",
"value": "sales-team"
},
"otherOptions": {}
},
"typeVersion": 2.3
},
{
"id": "c4528b13-eead-4b5f-b7f3-6ff32be488ef",
"name": "Error Alert to Slack",
"type": "n8n-nodes-base.slack",
"onError": "continueRegularOutput",
"position": [
5056,
1712
],
"parameters": {
"text": "=\ud83d\udd34 *Workflow Error \u2014 Deal Stall Detector*\n\n*Error:* {{ $json.message || 'Unknown error' }}\n*Node:* {{ $json.node?.name || 'Unknown node' }}\n*Time:* {{ $now.toISO() }}\n\nPlease check your n8n execution log for full details.",
"select": "channel",
"channelId": {
"__rl": true,
"mode": "name",
"value": "n8n-errors"
},
"otherOptions": {}
},
"typeVersion": 2.3
}
],
"active": false,
"settings": {
"executionOrder": "v1"
},
"versionId": "aa1af406-4637-46ff-bf9c-63d76600878a",
"connections": {
"Parse AI Email": {
"main": [
[
{
"node": "Send Re-Engagement Email",
"type": "main",
"index": 0
}
]
]
},
"Get Deal Contact": {
"main": [
[
{
"node": "Has Valid Email?",
"type": "main",
"index": 0
}
]
]
},
"Has Valid Email?": {
"main": [
[
{
"node": "Generate Re-Engagement Email",
"type": "main",
"index": 0
}
],
[
{
"node": "Log Skipped \u2014 No Email",
"type": "main",
"index": 0
}
]
]
},
"Schedule Trigger": {
"main": [
[
{
"node": "Get All Open Deals",
"type": "main",
"index": 0
}
]
]
},
"Build Daily Digest": {
"main": [
[
{
"node": "Send Daily Digest to Slack",
"type": "main",
"index": 0
}
]
]
},
"Get All Open Deals": {
"main": [
[
{
"node": "Filter Stalled Deals",
"type": "main",
"index": 0
}
]
]
},
"Log to Google Sheet": {
"main": [
[
{
"node": "Build Daily Digest",
"type": "main",
"index": 0
}
]
]
},
"Filter Stalled Deals": {
"main": [
[
{
"node": "Has Associated Contact?",
"type": "main",
"index": 0
}
]
]
},
"Has Associated Contact?": {
"main": [
[
{
"node": "Get Deal Contact",
"type": "main",
"index": 0
}
],
[
{
"node": "Log Skipped \u2014 No Contact",
"type": "main",
"index": 0
}
]
]
},
"Log Note on HubSpot Deal": {
"main": [
[
{
"node": "Slack Alert to Sales Team",
"type": "main",
"index": 0
}
]
]
},
"Log Skipped \u2014 No Email": {
"main": [
[
{
"node": "Build Daily Digest",
"type": "main",
"index": 0
}
]
]
},
"Send Re-Engagement Email": {
"main": [
[
{
"node": "Log Note on HubSpot Deal",
"type": "main",
"index": 0
},
{
"node": "Error Alert to Slack",
"type": "main",
"index": 0
}
]
]
},
"Slack Alert to Sales Team": {
"main": [
[
{
"node": "Log to Google Sheet",
"type": "main",
"index": 0
}
]
]
},
"Log Skipped \u2014 No Contact": {
"main": [
[
{
"node": "Build Daily Digest",
"type": "main",
"index": 0
}
]
]
},
"Generate Re-Engagement Email": {
"main": [
[
{
"node": "Parse AI Email",
"type": "main",
"index": 0
}
]
]
}
}
}
For the full experience including quality scoring and batch install features for each workflow upgrade to Pro
About this workflow
This workflow runs every weekday morning to find HubSpot deals with no recent activity, uses OpenAI to generate a personalized re-engagement email, sends it via Gmail, logs the outcome to Google Sheets, and notifies your team in Slack with per-deal alerts and a daily digest.…
Source: https://n8n.io/workflows/16077/ — 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.
This workflow runs on a daily schedule to analyze all Closed–Lost deals from your CRM and uncover the true reason behind each loss. It uses AI to classify the primary loss category, generate a confide
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
This workflow triggers on HubSpot dealstage changes, pulls full deal, contact, and owner details, uses OpenAI to generate a concise Slack-ready update with next steps, notifies the right Slack channel
Property management companies, building managers, and inspection teams who want to automate recurring property inspections, improve issue tracking, and streamline reporting.
Business owners and service providers who want to reduce no-show rates for appointments booked via Google Calendar.