This workflow corresponds to n8n.io template #15108 — we link there as the canonical source.
This workflow follows the Gmail → Google Calendar 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": "bMqCCMpHCMnY9VeR",
"meta": {
"templateCredsSetupCompleted": true
},
"name": "Predict No-show Risk and Route Reminders from Google Calendar Bookings",
"tags": [],
"nodes": [
{
"id": "b40b46db-8de0-488b-a1af-33583e4161c0",
"name": "README",
"type": "n8n-nodes-base.stickyNote",
"position": [
1312,
-192
],
"parameters": {
"width": 588,
"height": 396,
"content": "## Predict No-show Risk and Route Reminders from Google Calendar Bookings\n\nThis workflow predicts appointment no-show risk using historical customer data and sends differentiated reminders based on risk level.\n\n## How it works\n1. Runs daily at 9 AM to check tomorrow's bookings\n2. Fetches scheduled appointments from Google Calendar\n3. Looks up each customer's history from Google Sheets\n4. Calculates a risk score (0-100) from four weighted signals\n5. Routes to three paths based on risk level\n\nSuper High (>=70): Slack alert + AI re-confirmation email\nHigh (>=40): AI-generated friendly reminder email\nLow (<40): Silent log for records\n\n"
},
"typeVersion": 1
},
{
"id": "f7ac8389-e75d-4799-9082-43d4938e0085",
"name": "Trigger & Configuration",
"type": "n8n-nodes-base.stickyNote",
"position": [
1920,
-192
],
"parameters": {
"color": 7,
"width": 380,
"height": 540,
"content": "## Trigger & Configuration\n\nRuns daily at 9 AM.\nAll settings managed in Set Configuration."
},
"typeVersion": 1
},
{
"id": "cca5d574-b120-45a7-9925-8f7d6f13d025",
"name": "Data Collection",
"type": "n8n-nodes-base.stickyNote",
"position": [
2320,
-192
],
"parameters": {
"color": 7,
"width": 580,
"height": 540,
"content": "## Data Collection\n\nFetches tomorrow's bookings from Google Calendar.\nLooks up each customer's history from Google Sheets."
},
"typeVersion": 1
},
{
"id": "ee60caa9-5f9e-4310-8139-f7c1225afb17",
"name": "Risk Assessment",
"type": "n8n-nodes-base.stickyNote",
"position": [
2928,
-192
],
"parameters": {
"color": 7,
"height": 540,
"content": "## Risk Assessment\n\nCalculates score (0-100)\nfrom 4 weighted signals:\n- No-show rate (40%)\n- Lead time (20%)\n- Day/time pattern (20%)\n- Recent cancellations (20%)"
},
"typeVersion": 1
},
{
"id": "b8cd812d-765b-4bd2-ab03-501267182d38",
"name": "Risk Routing",
"type": "n8n-nodes-base.stickyNote",
"position": [
3184,
-192
],
"parameters": {
"color": 7,
"width": 200,
"height": 540,
"content": "## Risk Routing\n\nRoutes by score:\n- Super High (>=70)\n- High (>=40)\n- Low (<40)"
},
"typeVersion": 1
},
{
"id": "5c4ffe28-858f-4aef-afbb-bfff57954b51",
"name": "Super High Response",
"type": "n8n-nodes-base.stickyNote",
"position": [
3408,
-192
],
"parameters": {
"color": 7,
"width": 640,
"height": 336,
"content": "## Super High (>=70)\n\nSlack alert + re-confirmation email."
},
"typeVersion": 1
},
{
"id": "5c3e5413-89a3-4e17-b172-c55fd753077a",
"name": "High Response",
"type": "n8n-nodes-base.stickyNote",
"position": [
3408,
160
],
"parameters": {
"color": 7,
"width": 640,
"height": 272,
"content": "## High (>=40)\n\nAI reminder email to customer."
},
"typeVersion": 1
},
{
"id": "06c3a52b-2a77-4eea-9349-de20d07c65ed",
"name": "Low Response",
"type": "n8n-nodes-base.stickyNote",
"position": [
3408,
464
],
"parameters": {
"color": 7,
"width": 256,
"height": 304,
"content": "## Low (<40)\n\nSilent log to Google Sheets."
},
"typeVersion": 1
},
{
"id": "7ac2141d-7f2d-46f1-9f94-edf37eec0f81",
"name": "Daily at 9 AM",
"type": "n8n-nodes-base.scheduleTrigger",
"position": [
1968,
48
],
"parameters": {
"rule": {
"interval": [
{
"triggerAtHour": 9
}
]
}
},
"typeVersion": 1.3
},
{
"id": "40791af4-a6f3-4943-8e4b-5fa957990c16",
"name": "Set Configuration",
"type": "n8n-nodes-base.set",
"position": [
2144,
48
],
"parameters": {
"options": {},
"assignments": {
"assignments": [
{
"id": "cfg-1",
"name": "calendar_id",
"type": "string",
"value": "primary"
},
{
"id": "cfg-2",
"name": "history_sheet_id",
"type": "string",
"value": "REPLACE_WITH_YOUR_SHEET_ID"
},
{
"id": "cfg-3",
"name": "history_sheet_name",
"type": "string",
"value": "customer_history"
},
{
"id": "cfg-4",
"name": "manager_slack_channel",
"type": "string",
"value": "#booking-alerts"
},
{
"id": "cfg-5",
"name": "super_high_threshold",
"type": "number",
"value": 70
},
{
"id": "cfg-6",
"name": "high_threshold",
"type": "number",
"value": 40
}
]
}
},
"typeVersion": 3.4
},
{
"id": "fee3e1b0-b797-4870-b907-b39fc5ac49ee",
"name": "Get Tomorrow's Bookings",
"type": "n8n-nodes-base.googleCalendar",
"position": [
2384,
48
],
"parameters": {
"options": {},
"timeMax": "={{ $now.plus(1, 'day').endOf('day').toISO() }}",
"timeMin": "={{ $now.plus(1, 'day').startOf('day').toISO() }}",
"calendar": {
"__rl": true,
"mode": "id",
"value": "={{ $json.calendar_id }}"
},
"operation": "getAll",
"returnAll": true
},
"typeVersion": 1.3
},
{
"id": "2606e518-fdf8-431e-8021-1711d3a4f165",
"name": "Extract Customer Info",
"type": "n8n-nodes-base.set",
"position": [
2576,
48
],
"parameters": {
"options": {},
"assignments": {
"assignments": [
{
"id": "ex-1",
"name": "customer_email",
"type": "string",
"value": "={{ $json.attendees ? $json.attendees[0].email : ($json.creator ? $json.creator.email : '') }}"
},
{
"id": "ex-2",
"name": "customer_name",
"type": "string",
"value": "={{ $json.attendees && $json.attendees[0].displayName ? $json.attendees[0].displayName : $json.summary }}"
},
{
"id": "ex-3",
"name": "event_id",
"type": "string",
"value": "={{ $json.id }}"
},
{
"id": "ex-4",
"name": "start_time",
"type": "string",
"value": "={{ $json.start.dateTime }}"
},
{
"id": "ex-5",
"name": "summary",
"type": "string",
"value": "={{ $json.summary }}"
},
{
"id": "ex-6",
"name": "config_sheet_id",
"type": "string",
"value": "={{ $('Set Configuration').item.json.history_sheet_id }}"
},
{
"id": "ex-7",
"name": "config_sheet_name",
"type": "string",
"value": "={{ $('Set Configuration').item.json.history_sheet_name }}"
},
{
"id": "ex-8",
"name": "config_slack_channel",
"type": "string",
"value": "={{ $('Set Configuration').item.json.manager_slack_channel }}"
}
]
}
},
"typeVersion": 3.4
},
{
"id": "378138f1-3e6a-4562-8836-5d3546f7bb14",
"name": "Lookup Customer History",
"type": "n8n-nodes-base.googleSheets",
"onError": "continueRegularOutput",
"maxTries": 3,
"position": [
2768,
48
],
"parameters": {
"options": {},
"filtersUI": {
"values": [
{
"lookupValue": "={{ $json.customer_email }}",
"lookupColumn": "customer_email"
}
]
},
"sheetName": {
"__rl": true,
"mode": "name",
"value": "={{ $json.config_sheet_name }}"
},
"documentId": {
"__rl": true,
"mode": "id",
"value": "={{ $json.config_sheet_id }}"
}
},
"retryOnFail": true,
"typeVersion": 4.7
},
{
"id": "f4204ea2-9524-411c-a542-748ccba61eea",
"name": "Calculate Risk Score",
"type": "n8n-nodes-base.code",
"position": [
2992,
48
],
"parameters": {
"jsCode": "// Risk Score Calculator\n// Combines 4 weighted indicators into a 0-100 score\nconst booking = $input.first().json;\n\nconst customer_email = booking.customer_email || '';\nconst customer_name = booking.customer_name || 'Customer';\nconst event_id = booking.event_id || '';\nconst start_time = booking.start_time || '';\nconst summary = booking.summary || 'Appointment';\nconst config_slack_channel = booking.config_slack_channel || '#booking-alerts';\n\nconst total_bookings = Number(booking.total_bookings) || 0;\nconst no_show_count = Number(booking.no_show_count) || 0;\nconst last_booking_date = booking.last_booking_date || null;\nconst last_status = booking.last_status || null;\n\n// Signal 1: No-show rate (40%)\nlet noShowRateScore = 0;\nif (total_bookings > 0) {\n const rate = no_show_count / total_bookings;\n noShowRateScore = Math.min(rate * 100, 100) * 0.4;\n} else {\n noShowRateScore = 15;\n}\n\n// Signal 2: Lead time (20%)\nlet leadTimeScore = 10;\ntry {\n const appt = new Date(start_time);\n const now = new Date();\n const leadDays = (appt - now) / (1000 * 60 * 60 * 24);\n if (leadDays > 14) leadTimeScore = 20;\n else if (leadDays > 7) leadTimeScore = 15;\n else if (leadDays > 3) leadTimeScore = 10;\n else leadTimeScore = 5;\n} catch (e) {}\n\n// Signal 3: Day/time pattern (20%)\nlet timeSlotScore = 5;\ntry {\n const appt = new Date(start_time);\n const dow = appt.getDay();\n const hour = appt.getHours();\n if ((dow === 1 && hour >= 9 && hour <= 11) ||\n (dow === 5 && hour >= 17 && hour <= 20)) {\n timeSlotScore = 20;\n } else if (dow === 0 || dow === 6) {\n timeSlotScore = 10;\n }\n} catch (e) {}\n\n// Signal 4: Recent cancellations (20%)\nlet recentCancelScore = 0;\nif (last_status === 'no_show' || last_status === 'cancelled') {\n if (last_booking_date) {\n const last = new Date(last_booking_date);\n const daysSince = (new Date() - last) / (1000 * 60 * 60 * 24);\n if (daysSince < 30) recentCancelScore = 20;\n else if (daysSince < 90) recentCancelScore = 10;\n else recentCancelScore = 5;\n } else {\n recentCancelScore = 10;\n }\n}\n\nconst risk_score = Math.round(\n noShowRateScore + leadTimeScore + timeSlotScore + recentCancelScore\n);\n\nlet risk_level;\nif (risk_score >= 70) risk_level = 'super_high';\nelse if (risk_score >= 40) risk_level = 'high';\nelse risk_level = 'low';\n\nreturn {\n json: {\n customer_email,\n customer_name,\n event_id,\n start_time,\n summary,\n total_bookings,\n no_show_count,\n risk_score,\n risk_level,\n config_slack_channel,\n score_breakdown: {\n no_show_rate: Math.round(noShowRateScore),\n lead_time: leadTimeScore,\n time_slot: timeSlotScore,\n recent_cancel: recentCancelScore\n }\n }\n};"
},
"typeVersion": 2
},
{
"id": "cdf9d307-b7f0-437d-8bbf-88b94ab1a290",
"name": "Route by Risk Level",
"type": "n8n-nodes-base.switch",
"position": [
3232,
48
],
"parameters": {
"rules": {
"values": [
{
"outputKey": "super_high",
"conditions": {
"options": {
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "sw-1",
"operator": {
"type": "string",
"operation": "equals"
},
"leftValue": "={{ $json.risk_level }}",
"rightValue": "super_high"
}
]
},
"renameOutput": true
},
{
"outputKey": "high",
"conditions": {
"options": {
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "sw-2",
"operator": {
"type": "string",
"operation": "equals"
},
"leftValue": "={{ $json.risk_level }}",
"rightValue": "high"
}
]
},
"renameOutput": true
},
{
"outputKey": "low",
"conditions": {
"options": {
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "sw-3",
"operator": {
"type": "string",
"operation": "equals"
},
"leftValue": "={{ $json.risk_level }}",
"rightValue": "low"
}
]
},
"renameOutput": true
}
]
},
"options": {}
},
"typeVersion": 3.4
},
{
"id": "9a978253-1afe-44c0-b1f1-5c93401872b6",
"name": "Generate Urgent Message",
"type": "@n8n/n8n-nodes-langchain.openAi",
"position": [
3472,
-96
],
"parameters": {
"modelId": {
"__rl": true,
"mode": "list",
"value": "gpt-4o-mini",
"cachedResultName": "gpt-4o-mini"
},
"options": {},
"responses": {
"values": [
{
"role": "system",
"content": "You are a polite scheduling assistant. Write a brief re-confirmation email that asks the customer to confirm attendance. Warm, clear, under 5 sentences. Plain text only."
},
{
"content": "=Customer: {{ $('Calculate Risk Score').item.json.customer_name }}\nAppointment: {{ $('Calculate Risk Score').item.json.summary }}\nTime: {{ $('Calculate Risk Score').item.json.start_time }}\n\nDraft the re-confirmation email body now."
}
]
},
"builtInTools": {}
},
"typeVersion": 2.1
},
{
"id": "4a3132d2-b15c-4f6b-bcdf-e3d4a10229f1",
"name": "Send Slack Alert",
"type": "n8n-nodes-base.slack",
"position": [
3856,
-176
],
"parameters": {
"text": "=High no-show risk detected\n\n- Customer: {{ $('Calculate Risk Score').item.json.customer_name }}\n- Appointment: {{ $('Calculate Risk Score').item.json.summary }}\n- Time: {{ $('Calculate Risk Score').item.json.start_time }}\n- Risk score: {{ $('Calculate Risk Score').item.json.risk_score }}/100\n\nA re-confirmation email has been sent automatically.",
"select": "channel",
"channelId": {
"__rl": true,
"mode": "name",
"value": "={{ $('Calculate Risk Score').item.json.config_slack_channel }}"
},
"otherOptions": {}
},
"typeVersion": 2.4
},
{
"id": "a2a357d2-44b2-4480-8709-01c405d2c540",
"name": "Send Re-confirmation Email",
"type": "n8n-nodes-base.gmail",
"position": [
3856,
-16
],
"parameters": {
"sendTo": "={{ $('Calculate Risk Score').item.json.customer_email }}",
"message": "={{ $json.content }}",
"options": {
"appendAttribution": false
},
"subject": "=Please confirm your appointment on {{ $('Calculate Risk Score').item.json.start_time }}",
"emailType": "text"
},
"typeVersion": 2.2
},
{
"id": "ea721c09-b85e-4419-843b-e0e702f99b5a",
"name": "Generate Reminder",
"type": "@n8n/n8n-nodes-langchain.openAi",
"position": [
3472,
272
],
"parameters": {
"modelId": {
"__rl": true,
"mode": "list",
"value": "gpt-4o-mini",
"cachedResultName": "gpt-4o-mini"
},
"options": {},
"responses": {
"values": [
{
"role": "system",
"content": "You are a friendly scheduling assistant. Write a short reminder email in warm, casual tone. Under 4 sentences. Plain text only."
},
{
"content": "=Customer: {{ $('Calculate Risk Score').item.json.customer_name }}\nAppointment: {{ $('Calculate Risk Score').item.json.summary }}\nTime: {{ $('Calculate Risk Score').item.json.start_time }}\n\nDraft the reminder email body now."
}
]
},
"builtInTools": {}
},
"typeVersion": 2.1
},
{
"id": "f6223e65-e6e9-42a5-9a97-8288f82f1167",
"name": "Send Reminder Email",
"type": "n8n-nodes-base.gmail",
"position": [
3856,
272
],
"parameters": {
"sendTo": "={{ $('Calculate Risk Score').item.json.customer_email }}",
"message": "={{ $json.content }}",
"options": {
"appendAttribution": false
},
"subject": "=Reminder: your appointment on {{ $('Calculate Risk Score').item.json.start_time }}",
"emailType": "text"
},
"typeVersion": 2.2
},
{
"id": "077732fb-72e0-4f0c-9593-525ae39aff43",
"name": "Log Low-Risk Booking",
"type": "n8n-nodes-base.googleSheets",
"position": [
3472,
592
],
"parameters": {
"columns": {
"value": {
"last_status": "confirmed",
"customer_name": "={{ $json.customer_name }}",
"no_show_count": "={{ $json.no_show_count || 0 }}",
"customer_email": "={{ $json.customer_email }}",
"total_bookings": "={{ ($json.total_bookings || 0) + 1 }}",
"last_booking_date": "={{ $json.start_time.substring(0,10) }}"
},
"mappingMode": "defineBelow",
"matchingColumns": []
},
"options": {},
"operation": "append",
"sheetName": {
"__rl": true,
"mode": "name",
"value": "={{ $('Set Configuration').item.json.history_sheet_name }}"
},
"documentId": {
"__rl": true,
"mode": "id",
"value": "={{ $('Set Configuration').item.json.history_sheet_id }}"
}
},
"typeVersion": 4.7
},
{
"id": "22f0842a-ae35-49cb-ae9b-f187fca0dfe2",
"name": "Sticky Note",
"type": "n8n-nodes-base.stickyNote",
"position": [
1312,
224
],
"parameters": {
"width": 592,
"height": 240,
"content": "## Setup steps\n1. Create Google Sheets with customer_history tab\n (customer_email, customer_name, total_bookings, no_show_count, last_booking_date, last_status)\n2. Open Set Configuration and fill in Sheet ID and Slack channel\n3. Connect Google Calendar, Sheets, Gmail, Slack, and OpenAI credentials\n4. Activate the workflow\n\nNote: Risk scoring is 100% rule-based.\nAI is used only for generating reminder text, never for the risk judgment itself."
},
"typeVersion": 1
}
],
"active": false,
"settings": {
"binaryMode": "separate",
"executionOrder": "v1"
},
"versionId": "c5856c2c-4365-4e9f-bd9e-fb87da5fa52a",
"connections": {
"Daily at 9 AM": {
"main": [
[
{
"node": "Set Configuration",
"type": "main",
"index": 0
}
]
]
},
"Generate Reminder": {
"main": [
[
{
"node": "Send Reminder Email",
"type": "main",
"index": 0
}
]
]
},
"Set Configuration": {
"main": [
[
{
"node": "Get Tomorrow's Bookings",
"type": "main",
"index": 0
}
]
]
},
"Route by Risk Level": {
"main": [
[
{
"node": "Generate Urgent Message",
"type": "main",
"index": 0
}
],
[
{
"node": "Generate Reminder",
"type": "main",
"index": 0
}
],
[
{
"node": "Log Low-Risk Booking",
"type": "main",
"index": 0
}
]
]
},
"Calculate Risk Score": {
"main": [
[
{
"node": "Route by Risk Level",
"type": "main",
"index": 0
}
]
]
},
"Extract Customer Info": {
"main": [
[
{
"node": "Lookup Customer History",
"type": "main",
"index": 0
}
]
]
},
"Generate Urgent Message": {
"main": [
[
{
"node": "Send Slack Alert",
"type": "main",
"index": 0
},
{
"node": "Send Re-confirmation Email",
"type": "main",
"index": 0
}
]
]
},
"Get Tomorrow's Bookings": {
"main": [
[
{
"node": "Extract Customer Info",
"type": "main",
"index": 0
}
]
]
},
"Lookup Customer History": {
"main": [
[
{
"node": "Calculate Risk Score",
"type": "main",
"index": 0
}
]
]
}
}
}
For the full experience including quality scoring and batch install features for each workflow upgrade to Pro
About this workflow
Business owners and service providers who want to reduce no-show rates for appointments booked via Google Calendar.
Source: https://n8n.io/workflows/15108/ — 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.
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
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
Imagine your recruitment process transformed into a sleek, efficient, AI-powered assembly line for talent. That's exactly what this system creates. It automates the heavy lifting, allowing your human
Property management companies, building managers, and inspection teams who want to automate recurring property inspections, improve issue tracking, and streamline reporting.
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