This workflow corresponds to n8n.io template #8043 — we link there as the canonical source.
This workflow follows the Agent → 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 →
{
"id": "SAqkCDqXBdN0UN0F",
"meta": {
"templateCredsSetupCompleted": true
},
"name": "Dental Clinic Receptionist",
"tags": [],
"nodes": [
{
"id": "6cb4cb42-b5e8-446d-9711-d72649c8b6cc",
"name": "Check Availability",
"type": "n8n-nodes-base.googleCalendarTool",
"position": [
624,
928
],
"parameters": {
"options": {},
"timeMax": "={{ /*n8n-auto-generated-fromAI-override*/ $fromAI('End_Time', ``, 'string') }}",
"timeMin": "={{ /*n8n-auto-generated-fromAI-override*/ $fromAI('Start_Time', ``, 'string') }}",
"calendar": {
"__rl": true,
"mode": "list",
"value": "user@example.com",
"cachedResultName": "n8n"
},
"resource": "calendar"
},
"credentials": {
"googleCalendarOAuth2Api": {
"name": "<your credential>"
}
},
"typeVersion": 1.3
},
{
"id": "8b070be5-d1b2-4f02-99a4-6278450b9fe7",
"name": "Webhook",
"type": "n8n-nodes-base.webhook",
"position": [
304,
656
],
"parameters": {
"path": "getInput",
"options": {},
"httpMethod": "POST",
"responseMode": "responseNode"
},
"typeVersion": 2.1
},
{
"id": "69b5f76a-3c63-4e0c-b4cb-d52e7dc57cbf",
"name": "Respond to Webhook",
"type": "n8n-nodes-base.respondToWebhook",
"position": [
912,
656
],
"parameters": {
"options": {}
},
"typeVersion": 1.4
},
{
"id": "b55f6c71-39df-46f8-8439-2f6e3e125466",
"name": "Window Buffer Memory",
"type": "@n8n/n8n-nodes-langchain.memoryBufferWindow",
"position": [
400,
912
],
"parameters": {
"sessionKey": "={{ $json.body.chatId }}",
"sessionIdType": "customKey",
"contextWindowLength": 100
},
"typeVersion": 1.3
},
{
"id": "d48200d0-fbf4-4cef-bcd9-f0d9a6b66579",
"name": "OpenRouter Chat Model",
"type": "@n8n/n8n-nodes-langchain.lmChatOpenRouter",
"position": [
240,
896
],
"parameters": {
"model": "openai/gpt-4o-mini",
"options": {}
},
"credentials": {
"openRouterApi": {
"name": "<your credential>"
}
},
"typeVersion": 1
},
{
"id": "39947ea3-452c-4c93-bdaa-9639a16a574a",
"name": "AI Agent",
"type": "@n8n/n8n-nodes-langchain.agent",
"position": [
560,
656
],
"parameters": {
"text": "={{ $json.body.query }}",
"options": {
"systemMessage": "=You are a virtual assistant specializing in appointment management for Dr. Hakim. \nYour role is to manage consultations accurately, ensuring real availability while providing a smooth experience for patients.\n\nOffice Hours:\n- Monday \u2013 Friday: 9:00 AM \u2013 8:00 PM\n- Saturday: 9:00 AM \u2013 1:00 PM\n- Sunday: Closed\n- Consultation Duration: 1 hour\n- Break Between Patients: 15 minutes\n\nBooking Process:\n1. Collect Patient Information:\n - Full Name\n - Phone Number\n - Email Address\n - Desired Date and Time\n If any information is missing, politely ask the patient before proceeding.\n\n2. Availability Check:\n - Always use the \"Check Availability\" tool to verify free slots in Google Calendar.\n - If requested time overlaps with an existing appointment, suggest the next available slot.\n - Do not create or confirm an appointment until the user explicitly approves the slot.\n\n3. Booking Confirmation:\n - Only create the appointment using the \"Create Event\" tool after explicit user confirmation.\n - Do not say \"appointment booked\" unless the tool is actually used.\n - Appointment ID format: APT-YYYYMMDD-XXXXXX (6 random alphanumeric characters).\n - Event Title: Patient Name - Phone Number\n - Event Description: Include full name, phone, email, appointment ID, date & time.\n\nConfirmation Message Format:\nWhen booking is complete, return only this message:\n\u2705 Appointment Confirmed:\n- Full Name: {full_name}\n- Phone: {phone}\n- Email: {email}\n- Date: {date}\n- Time: {time}\n- Appointment ID: {appointment_id}\n\nDo NOT include:\n- Calendar event links\n- Extra text after the appointment ID\n- Any confirmation without Create Event tool usage\n\nCancellations:\nIf asked to cancel, reply: \"Please check your booking email. I cannot cancel appointments for you.\"\n\nVerification:\nIf asked to check an appointment by ID, reply: \"Check your email we sent when you booked.\"\n\nTools Available:\n- Check Availability \u2192 Verify free slots in Google Calendar\n- Create Event \u2192 Book confirmed appointment in Google Calendar\n\nCommunication Rules:\n- Always be polite, clear, and friendly.\n- Never assume missing details\u2014ask for them.\n- Never create or cancel without explicit confirmation.\n- Stick strictly to availability check and booking after confirmation.\n\nToday's Date: {{ $now }}"
},
"promptType": "=define"
},
"typeVersion": 1.7
},
{
"id": "cd82287b-0ea8-4e15-a8da-9e5c4bb7bcb2",
"name": "Code",
"type": "n8n-nodes-base.code",
"position": [
1248,
1024
],
"parameters": {
"jsCode": "// Get the AI Agent output (which is a JSON string)\nconst msg = $input.first().json.output || \"\";\n\nif (!msg) {\n throw new Error(\"AI Agent output not found.\");\n}\n\n// Parse the JSON string\nlet data;\ntry {\n data = JSON.parse(msg);\n} catch (err) {\n throw new Error(\"Failed to parse AI Agent output as JSON: \" + err.message);\n}\n\n// Fallback Appointment ID if missing\nif (!data.appointment_id || data.appointment_id === \"null\") {\n const randomId = Math.random().toString(36).substring(2, 8).toUpperCase();\n data.appointment_id = `APT-${randomId}`;\n}\n\nreturn [{ json: data }];\n"
},
"typeVersion": 2
},
{
"id": "2e634902-e4ce-4362-8ca2-5fa1bb4ebd73",
"name": "If",
"type": "n8n-nodes-base.if",
"position": [
1456,
1024
],
"parameters": {
"options": {},
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "f1cbfa92-a423-4baf-8ab1-f8cc8ab77038",
"operator": {
"type": "string",
"operation": "notEquals"
},
"leftValue": "={{ $json.patient.full_name }}",
"rightValue": "null"
}
]
}
},
"typeVersion": 2.2
},
{
"id": "cd01ecc0-431a-4c8b-a703-dbcef7075fcd",
"name": "Append row in sheet",
"type": "n8n-nodes-base.googleSheets",
"position": [
1152,
1232
],
"parameters": {
"columns": {
"value": {
"Time": "={{ $json.appointment.time }}",
"Date ": "={{ $json.appointment.date }}",
"Email": "={{ $json.patient.email }}",
"Name ": "={{ $json.patient.full_name }}",
"Phone Number": "={{ $json.patient.phone }}",
"Appointment ID": "={{ $json.appointment_id }}"
},
"schema": [
{
"id": "Appointment ID",
"type": "string",
"display": true,
"required": false,
"displayName": "Appointment ID",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Name ",
"type": "string",
"display": true,
"required": false,
"displayName": "Name ",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Email",
"type": "string",
"display": true,
"required": false,
"displayName": "Email",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Phone Number",
"type": "string",
"display": true,
"required": false,
"displayName": "Phone Number",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Date ",
"type": "string",
"display": true,
"required": false,
"displayName": "Date ",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Time",
"type": "string",
"display": true,
"required": false,
"displayName": "Time",
"defaultMatch": false,
"canBeUsedToMatch": true
}
],
"mappingMode": "defineBelow",
"matchingColumns": [],
"attemptToConvertTypes": false,
"convertFieldsToString": false
},
"options": {},
"operation": "append",
"sheetName": {
"__rl": true,
"mode": "list",
"value": "gid=0",
"cachedResultUrl": "https://docs.google.YOUR_AWS_SECRET_KEY_HERE-BSY47v486qkfGCA3gE8PKw/edit#gid=0",
"cachedResultName": "Sheet1"
},
"documentId": {
"__rl": true,
"mode": "list",
"value": "13HOoBmWcxkL8cUZtWFU1-BSY47v486qkfGCA3gE8PKw",
"cachedResultUrl": "https://docs.google.YOUR_AWS_SECRET_KEY_HERE-BSY47v486qkfGCA3gE8PKw/edit?usp=drivesdk",
"cachedResultName": "dr"
}
},
"credentials": {
"googleSheetsOAuth2Api": {
"name": "<your credential>"
}
},
"typeVersion": 4.7
},
{
"id": "ac1a8cc3-7d80-4885-8360-0dce48b2af89",
"name": "For Doctor",
"type": "n8n-nodes-base.gmail",
"position": [
1376,
1232
],
"parameters": {
"sendTo": "user@example.com",
"message": "=Hello Dr. Hakim,\nA new appointment has been scheduled. Here are the details: \nPatient Name: {{ $json['Name '] }}\nPhone Number: {{ $json['Phone Number'] }}\nEmail: {{ $json.Email }} \nAppointment Date: {{ $json['Date '] }} \nTime: {{ $json.Time }}\nAppointment ID:{{ $json['Appointment ID'] }} \nPlease check your Google Calendar for more details.",
"options": {
"appendAttribution": false
},
"subject": "New Appointment Scheduled",
"emailType": "text"
},
"credentials": {
"gmailOAuth2": {
"name": "<your credential>"
}
},
"typeVersion": 2.1
},
{
"id": "88e1c616-7014-4d49-8d32-fe9c77f85d86",
"name": "For User",
"type": "n8n-nodes-base.gmail",
"position": [
1632,
1232
],
"parameters": {
"sendTo": "={{ $('Append row in sheet').item.json.Email }}",
"message": "=Dear {{ $('Append row in sheet').item.json['Name '] }},\nYour appointment with Dr. Hakim has been successfully booked! Here are the details: \nName: {{ $('Append row in sheet').item.json['Name '] }}\nPhone Number: {{ $('Append row in sheet').item.json['Phone Number'] }}\nEmail: {{ $('Append row in sheet').item.json.Email }}\nAppointment Date: {{ $('Append row in sheet').item.json['Date '] }}\nTime: {{ $('Append row in sheet').item.json.Time }}\nAppointment ID:{{ $('Append row in sheet').item.json['Appointment ID'] }}\nPlease keep this Appointment ID for your reference. If you have any questions or need to cancel your appointment, feel free to contact us. \nBest regards, \nDr. Hakim's Clinic",
"options": {
"appendAttribution": false
},
"subject": "Appointment Confirmation \u2013 Dr. Hakim",
"emailType": "text"
},
"credentials": {
"gmailOAuth2": {
"name": "<your credential>"
}
},
"typeVersion": 2.1
},
{
"id": "7eec2403-de34-4163-8984-3c5f6be8070c",
"name": "Creat event",
"type": "n8n-nodes-base.googleCalendarTool",
"position": [
832,
912
],
"parameters": {
"end": "={{ /*n8n-auto-generated-fromAI-override*/ $fromAI('End', ``, 'string') }}",
"start": "={{ /*n8n-auto-generated-fromAI-override*/ $fromAI('Start', ``, 'string') }}",
"calendar": {
"__rl": true,
"mode": "list",
"value": "user@example.com",
"cachedResultName": "n8n"
},
"additionalFields": {}
},
"credentials": {
"googleCalendarOAuth2Api": {
"name": "<your credential>"
}
},
"typeVersion": 1.3
},
{
"id": "ab252537-767c-477d-9a0b-941bd4ed8558",
"name": "AI Agent1",
"type": "@n8n/n8n-nodes-langchain.agent",
"position": [
1168,
656
],
"parameters": {
"text": "={{$input.first().json.output}}",
"options": {
"systemMessage": "You are an Appointment Parsing Agent.\nYour job is to extract appointment details from text and return them as clean JSON.\n\n\ud83d\udd39 Rules\n\nAlways return data strictly in this JSON format:\n\n{\n \"patient\": {\n \"full_name\": \"John Doe\",\n \"phone\": \"123456789\",\n \"email\": \"user@example.com\"\n },\n \"appointment\": {\n \"date\": \"29th August 2025\",\n \"time\": \"5 PM\"\n },\n \"appointment_id\": \"APT20250831KY5T4N\"\n}\n\n\nAppointment ID:\n\nIf provided, return it exactly as-is.\n\nDo not add extra text (e.g. \"this is a unique ID for your appointment\").\n\nIf missing, generate one in this format:\n\nAPT + YYYYMMDD + 5 random alphanumeric characters\n\n\nExample: APT20250831KY5T4N\n\nAll fields must always be present in JSON:\n\npatient.full_name\n\npatient.phone\n\npatient.email\n\nappointment.date\n\nappointment.time\n\nappointment_id\n\nIf a value is missing, set it to \"null\" (string).\n\nOutput only valid JSON. No explanations, no Markdown, no extra text."
},
"promptType": "define"
},
"typeVersion": 2.2
},
{
"id": "f07a734c-618e-47a7-81d0-2a8f771f7fe1",
"name": "OpenRouter Chat Model1",
"type": "@n8n/n8n-nodes-langchain.lmChatOpenRouter",
"position": [
1040,
848
],
"parameters": {
"model": "openai/gpt-4o-mini",
"options": {}
},
"credentials": {
"openRouterApi": {
"name": "<your credential>"
}
},
"typeVersion": 1
}
],
"active": true,
"settings": {
"timezone": "Europe/Rome",
"callerPolicy": "workflowsFromSameOwner",
"executionOrder": "v1"
},
"versionId": "0fac2f0e-7ed9-4fbc-91c8-c318f819b03c",
"connections": {
"If": {
"main": [
[
{
"node": "Append row in sheet",
"type": "main",
"index": 0
}
]
]
},
"Code": {
"main": [
[
{
"node": "If",
"type": "main",
"index": 0
}
]
]
},
"Webhook": {
"main": [
[
{
"node": "AI Agent",
"type": "main",
"index": 0
}
]
]
},
"AI Agent": {
"main": [
[
{
"node": "Respond to Webhook",
"type": "main",
"index": 0
}
]
]
},
"AI Agent1": {
"main": [
[
{
"node": "Code",
"type": "main",
"index": 0
}
]
]
},
"For Doctor": {
"main": [
[
{
"node": "For User",
"type": "main",
"index": 0
}
]
]
},
"Creat event": {
"ai_tool": [
[
{
"node": "AI Agent",
"type": "ai_tool",
"index": 0
}
]
]
},
"Check Availability": {
"ai_tool": [
[
{
"node": "AI Agent",
"type": "ai_tool",
"index": 0
}
]
]
},
"Respond to Webhook": {
"main": [
[
{
"node": "AI Agent1",
"type": "main",
"index": 0
}
]
]
},
"Append row in sheet": {
"main": [
[
{
"node": "For Doctor",
"type": "main",
"index": 0
}
]
]
},
"Window Buffer Memory": {
"ai_memory": [
[
{
"node": "AI Agent",
"type": "ai_memory",
"index": 0
}
]
]
},
"OpenRouter Chat Model": {
"ai_languageModel": [
[
{
"node": "AI Agent",
"type": "ai_languageModel",
"index": 0
}
]
]
},
"OpenRouter Chat Model1": {
"ai_languageModel": [
[
{
"node": "AI Agent1",
"type": "ai_languageModel",
"index": 0
}
]
]
}
}
}
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.
gmailOAuth2googleCalendarOAuth2ApigoogleSheetsOAuth2ApiopenRouterApi
For the full experience including quality scoring and batch install features for each workflow upgrade to Pro
About this workflow
🦷 Dental Clinic Appointment Booking System (n8n)
Source: https://n8n.io/workflows/8043/ — 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.
Arvifund - Supabase (Fixed v5). Uses httpRequest, telegram, googleSheets, telegramTrigger. Event-driven trigger; 91 nodes.
Arvifund - Supabase. Uses httpRequest, telegram, googleSheets, telegramTrigger. Event-driven trigger; 90 nodes.
Arvifund - Supabase (Fixed v2). Uses httpRequest, telegram, googleSheets, telegramTrigger. Event-driven trigger; 90 nodes.
Arvifund - Supabase (Fixed v4). Uses httpRequest, telegram, googleSheets, telegramTrigger. Event-driven trigger; 90 nodes.
Arvifund - Supabase (Fixed v3). Uses httpRequest, telegram, googleSheets, telegramTrigger. Event-driven trigger; 90 nodes.