This workflow corresponds to n8n.io template #12070 — we link there as the canonical source.
This workflow follows the Agent → Form Trigger 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": "Employee Leave Approval System",
"nodes": [
{
"id": "90969d48-aed1-4a4b-9a54-557e2c1e66bf",
"name": "On form submission",
"type": "n8n-nodes-base.formTrigger",
"position": [
-160,
-64
],
"parameters": {
"options": {
"appendAttribution": false,
"respondWithOptions": {
"values": {
"formSubmittedText": "You will be notified via email regarding the approval or rejection of your leave request."
}
}
},
"formTitle": "Leave Request Form",
"formFields": {
"values": [
{
"fieldLabel": "Employee Name",
"requiredField": true
},
{
"fieldType": "email",
"fieldLabel": "Email",
"requiredField": true
},
{
"fieldType": "date",
"fieldLabel": "From",
"requiredField": true
},
{
"fieldType": "date",
"fieldLabel": "To"
},
{
"fieldLabel": "Reason for the Leave",
"requiredField": true
},
{
"fieldLabel": "What Important tasks were you working on?",
"placeholder": "Priorities",
"requiredField": true
}
]
},
"formDescription": "Please fill every detail properly, and explain in detail about the task and priorities that you have "
},
"typeVersion": 2.3
},
{
"id": "8cd2b052-fd8f-4d79-88f8-4e5a717ad97a",
"name": "AI Agent",
"type": "@n8n/n8n-nodes-langchain.agent",
"position": [
64,
-64
],
"parameters": {
"text": "=Role:\nYou are an HR Operations Assistant.\n\nInput:\nYou will receive an array of objects containing employee leave request data in the following JSON format:\n\n[\n {\n \"Employee Name\": \"{{ $json['Employee Name'] }}\",\n \"Email\": \"{{ $json.Email }}\",\n \"From\": \"{{ $json.From }}\",\n \"To\": \"{{ $json.To }}\",\n \"Reason for the Leave\": \"{{ $json['Reason for the Leave'] }}\",\n \"What Important tasks were you working on?\": \"{{ $json['What Important tasks were you working on?'] }}\"\n }\n]\n\nRules & Logic:\n1. If the \"To\" field is empty or null, treat the leave as a single-day leave on the \"From\" date.\n2. If the \"To\" field is present, treat it as a multi-day leave from the \"From\" date to the \"To\" date (inclusive).\n3. Read all the input data and generate a **professional summary** of the leave request instead of just copying the text.\n4. Include in the summary:\n - Employee Name\n - Leave duration (single-day or date range)\n - Reason for leave (summarized in professional wording)\n - Status of ongoing tasks and how they are covered or handed over\n5. Keep a **polite and professional tone** suitable for sending to Manager and HR.\n6. Do not add any information not present in the input.\n7. Output must be valid HTML content only.\n8. Allowed HTML tags: <p>, <br>, <strong>, <b>, <em>, <ul>, <ol>, <li>, <span>, <div>\n9. Do NOT include <html>, <head>, or <body> tags.\n10. Generate exactly **two outputs**:\n - `subject` \u2192 a concise email subject line\n - `body` \u2192 the full HTML-formatted email content\n\nOutput Example:\n\nsubject:\nLeave Approval Request \u2013 {{ $json['Employee Name'] }}\n\nbody:\n<div>\n <p>\n <strong>Employee:</strong> {{ $json['Employee Name'] }}\n </p>\n\n <p>\n <strong>Leave Duration:</strong> {% if $json.To == \"\" %}{{ $json.From }} (single-day leave){% else %}{{ $json.From }} to {{ $json.To }}{% endif %}\n </p>\n\n <p>\n <strong>Reason for Leave:</strong> A summary of the employee's reason for leave in professional wording.\n </p>\n\n <p>\n <strong>Task Status & Coverage:</strong> Summarize the important tasks the employee was working on and explain how they will be managed or covered during their absence.\n </p>\n\n <p>\n Kindly review and approve or reject the leave request.\n </p>\n</div>",
"options": {},
"promptType": "define",
"hasOutputParser": true
},
"typeVersion": 3
},
{
"id": "ad65648f-540b-45b7-abe7-a9c40391a2f4",
"name": "OpenAI Chat Model",
"type": "@n8n/n8n-nodes-langchain.lmChatOpenAi",
"position": [
0,
144
],
"parameters": {
"model": {
"__rl": true,
"mode": "list",
"value": "gpt-4o-mini"
},
"options": {},
"builtInTools": {}
},
"typeVersion": 1.3
},
{
"id": "ee1f1646-fb35-4969-91f9-c40c800dbff7",
"name": "Structured Output Parser",
"type": "@n8n/n8n-nodes-langchain.outputParserStructured",
"position": [
240,
144
],
"parameters": {
"jsonSchemaExample": "{\n\t\"Subject\": \"\",\n\t\"Body\": \"\"\n}"
},
"typeVersion": 1.3
},
{
"id": "6e87890d-324b-4619-981d-5c9e1d068215",
"name": "Send message and wait for response",
"type": "n8n-nodes-base.gmail",
"position": [
416,
-64
],
"parameters": {
"sendTo": "={{ $('On form submission').item.json.Email }}",
"message": "={{ $json.output.Body }}",
"options": {
"appendAttribution": false
},
"subject": "={{ $json.output.Subject }}",
"operation": "sendAndWait",
"approvalOptions": {
"values": {
"approvalType": "double",
"disapproveLabel": "Reject"
}
}
},
"typeVersion": 2.2
},
{
"id": "3efdcfc0-b320-4841-85a4-b6ff480c1f35",
"name": "If",
"type": "n8n-nodes-base.if",
"position": [
640,
-64
],
"parameters": {
"options": {},
"conditions": {
"options": {
"version": 3,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "c440577b-0795-4886-be4d-4e0e5b541615",
"operator": {
"type": "boolean",
"operation": "equals"
},
"leftValue": "={{ $json.data.approved }}",
"rightValue": true
}
]
}
},
"typeVersion": 2.3
},
{
"id": "1fec2797-b9e9-48cf-9aad-c1f75283e4c8",
"name": "Send a message",
"type": "n8n-nodes-base.gmail",
"position": [
1008,
-128
],
"parameters": {
"sendTo": "={{ $('On form submission').item.json.Email }}",
"message": "=<div>\n <p>Dear {{ $('On form submission').item.json['Employee Name'] }},</p>\n\n <p>\n We are pleased to inform you that your leave request has been <strong>approved</strong>.\n </p>\n\n <p>\n <strong>Leave Duration:</strong>\n {{ $('On form submission').item.json.From }} - {{ $('On form submission').item.json.To }}<br>\n <strong>Reason for Leave:</strong> {{ $('On form submission').item.json['Reason for the Leave'] }}<br>\n <strong>Task Coverage:</strong> Your submitted plan for handling ongoing tasks has been acknowledged.\n </p>\n\n <p>\n We wish you a productive and safe time off. Please ensure any pending tasks are handed over as discussed.\n </p>\n\n <p>\n Best regards,<br>\n HR Department<br>\n </p>\n</div>\n",
"options": {
"appendAttribution": false
},
"subject": "Leave Approval Status"
},
"typeVersion": 2.2
},
{
"id": "30be9378-7c6a-46cb-9956-a0ff539b6c9d",
"name": "Check Availability",
"type": "n8n-nodes-base.googleCalendarTool",
"position": [
992,
320
],
"parameters": {
"options": {
"timezone": {
"__rl": true,
"mode": "list",
"value": "Asia/Kolkata",
"cachedResultName": "Asia/Kolkata"
},
"outputFormat": "availability"
},
"timeMax": "={{ new Date(new Date().setDate(new Date().getDate() + 1)).setHours(18, 0, 0, 0) && new Date(new Date().setDate(new Date().getDate() + 1)).toISOString().replace('T', ' ').slice(0, 19).replace(/ \\d{2}:\\d{2}:\\d{2}$/, ' 18:00:00') }}",
"timeMin": "={{ new Date(new Date().setDate(new Date().getDate() + 1)).setHours(9, 0, 0, 0) && new Date(new Date().setDate(new Date().getDate() + 1)).toISOString().replace('T', ' ').slice(0, 19).replace(/ \\d{2}:\\d{2}:\\d{2}$/, ' 09:00:00') }}",
"calendar": {
"__rl": true,
"mode": "list",
"value": "user@example.com",
"cachedResultName": "user@example.com"
},
"resource": "calendar"
},
"typeVersion": 1.3
},
{
"id": "2ccb4823-6d9d-4423-b9be-7d36e6cafa28",
"name": "Get Events",
"type": "n8n-nodes-base.googleCalendarTool",
"position": [
1120,
320
],
"parameters": {
"options": {},
"timeMax": "={{ new Date(new Date().setDate(new Date().getDate() + 1)).setHours(18, 0, 0, 0) && new Date(new Date().setDate(new Date().getDate() + 1)).toISOString().replace('T', ' ').slice(0, 19).replace(/ \\d{2}:\\d{2}:\\d{2}$/, ' 18:00:00') }}",
"timeMin": "={{ new Date(new Date().setDate(new Date().getDate() + 1)).setHours(9, 0, 0, 0) && new Date(new Date().setDate(new Date().getDate() + 1)).toISOString().replace('T', ' ').slice(0, 19).replace(/ \\d{2}:\\d{2}:\\d{2}$/, ' 09:00:00') }}",
"calendar": {
"__rl": true,
"mode": "list",
"value": "user@example.com",
"cachedResultName": "user@example.com"
},
"operation": "getAll",
"returnAll": true
},
"typeVersion": 1.3
},
{
"id": "8c598b23-1d6a-47b6-8dc1-fdae56eddd2c",
"name": "OpenAI",
"type": "@n8n/n8n-nodes-langchain.lmChatOpenAi",
"position": [
864,
320
],
"parameters": {
"model": {
"__rl": true,
"mode": "list",
"value": "gpt-4o-mini"
},
"options": {}
},
"typeVersion": 1.2
},
{
"id": "3e86fb9e-150c-4e7c-b87c-0994676087d9",
"name": "Booking Agent",
"type": "@n8n/n8n-nodes-langchain.agent",
"position": [
992,
96
],
"parameters": {
"text": "=You are an availability checking agent.\n\nTODAY'S DATE: {{ new Date().toISOString().slice(0, 10) }}\n\nGOAL:\n\nYour job is to:\n1. Check availability for tomorrow (the next business day after today)\n2. Find the FIRST free 10-minute slot between 9:00 AM and 6:00 PM\n3. Return only ONE timeslot (the earliest available)\n\nYou must NEVER ask the user questions.\nYou must NEVER wait for user input.\nEverything must be automatic.\n\nCRITICAL DATE RULE:\n\n- Today is {{ new Date().toISOString().slice(0, 10) }}\n- Tomorrow's date is the day after today\n- The tools are already configured with timeMin and timeMax for tomorrow\n- You MUST use tomorrow's actual date in your output\n- If \"Get Events\" returns empty results, it means ALL slots are free\n- DO NOT make up or hallucinate dates\n- Calculate tomorrow's date from today's date\n\nCHECK AVAILABILITY TOOL RULES:\n\n1. First, call the \"Get Events\" tool to fetch all events booked for tomorrow.\n - If the result is empty or shows no events: ALL SLOTS ARE FREE - return 09:00:00 as the start time\n - If there are events: proceed to check availability between them\n - Extract the date from the timeMin parameter or from any returned events\n\n2. Only if there are existing events, call the \"Check Availability\" tool.\n - Look for slots with at least 10 minutes of free time between 09:00 and 18:00\n\nSLOT SELECTION LOGIC:\n\n- If NO events exist: The first slot is 09:00-09:10 (return 09:00:00)\n- If events exist: Start checking from 09:00:00 on tomorrow's date\n- Look for the FIRST available 10-minute slot\n- If 09:00-09:10 is booked, check 09:10-09:20\n- Continue in 10-minute increments until you find a free slot\n- Stop as soon as you find the first available slot\n\nOUTPUT RULES:\n\nAfter checking availability:\n- Calculate tomorrow's date from today ({{ new Date().toISOString().slice(0, 10) }})\n- Extract the date from the tool results or timeMin parameter\n- If calendar is completely free (no events): Return 09:00:00 as start_time with tomorrow's date\n- If there are events: Return the FIRST available 10-minute slot\n- Format EXACTLY like this JSON:\n {\n \"start_time\": \"YYYY-MM-DD 09:00:00\"\n }\n- Replace YYYY-MM-DD with tomorrow's actual date\n- The end time is automatically start_time + 10 minutes\n- If no slots available between 09:00-18:00, return: {\"start_time\": \"No available slots found\"}\n- MUST use format: YYYY-MM-DD HH:MM:SS\n- Do NOT list multiple slots - just ONE slot\n- Do NOT add any extra text or explanation\n\nIMPORTANT: Empty events list = All slots free = Return 09:00:00 with tomorrow's date\n\nEverything must be fully automatic.",
"options": {},
"promptType": "define",
"hasOutputParser": true
},
"typeVersion": 2.2
},
{
"id": "68288708-a89d-4112-a2bc-37878a9926fc",
"name": "Output Parser",
"type": "@n8n/n8n-nodes-langchain.outputParserStructured",
"position": [
1248,
320
],
"parameters": {
"jsonSchemaExample": "{\n\t\"start_time\": \"\"\n}"
},
"typeVersion": 1.3
},
{
"id": "8cf6468f-a58b-46ec-96fc-a673d071b06e",
"name": "Send a message1",
"type": "n8n-nodes-base.gmail",
"position": [
1440,
96
],
"parameters": {
"sendTo": "={{ $('On form submission').item.json.Email }}",
"message": "=<div>\n <p>Dear {{ $('On form submission').item.json['Employee Name'] }},</p>\n\n <p>\n Thank you for submitting your leave request.\n </p>\n\n <p>\n At this time, it is difficult to proceed with the approval of the requested leave without further clarification.\n To discuss this in more detail, a short discussion has been scheduled.\n </p>\n\n <p>\n <strong>Scheduled Time:</strong> {{ $json.output.start_time }}\n </p>\n\n <p>\n Kindly ensure your availability at the above-mentioned time so we can review the request and reach a conclusion.\n </p>\n\n <p>\n Best regards,<br>\n HR Department\n </p>\n</div>",
"options": {
"appendAttribution": false
},
"subject": "Discussion Required Regarding Leave Request"
},
"typeVersion": 2.2
},
{
"id": "89454c1c-11e7-415d-b5e8-62a9e281ef30",
"name": "Sticky Note",
"type": "n8n-nodes-base.stickyNote",
"position": [
-832,
-272
],
"parameters": {
"width": 608,
"height": 736,
"content": "## Leave Request Approval Automation with AI & Calendar Scheduling\nThis workflow automates the complete leave request process\u2014from employee submission to approval or discussion scheduling. It uses AI to generate professional summaries, routes decisions automatically, and ensures clear communication between employees and HR.\n\n### How It Works\n### Leave Submission & Review Flow:\n- Employees submit leave requests through a structured form with dates, reason, and task handover details\n- An AI agent generates a professional leave summary suitable for managers and HR\n- The system sends the summary via email and waits for approval or rejection\n- Single-day and multi-day leaves are handled automatically based on form input\n\n### Approval & Follow-Up Handling:\n- If approved, the employee receives an official approval email instantly\n- If rejected or clarification is needed, the workflow checks the manager's calendar\n- Finds the first available 10-minute slot on the next working day\n- Schedules a discussion and emails the time to the employee\n- Ensures all communication remains professional, consistent, and logged\n\n### Setup steps\n- Configure the leave request form fields in On form submission\n- Connect OpenAI and set prompts in AI Agent for summary generation\n- Connect Gmail for approval, rejection, and discussion emails\n- Enable \"Send and Wait\" approval in Send message and wait for response\n- Connect Google Calendar for availability checks\n- Review approval and discussion email templates\n- Turn the workflow ON and test with a sample leave request"
},
"typeVersion": 1
},
{
"id": "30641c1f-9701-44e6-95ae-71cbe18fbaac",
"name": "Sticky Note1",
"type": "n8n-nodes-base.stickyNote",
"position": [
-208,
-272
],
"parameters": {
"color": 7,
"width": 1024,
"height": 736,
"content": "## Step 1: Capture, summarize, and request approval\nThe employee submits the leave form, an AI agent generates a professional summary, and the request is emailed to the approver using a send-and-wait step for approval or rejection."
},
"typeVersion": 1
},
{
"id": "6dbc5ca3-7224-467f-81d5-239265ea2371",
"name": "Sticky Note2",
"type": "n8n-nodes-base.stickyNote",
"position": [
832,
-272
],
"parameters": {
"color": 7,
"width": 816,
"height": 736,
"content": "## Step 2: Notify or schedule discussion\nIf approved, the employee receives an approval email; if rejected or clarification is needed, the workflow checks calendar availability and schedules a discussion automatically."
},
"typeVersion": 1
}
],
"active": false,
"settings": {
"executionOrder": "v1"
},
"connections": {
"If": {
"main": [
[
{
"node": "Send a message",
"type": "main",
"index": 0
}
],
[
{
"node": "Booking Agent",
"type": "main",
"index": 0
}
]
]
},
"OpenAI": {
"ai_languageModel": [
[
{
"node": "Booking Agent",
"type": "ai_languageModel",
"index": 0
}
]
]
},
"AI Agent": {
"main": [
[
{
"node": "Send message and wait for response",
"type": "main",
"index": 0
}
]
]
},
"Get Events": {
"ai_tool": [
[
{
"node": "Booking Agent",
"type": "ai_tool",
"index": 0
}
]
]
},
"Booking Agent": {
"main": [
[
{
"node": "Send a message1",
"type": "main",
"index": 0
}
]
]
},
"Output Parser": {
"ai_outputParser": [
[
{
"node": "Booking Agent",
"type": "ai_outputParser",
"index": 0
}
]
]
},
"OpenAI Chat Model": {
"ai_languageModel": [
[
{
"node": "AI Agent",
"type": "ai_languageModel",
"index": 0
}
]
]
},
"Check Availability": {
"ai_tool": [
[
{
"node": "Booking Agent",
"type": "ai_tool",
"index": 0
}
]
]
},
"On form submission": {
"main": [
[
{
"node": "AI Agent",
"type": "main",
"index": 0
}
]
]
},
"Structured Output Parser": {
"ai_outputParser": [
[
{
"node": "AI Agent",
"type": "ai_outputParser",
"index": 0
}
]
]
},
"Send message and wait for response": {
"main": [
[
{
"node": "If",
"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 automates the complete employee leave approval process from submission to final resolution. Employees submit leave requests through a form, which are summarized professionally using AI and sent for approval via email. The workflow waits for the approver’s response…
Source: https://n8n.io/workflows/12070/ — 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 employee expense reimbursements from submission to final resolution. Expenses are captured via a form, reviewed by an AI agent for justification, and routed to managers for app
This workflow automates end-to-end contract and invoice management using AI intelligence. It processes proposals through intelligent contract generation, approval workflows, and automated invoicing. O
This n8n workflow template automates your lead generation and follow-up process using AI. It captures leads through a form, enriches them with company data, classifies them into different categories,
Automates SaaS operations by consolidating user management, AI-driven support triage, analytics, and billing into one unified system. User signups flow through registration, support requests route via
The workflow runs every hour with a randomized delay of 5–20 minutes to help distribute load. It records the exact date and time a lead is emailed so you can track outreach. Follow-ups are automatical