This workflow corresponds to n8n.io template #13960 — 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": "qTRNRt0PTo2NyObN",
"meta": {
"templateCredsSetupCompleted": true
},
"name": "Healthcare - Appointment Booking Agent",
"tags": [],
"nodes": [
{
"id": "1bba7f53-0f2d-4524-b568-4c50ff57a7f1",
"name": "\ud83d\udccb Workflow Overview",
"type": "n8n-nodes-base.stickyNote",
"position": [
-1136,
1424
],
"parameters": {
"color": 3,
"width": 500,
"height": 940,
"content": "## \ud83c\udfe5 Healthcare Appointment Booking Agent\n\n### How it works\nThis workflow automates the end-to-end healthcare appointment lifecycle across three parallel pipelines:\n\n**Pipeline 1 \u2013 Patient Intake & Triage:** A webhook receives new patient lead data (name, age, gender, symptoms). The data is formatted, then passed to an AI Triage Agent (Azure OpenAI) that assesses urgency level (LOW / MEDIUM / HIGH / CRITICAL), recommends a department, and sets appointment priority. Normalized results are saved to a Google Sheet (\"Patient forms\" tab).\n\n**Pipeline 2 \u2013 Doctor Assignment:** A Google Sheets trigger fires whenever a new patient row is added. A second AI Agent reads the doctor list from the sheet and assigns the most suitable doctor based on symptoms and specialty. The assignment is written back to the \"Scheduled appointments\" tab. Appointment details are then fetched and an HTML email digest is sent to the assigned doctor.\n\n**Pipeline 3 \u2013 Post-Visit Feedback:** An hourly Schedule Trigger reads the appointments sheet, filters records that have a completed visit flag, and sends a personalized feedback-form email to each patient.\n\n### Setup steps\n1. **Webhook** \u2013 Activate the workflow; note the webhook URL and configure your patient intake form to POST to it.\n2. **Azure OpenAI** \u2013 Add your Azure OpenAI credentials to both AI Agent nodes (model: `gpt-4o-mini`).\n3. **Google Sheets** \u2013 Connect Google Sheets OAuth2 to all sheet nodes. Create a spreadsheet with three tabs: *Patient forms*, *Doctor details*, and *Scheduled appointments*.\n4. **Gmail** \u2013 Connect Gmail OAuth2 to both email nodes and update the `sendTo` addresses.\n5. **Schedule Trigger** \u2013 Adjust the hourly interval to match your clinic's feedback cadence.\n\n### Customization\n- Swap Azure OpenAI for any other LLM by replacing the Chat Model sub-node.\n- Add a Slack or WhatsApp node after doctor assignment for real-time staff alerts."
},
"typeVersion": 1
},
{
"id": "ac280c74-63ef-48d7-a875-a195e7e86d58",
"name": "Section: Patient Intake & Triage",
"type": "n8n-nodes-base.stickyNote",
"position": [
-512,
1040
],
"parameters": {
"width": 760,
"height": 260,
"content": "## \ud83d\udce5 Patient Intake & Triage\nReceives raw patient lead via webhook, normalizes fields, and runs AI triage to determine urgency level, recommended department, and appointment priority. Results are saved to the Patient forms sheet."
},
"typeVersion": 1
},
{
"id": "5defe252-87c6-47c1-ad25-719918a0e3dc",
"name": "Section: Doctor Assignment & Notification",
"type": "n8n-nodes-base.stickyNote",
"position": [
-544,
1568
],
"parameters": {
"width": 860,
"height": 560,
"content": "## \ud83e\ude7a Doctor Assignment\nTriggered when a new patient row lands in Google Sheets. An AI Agent consults the doctor list and assigns the best-matched specialist. The appointment is written back to the sheet and a schedule digest email is sent to the doctor."
},
"typeVersion": 1
},
{
"id": "8ee3e637-1a7f-496a-931b-8031836c77ad",
"name": "Section: Post-Visit Feedback",
"type": "n8n-nodes-base.stickyNote",
"position": [
-464,
2336
],
"parameters": {
"width": 620,
"height": 200,
"content": "## \ud83d\udcec Post-Visit Feedback\nRuns hourly via Schedule Trigger. Fetches appointments with a completed visit flag and sends each patient a personalized feedback form email."
},
"typeVersion": 1
},
{
"id": "0632ebb3-6f43-4c29-b7c2-414e5694321a",
"name": "\u26a0\ufe0f Warning: Azure OpenAI Credentials",
"type": "n8n-nodes-base.stickyNote",
"position": [
544,
1568
],
"parameters": {
"color": 2,
"width": 280,
"height": 176,
"content": "\u26a0\ufe0f **Azure OpenAI Credentials Required**\nBoth AI Agent nodes depend on this model. Ensure your Azure OpenAI API key, endpoint, and deployment name are correctly configured. Misconfiguration will silently break triage and doctor assignment."
},
"typeVersion": 1
},
{
"id": "a2fbc4ad-9ebc-40f8-ab53-8557e165581f",
"name": "\u26a0\ufe0f Warning: Sheet Overwrite Risk",
"type": "n8n-nodes-base.stickyNote",
"position": [
560,
2080
],
"parameters": {
"color": 2,
"width": 280,
"height": 144,
"content": "\u26a0\ufe0f **Google Sheets Write Risk**\nThis node appends or overwrites rows in the Scheduled Appointments sheet. Ensure column headers in the sheet exactly match the mapped fields to avoid data loss or misalignment."
},
"typeVersion": 1
},
{
"id": "de14fad4-bd31-44c1-a639-600f5faf6cfb",
"name": "\ud83d\udd14 Patient Lead Webhook",
"type": "n8n-nodes-base.webhook",
"position": [
-480,
1312
],
"parameters": {
"path": "patient-lead",
"options": {},
"httpMethod": "POST"
},
"typeVersion": 1
},
{
"id": "38b38cfc-c8a4-4ebb-b217-1058a116eb90",
"name": "\ud83d\uddc2\ufe0f Format Patient Data",
"type": "n8n-nodes-base.set",
"position": [
-256,
1312
],
"parameters": {
"values": {
"string": [
{
"name": "patient_name",
"value": "={{$json[\"name\"]}}"
},
{
"name": "phone",
"value": "={{$json[\"phone\"]}}"
},
{
"name": "symptoms",
"value": "={{$json[\"symptoms\"]}}"
},
{
"name": "age",
"value": "={{$json[\"age\"]}}"
},
{
"name": "gender",
"value": "={{$json[\"gender\"]}}"
},
{
"name": "additonal notes",
"value": "={{$json[\"additonal notes\"]}}"
}
]
},
"options": {}
},
"typeVersion": 2
},
{
"id": "9edcdc48-1d7c-4a41-b59f-da560b2d806c",
"name": "\ud83e\udd16 AI Triage Agent",
"type": "@n8n/n8n-nodes-langchain.agent",
"position": [
-32,
1200
],
"parameters": {
"text": "=Analyze the following patient intake information and perform triage.\n\nPatient Name: {{$json.patient_name}}\n\nAge: {{$json.age}}\n\nGender: {{$json.gender}}\n\nPhone: {{$json.phone}}\n\nSymptoms: {{$json.symptoms}}\n\nAdditional Notes: {{$json.notes}}\n\nPlease determine urgency level, recommended department, appointment priority, and recommended action.",
"options": {
"systemMessage": "=You are an AI healthcare triage assistant working for a medical clinic.\n\nYour role is NOT to diagnose diseases. Your responsibility is to analyze patient-reported symptoms and determine:\n\n1. Urgency level\n2. Recommended department\n3. Suggested appointment priority\n4. Whether emergency care may be required\n\nYou must follow safe triage rules.\n\nUrgency levels:\nLOW \u2192 routine consultation within 3\u20135 days\nMEDIUM \u2192 consultation within 24\u201348 hours\nHIGH \u2192 same day appointment\nCRITICAL \u2192 immediate emergency care recommended\n\nDepartments available:\nGeneral Physician\nCardiology\nOrthopedics\nDermatology\nNeurology\nENT\nGastroenterology\nPulmonology\nGynecology\nPediatrics\n\nInstructions:\n\u2022 Analyze symptoms carefully\n\u2022 Prioritize patient safety\n\u2022 If symptoms suggest severe danger (chest pain, breathing difficulty, unconsciousness, stroke symptoms), mark CRITICAL.\n\u2022 If unsure, classify as MEDIUM.\n\nOutput must ALWAYS be structured JSON.\n\nReturn ONLY valid JSON in this format:\n\n{\n\"patient_name\": \"\",\n\"patient_age\": \"\",\n\"patient_gender\": \"\",\n\"phone\": \"\",\n\"symptoms\": \"\",\n\"symptom_duration\": \"\",\n\"urgency_level\": \"\",\n\"recommended_department\": \"\",\n\"appointment_priority\": \"\",\n\"triage_reasoning\": \"\",\n\"recommended_action\": \"\",\n\"sheet_row\": {\n \"Name\": \"\",\n \"Age\": \"\",\n \"Gender\": \"\",\n \"Phone\": \"\",\n \"Symptoms\": \"\",\n \"Urgency\": \"\",\n \"Department\": \"\",\n \"Priority\": \"\",\n \"Recommended Action\": \"\"\n}\n}\n\nDo not include any extra text outside the JSON."
},
"promptType": "define"
},
"typeVersion": 3
},
{
"id": "724790a3-9506-40c2-9724-d10295e742ef",
"name": "\ud83e\udde0 Azure OpenAI \u2013 Triage Model",
"type": "@n8n/n8n-nodes-langchain.lmChatAzureOpenAi",
"position": [
400,
1600
],
"parameters": {
"model": "gpt-4o-mini",
"options": {}
},
"credentials": {
"azureOpenAiApi": {
"name": "<your credential>"
}
},
"typeVersion": 1
},
{
"id": "e30b1134-b3ed-40e1-b214-b784625f76ed",
"name": "\u2699\ufe0f Normalize Triage Output",
"type": "n8n-nodes-base.code",
"position": [
320,
1312
],
"parameters": {
"jsCode": "// Get AI response\nconst raw = $json;\n\n// Sometimes AI returns JSON as string\nlet data;\n\ntry {\n data = typeof raw === \"string\" ? JSON.parse(raw) : raw;\n} catch (error) {\n data = JSON.parse(raw.output || \"{}\");\n}\n\n// Helper function\nconst safe = (value, fallback = \"\") => {\n if (value === undefined || value === null) return fallback;\n return String(value).trim();\n};\n\n// Normalize urgency\nconst normalizeUrgency = (urgency) => {\n if (!urgency) return \"MEDIUM\";\n \n const u = urgency.toLowerCase();\n\n if (u.includes(\"critical\")) return \"CRITICAL\";\n if (u.includes(\"high\")) return \"HIGH\";\n if (u.includes(\"medium\")) return \"MEDIUM\";\n if (u.includes(\"low\")) return \"LOW\";\n\n return \"MEDIUM\";\n};\n\nconst urgency = normalizeUrgency(data.urgency_level);\n\n// Normalize department\nconst department = safe(data.recommended_department, \"General Physician\");\n\n// Priority mapping\nlet priority = \"Routine\";\n\nif (urgency === \"CRITICAL\") priority = \"Immediate\";\nif (urgency === \"HIGH\") priority = \"Same Day\";\nif (urgency === \"MEDIUM\") priority = \"24-48 Hours\";\nif (urgency === \"LOW\") priority = \"3-5 Days\";\n\n// Normalized object\nconst normalized = {\n patient_name: safe(data.patient_name),\n patient_age: safe(data.patient_age),\n patient_gender: safe(data.patient_gender),\n phone: safe(data.phone),\n symptoms: safe(data.symptoms),\n symptom_duration: safe(data.symptom_duration),\n urgency_level: urgency,\n recommended_department: department,\n appointment_priority: priority,\n triage_reasoning: safe(data.triage_reasoning),\n recommended_action: safe(data.recommended_action),\n sheet_row: {\n Name: safe(data.patient_name),\n Age: safe(data.patient_age),\n Gender: safe(data.patient_gender),\n Phone: safe(data.phone),\n Symptoms: safe(data.symptoms),\n Urgency: urgency,\n Department: department,\n Priority: priority,\n \"Recommended Action\": safe(data.recommended_action)\n }\n};\n\nreturn [{ json: normalized }];"
},
"typeVersion": 2
},
{
"id": "1ae0015a-5eb5-4b7a-a0f9-0fe62fdd4138",
"name": "\ud83d\udcdd Save Triage to Patient Forms Sheet",
"type": "n8n-nodes-base.googleSheets",
"position": [
544,
1312
],
"parameters": {
"columns": {
"value": {},
"schema": [],
"mappingMode": "autoMapInputData",
"matchingColumns": [],
"attemptToConvertTypes": false,
"convertFieldsToString": false
},
"options": {},
"operation": "appendOrUpdate",
"sheetName": {
"__rl": true,
"mode": "list",
"value": "gid=0",
"cachedResultUrl": "https://docs.google.com/spreadsheets/d/1qyt7yT1AzbOurF4P55duqaUoeeAZhyFgbxjMiOONRz8/edit#gid=0",
"cachedResultName": "Patient forms"
},
"documentId": {
"__rl": true,
"mode": "list",
"value": "1qyt7yT1AzbOurF4P55duqaUoeeAZhyFgbxjMiOONRz8",
"cachedResultUrl": "https://docs.google.com/spreadsheets/d/1qyt7yT1AzbOurF4P55duqaUoeeAZhyFgbxjMiOONRz8/edit?usp=drivesdk",
"cachedResultName": "Patient & Doctors details"
}
},
"credentials": {
"googleSheetsOAuth2Api": {
"name": "<your credential>"
}
},
"typeVersion": 4.7
},
{
"id": "e789fa52-cba1-4957-9f0c-900e518934a2",
"name": "\ud83d\udcca New Patient Row Trigger",
"type": "n8n-nodes-base.googleSheetsTrigger",
"position": [
-432,
1832
],
"parameters": {
"options": {},
"pollTimes": {
"item": [
{
"mode": "everyMinute"
}
]
},
"sheetName": {
"__rl": true,
"mode": "list",
"value": "gid=0",
"cachedResultUrl": "https://docs.google.com/spreadsheets/d/1qyt7yT1AzbOurF4P55duqaUoeeAZhyFgbxjMiOONRz8/edit#gid=0",
"cachedResultName": "Patient forms"
},
"documentId": {
"__rl": true,
"mode": "list",
"value": "1qyt7yT1AzbOurF4P55duqaUoeeAZhyFgbxjMiOONRz8",
"cachedResultUrl": "https://docs.google.com/spreadsheets/d/1qyt7yT1AzbOurF4P55duqaUoeeAZhyFgbxjMiOONRz8/edit?usp=drivesdk",
"cachedResultName": "Patient & Doctors details"
}
},
"credentials": {
"googleSheetsTriggerOAuth2Api": {
"name": "<your credential>"
}
},
"typeVersion": 1
},
{
"id": "84c3a139-d345-4376-b824-5394d648603e",
"name": "\ud83e\udd16 AI Doctor Assignment Agent",
"type": "@n8n/n8n-nodes-langchain.agent",
"position": [
-208,
1728
],
"parameters": {
"text": "=Analyze the following patient intake form and assign the most suitable doctor.\n\nPatient Information:\n\nName: {{$json.name}}\n\nAge: {{$json.age}}\n\nGender: {{$json.gender}}\n\nSymptoms: {{$json.symptoms}}\n\nUse the connected doctor sheet tool to find available doctors and their specialties.\n\nSelect the most appropriate doctor based on the symptoms.\n",
"options": {
"systemMessage": "=You are an AI medical routing assistant working for a healthcare clinic.\n\nYour job is to read a patient's intake form and assign the most appropriate doctor based on the symptoms and the doctor's specialty.\n\nYou have access to a tool connected to a Google Sheet that contains a list of doctors and their specialties.\n\nYou MUST follow these rules:\n\n1. Always read the doctor list from the sheet tool before selecting a doctor.\n2. Match the patient symptoms with the most relevant medical specialty.\n3. Only choose a doctor that exists in the sheet data.\n4. If multiple doctors match the specialty, choose one randomly or the first available.\n5. If no direct specialty match exists, assign a \"General Physician\".\n6. Do NOT diagnose diseases. Your job is only to route the patient to the correct doctor.\n\nDoctor specialties may include:\nGeneral Physician\nCardiologist\nDermatologist\nOrthopedic\nNeurologist\nENT Specialist\nGastroenterologist\nPulmonologist\nGynecologist\nPediatrician\n\nSymptoms \u2192 Specialty Guidelines:\n\nChest pain / heart issues \u2192 Cardiologist\nSkin rash / acne \u2192 Dermatologist\nBone pain / injury \u2192 Orthopedic\nHeadache / seizures \u2192 Neurologist\nEar / nose / throat \u2192 ENT\nStomach pain / digestion \u2192 Gastroenterologist\nBreathing problems \u2192 Pulmonologist\nWomen's health \u2192 Gynecologist\nChildren under 14 \u2192 Pediatrician\nGeneral illness \u2192 General Physician\n\nAfter selecting the doctor, return structured JSON.\n\nOutput format must be:\n\n{\n\"patient_details\": {\n\"name\": \"\",\n\"age\": \"\",\n\"gender\": \"\",\n\"symptoms\": \"\"\n},\n\"recommended_specialty\": \"\",\n\"assigned_doctor\": \"\",\n\"assignment_reason\": \"\",\n\"sheet_update\": {\n\"Patient Name\": \"\",\n\"Age\": \"\",\n\"Gender\": \"\",\n\"Symptoms\": \"\",\n\"Specialty\": \"\",\n\"Doctor Assigned\": \"\"\n}\n}\n\nReturn ONLY JSON. Do not include explanations outside the JSON.\n"
},
"promptType": "define"
},
"typeVersion": 3
},
{
"id": "99406cde-5ede-4567-b8c7-ef41b8da747d",
"name": "\ud83e\udde0 Azure OpenAI \u2013 Assignment Model",
"type": "@n8n/n8n-nodes-langchain.lmChatAzureOpenAi",
"position": [
-200,
1952
],
"parameters": {
"model": "gpt-4o-mini",
"options": {}
},
"credentials": {
"azureOpenAiApi": {
"name": "<your credential>"
}
},
"typeVersion": 1
},
{
"id": "ab7c8c9a-14f8-4276-8d70-ed48759ae002",
"name": "\ud83d\udccb Fetch Doctor List from Sheet",
"type": "n8n-nodes-base.googleSheetsTool",
"position": [
-72,
1952
],
"parameters": {
"options": {},
"sheetName": {
"__rl": true,
"mode": "list",
"value": 1035960590,
"cachedResultUrl": "https://docs.google.com/spreadsheets/d/1qyt7yT1AzbOurF4P55duqaUoeeAZhyFgbxjMiOONRz8/edit#gid=1035960590",
"cachedResultName": "Doctor details"
},
"documentId": {
"__rl": true,
"mode": "list",
"value": "1qyt7yT1AzbOurF4P55duqaUoeeAZhyFgbxjMiOONRz8",
"cachedResultUrl": "https://docs.google.com/spreadsheets/d/1qyt7yT1AzbOurF4P55duqaUoeeAZhyFgbxjMiOONRz8/edit?usp=drivesdk",
"cachedResultName": "Patient & Doctors details"
}
},
"credentials": {
"googleSheetsOAuth2Api": {
"name": "<your credential>"
}
},
"typeVersion": 4.7
},
{
"id": "437529df-3dbf-47f9-b1f1-89683952c744",
"name": "\u2699\ufe0f Normalize Assignment Output",
"type": "n8n-nodes-base.code",
"position": [
144,
1832
],
"parameters": {
"jsCode": "// Get raw AI response\nlet raw = $json;\n\n// If AI returned text containing JSON\nif (typeof raw === \"string\") {\n try {\n raw = JSON.parse(raw);\n } catch {\n const match = raw.match(/\\{[\\s\\S]*\\}/);\n if (match) {\n raw = JSON.parse(match[0]);\n } else {\n raw = {};\n }\n }\n}\n\n// Helper functions\nconst clean = (value, fallback = \"\") => {\n if (value === undefined || value === null) return fallback;\n return String(value).trim();\n};\n\nconst normalizeGender = (gender) => {\n const g = clean(gender).toLowerCase();\n if (g.includes(\"male\")) return \"Male\";\n if (g.includes(\"female\")) return \"Female\";\n return \"Other\";\n};\n\nconst normalizeAge = (age) => {\n const num = parseInt(age);\n if (isNaN(num)) return \"\";\n return num;\n};\n\nconst normalizeSpecialty = (specialty) => {\n if (!specialty) return \"General Physician\";\n\n const s = specialty.toLowerCase();\n\n if (s.includes(\"cardio\")) return \"Cardiologist\";\n if (s.includes(\"derma\")) return \"Dermatologist\";\n if (s.includes(\"ortho\")) return \"Orthopedic\";\n if (s.includes(\"neuro\")) return \"Neurologist\";\n if (s.includes(\"ent\")) return \"ENT Specialist\";\n if (s.includes(\"gastro\")) return \"Gastroenterologist\";\n if (s.includes(\"pulmo\")) return \"Pulmonologist\";\n if (s.includes(\"gyn\")) return \"Gynecologist\";\n if (s.includes(\"pedia\")) return \"Pediatrician\";\n\n return \"General Physician\";\n};\n\n// Extract patient details\nconst patient = raw.patient_details || {};\n\n// Normalize fields\nconst name = clean(patient.name);\nconst age = normalizeAge(patient.age);\nconst gender = normalizeGender(patient.gender);\nconst symptoms = clean(patient.symptoms);\n\nconst specialty = normalizeSpecialty(raw.recommended_specialty);\nconst doctor = clean(raw.assigned_doctor);\nconst reason = clean(raw.assignment_reason);\n\n// Final normalized object\nconst normalized = {\n patient_name: name,\n patient_age: age,\n patient_gender: gender,\n symptoms: symptoms,\n recommended_specialty: specialty,\n assigned_doctor: doctor,\n assignment_reason: reason,\n\n sheet_row: {\n \"Patient Name\": name,\n \"Age\": age,\n \"Gender\": gender,\n \"Symptoms\": symptoms,\n \"Specialty\": specialty,\n \"Doctor Assigned\": doctor\n }\n};\n\nreturn [{ json: normalized }];"
},
"typeVersion": 2
},
{
"id": "917f4ee8-1b44-427d-b1f6-1a8d17ee6123",
"name": "\ud83d\udcdd Save Assignment to Appointments Sheet",
"type": "n8n-nodes-base.googleSheets",
"position": [
368,
1832
],
"parameters": {
"columns": {
"value": {},
"schema": [],
"mappingMode": "autoMapInputData",
"matchingColumns": [],
"attemptToConvertTypes": false,
"convertFieldsToString": false
},
"options": {},
"operation": "appendOrUpdate",
"sheetName": {
"__rl": true,
"mode": "list",
"value": 813175484,
"cachedResultUrl": "https://docs.google.com/spreadsheets/d/1qyt7yT1AzbOurF4P55duqaUoeeAZhyFgbxjMiOONRz8/edit#gid=813175484",
"cachedResultName": "Scheduled appointments"
},
"documentId": {
"__rl": true,
"mode": "list",
"value": "1qyt7yT1AzbOurF4P55duqaUoeeAZhyFgbxjMiOONRz8",
"cachedResultUrl": "https://docs.google.com/spreadsheets/d/1qyt7yT1AzbOurF4P55duqaUoeeAZhyFgbxjMiOONRz8/edit?usp=drivesdk",
"cachedResultName": "Patient & Doctors details"
}
},
"credentials": {
"googleSheetsOAuth2Api": {
"name": "<your credential>"
}
},
"typeVersion": 4.7
},
{
"id": "37b9522c-7e6d-4d81-9499-175bba974b89",
"name": "\ud83d\udccb Fetch Appointments for Doctor Email",
"type": "n8n-nodes-base.googleSheets",
"position": [
592,
1832
],
"parameters": {
"options": {},
"sheetName": {
"__rl": true,
"mode": "list",
"value": 813175484,
"cachedResultUrl": "https://docs.google.com/spreadsheets/d/1qyt7yT1AzbOurF4P55duqaUoeeAZhyFgbxjMiOONRz8/edit#gid=813175484",
"cachedResultName": "Scheduled appointments"
},
"documentId": {
"__rl": true,
"mode": "list",
"value": "1qyt7yT1AzbOurF4P55duqaUoeeAZhyFgbxjMiOONRz8",
"cachedResultUrl": "https://docs.google.com/spreadsheets/d/1qyt7yT1AzbOurF4P55duqaUoeeAZhyFgbxjMiOONRz8/edit?usp=drivesdk",
"cachedResultName": "Patient & Doctors details"
}
},
"credentials": {
"googleSheetsOAuth2Api": {
"name": "<your credential>"
}
},
"typeVersion": 4.7
},
{
"id": "59e63ad0-287e-41a7-a2d7-c882407b6478",
"name": "\u2699\ufe0f Build Doctor Schedule Email HTML",
"type": "n8n-nodes-base.code",
"position": [
816,
1832
],
"parameters": {
"jsCode": "const items = $input.all();\n\n// Extract doctor info from first record\nconst doctorName = items[0]?.json?.doctor_name || \"Doctor\";\nconst date = new Date().toLocaleDateString();\n\n// Build table rows\nlet rows = \"\";\n\nfor (const item of items) {\n const a = item.json;\n\n rows += `\n <tr>\n <td>${a.time || \"-\"}</td>\n <td>${a.patient_name || \"-\"}</td>\n <td>${a.age || \"-\"}</td>\n <td>${a.gender || \"-\"}</td>\n <td>${a.symptoms || \"-\"}</td>\n </tr>\n `;\n}\n\n// HTML Email Template\nconst html = `\n<!DOCTYPE html>\n<html>\n<head>\n<meta charset=\"UTF-8\">\n<style>\nbody{\nfont-family: Arial, sans-serif;\nbackground:#f4f6f9;\nmargin:0;\npadding:0;\n}\n\n.container{\nmax-width:700px;\nmargin:auto;\nbackground:white;\nborder-radius:10px;\noverflow:hidden;\nbox-shadow:0 4px 12px rgba(0,0,0,0.1);\n}\n\n.header{\nbackground:#2c7be5;\ncolor:white;\npadding:25px;\ntext-align:center;\n}\n\n.header h1{\nmargin:0;\nfont-size:22px;\n}\n\n.content{\npadding:25px;\n}\n\n.table{\nwidth:100%;\nborder-collapse:collapse;\nmargin-top:20px;\n}\n\n.table th{\nbackground:#f1f3f7;\npadding:12px;\ntext-align:left;\nfont-size:14px;\nborder-bottom:1px solid #ddd;\n}\n\n.table td{\npadding:12px;\nborder-bottom:1px solid #eee;\nfont-size:13px;\n}\n\n.footer{\npadding:20px;\ntext-align:center;\nfont-size:12px;\ncolor:#888;\nbackground:#f7f7f7;\n}\n</style>\n</head>\n\n<body>\n\n<div class=\"container\">\n\n<div class=\"header\">\n<h1>Daily Appointment Schedule</h1>\n</div>\n\n<div class=\"content\">\n\n<p>Hello <strong>${doctorName}</strong>,</p>\n\n<p>Here is your appointment schedule for <strong>${date}</strong>.</p>\n\n<table class=\"table\">\n\n<thead>\n<tr>\n<th>Time</th>\n<th>Patient</th>\n<th>Age</th>\n<th>Gender</th>\n<th>Symptoms</th>\n</tr>\n</thead>\n\n<tbody>\n\n${rows}\n\n</tbody>\n\n</table>\n\n<p style=\"margin-top:20px;\">\nPlease review the schedule and prepare for the consultations.\n</p>\n\n</div>\n\n<div class=\"footer\">\nAutomated Healthcare Operations System\n</div>\n\n</div>\n\n</body>\n</html>\n`;\n\nreturn [\n{\njson: {\nemail_html: html\n}\n}\n];"
},
"typeVersion": 2
},
{
"id": "e32eaa6d-7abf-4421-80b2-d2c268ee83d5",
"name": "\ud83d\udce7 Send Schedule Email to Doctor",
"type": "n8n-nodes-base.gmail",
"position": [
1040,
1832
],
"parameters": {
"sendTo": "info@example.com",
"message": "={{$json.email_html}}",
"options": {},
"subject": "Today's Appointment Schedule"
},
"credentials": {
"gmailOAuth2": {
"name": "<your credential>"
}
},
"typeVersion": 2.2
},
{
"id": "76c06afc-5d1d-4fad-a69a-9943dee7d3d4",
"name": "\u23f0 Hourly Feedback Schedule Trigger",
"type": "n8n-nodes-base.scheduleTrigger",
"position": [
-432,
2160
],
"parameters": {
"rule": {
"interval": [
{
"field": "hours"
}
]
}
},
"typeVersion": 1.3
},
{
"id": "f8994eed-f199-48de-bdcc-9d34ea2262a8",
"name": "\ud83d\udccb Fetch All Appointments for Feedback",
"type": "n8n-nodes-base.googleSheets",
"position": [
-208,
2160
],
"parameters": {
"options": {},
"sheetName": {
"__rl": true,
"mode": "list",
"value": 813175484,
"cachedResultUrl": "https://docs.google.com/spreadsheets/d/1qyt7yT1AzbOurF4P55duqaUoeeAZhyFgbxjMiOONRz8/edit#gid=813175484",
"cachedResultName": "Scheduled appointments"
},
"documentId": {
"__rl": true,
"mode": "list",
"value": "1qyt7yT1AzbOurF4P55duqaUoeeAZhyFgbxjMiOONRz8",
"cachedResultUrl": "https://docs.google.com/spreadsheets/d/1qyt7yT1AzbOurF4P55duqaUoeeAZhyFgbxjMiOONRz8/edit?usp=drivesdk",
"cachedResultName": "Patient & Doctors details"
}
},
"credentials": {
"googleSheetsOAuth2Api": {
"name": "<your credential>"
}
},
"typeVersion": 4.7
},
{
"id": "3c649240-625b-4d33-b192-fcb7a7d09e23",
"name": "\ud83d\udd00 Filter Completed Visits",
"type": "n8n-nodes-base.if",
"position": [
16,
2160
],
"parameters": {
"options": {},
"conditions": {
"options": {
"version": 3,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "8f0ea0a1-d188-4b82-a51c-8472f75a638d",
"operator": {
"type": "string",
"operation": "notEmpty",
"singleValue": true
},
"leftValue": "={{$json.visit}}",
"rightValue": ""
}
]
}
},
"typeVersion": 2.3
},
{
"id": "26e36e5e-d509-464f-a762-3bcfe818a620",
"name": "\ud83d\udce7 Send Feedback Email to Patient",
"type": "n8n-nodes-base.gmail",
"position": [
240,
2160
],
"parameters": {
"sendTo": "info@example.com",
"message": "=Hi {{$json.name}},\n\nPlease rate your experience!\nhere is the feedback form: {{$json.form_link}}",
"options": {},
"subject": "Feedback Form",
"emailType": "text"
},
"credentials": {
"gmailOAuth2": {
"name": "<your credential>"
}
},
"typeVersion": 2.2
}
],
"active": false,
"settings": {
"executionOrder": "v1"
},
"versionId": "5bd13f18-df08-497d-ac5a-2f302f80a812",
"connections": {
"\ud83e\udd16 AI Triage Agent": {
"main": [
[
{
"node": "\u2699\ufe0f Normalize Triage Output",
"type": "main",
"index": 0
}
]
]
},
"\ud83d\udd14 Patient Lead Webhook": {
"main": [
[
{
"node": "\ud83d\uddc2\ufe0f Format Patient Data",
"type": "main",
"index": 0
}
]
]
},
"\ud83d\uddc2\ufe0f Format Patient Data": {
"main": [
[
{
"node": "\ud83e\udd16 AI Triage Agent",
"type": "main",
"index": 0
}
]
]
},
"\ud83d\udcca New Patient Row Trigger": {
"main": [
[
{
"node": "\ud83e\udd16 AI Doctor Assignment Agent",
"type": "main",
"index": 0
}
]
]
},
"\ud83d\udd00 Filter Completed Visits": {
"main": [
[
{
"node": "\ud83d\udce7 Send Feedback Email to Patient",
"type": "main",
"index": 0
}
]
]
},
"\u2699\ufe0f Normalize Triage Output": {
"main": [
[
{
"node": "\ud83d\udcdd Save Triage to Patient Forms Sheet",
"type": "main",
"index": 0
}
]
]
},
"\ud83e\udd16 AI Doctor Assignment Agent": {
"main": [
[
{
"node": "\u2699\ufe0f Normalize Assignment Output",
"type": "main",
"index": 0
}
]
]
},
"\ud83d\udccb Fetch Doctor List from Sheet": {
"ai_tool": [
[
{
"node": "\ud83e\udd16 AI Doctor Assignment Agent",
"type": "ai_tool",
"index": 0
}
]
]
},
"\u2699\ufe0f Normalize Assignment Output": {
"main": [
[
{
"node": "\ud83d\udcdd Save Assignment to Appointments Sheet",
"type": "main",
"index": 0
}
]
]
},
"\ud83e\udde0 Azure OpenAI \u2013 Triage Model": {
"ai_languageModel": [
[
{
"node": "\ud83e\udd16 AI Triage Agent",
"type": "ai_languageModel",
"index": 0
}
]
]
},
"\u23f0 Hourly Feedback Schedule Trigger": {
"main": [
[
{
"node": "\ud83d\udccb Fetch All Appointments for Feedback",
"type": "main",
"index": 0
}
]
]
},
"\ud83e\udde0 Azure OpenAI \u2013 Assignment Model": {
"ai_languageModel": [
[
{
"node": "\ud83e\udd16 AI Doctor Assignment Agent",
"type": "ai_languageModel",
"index": 0
}
]
]
},
"\u2699\ufe0f Build Doctor Schedule Email HTML": {
"main": [
[
{
"node": "\ud83d\udce7 Send Schedule Email to Doctor",
"type": "main",
"index": 0
}
]
]
},
"\ud83d\udccb Fetch All Appointments for Feedback": {
"main": [
[
{
"node": "\ud83d\udd00 Filter Completed Visits",
"type": "main",
"index": 0
}
]
]
},
"\ud83d\udccb Fetch Appointments for Doctor Email": {
"main": [
[
{
"node": "\u2699\ufe0f Build Doctor Schedule Email HTML",
"type": "main",
"index": 0
}
]
]
},
"\ud83d\udcdd Save Assignment to Appointments Sheet": {
"main": [
[
{
"node": "\ud83d\udccb Fetch Appointments for Doctor Email",
"type": "main",
"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.
azureOpenAiApigmailOAuth2googleSheetsOAuth2ApigoogleSheetsTriggerOAuth2Api
For the full experience including quality scoring and batch install features for each workflow upgrade to Pro
About this workflow
Automate your clinic’s patient intake and scheduling with an intelligent AI Healthcare Appointment Booking Agent built in n8n. 🏥 This workflow receives patient form submissions, analyzes symptoms using AI triage, determines urgency levels, and automatically assigns the most…
Source: https://n8n.io/workflows/13960/ — 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 automates payment-related customer support escalation by validating reported issues against transaction data and coordinating all downstream actions in a controlled, auditable way. It is
Enhance your support, onboarding, and internal knowledge workflows with an intelligent RAG-powered chatbot that responds using live data stored in Google Sheets. 🤖📚 Built for teams that rely on struct
Automate post-purchase workflows by instantly fetching successful Stripe payments, matching them to corresponding automation templates in Google Sheets, and sending customers personalized access email
Instant, automated scheduling. This AI Scheduling Agent manages real-time appointments, availability checks, and rescheduling across Google Calendar and Sheets, eliminating human hold times.
This workflow automates the complete DPDP-aligned Consent Manager Registration screening pipeline — from intake to eligibility evaluation and final compliance routing. Every incoming registration requ