This workflow corresponds to n8n.io template #10144 — we link there as the canonical source.
This workflow follows the Google Calendar → HTTP Request 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": "K67zdPMDUtledQFV",
"name": "Global Holiday Conflict Detector and Meeting Rescheduler",
"tags": [],
"nodes": [
{
"id": "b487208f-33b3-4527-8e18-bb3c2a8746d7",
"name": "Daily Check",
"type": "n8n-nodes-base.scheduleTrigger",
"position": [
-752,
224
],
"parameters": {
"rule": {
"interval": [
{
"triggerAtHour": 9
}
]
}
},
"typeVersion": 1.2
},
{
"id": "a8f9264a-5dba-4801-9c1e-e12a59237b8b",
"name": "Workflow Configuration",
"type": "n8n-nodes-base.set",
"position": [
-528,
224
],
"parameters": {
"options": {},
"assignments": {
"assignments": [
{
"id": "id-1",
"name": "currentYear",
"type": "number",
"value": "={{ new Date().getFullYear() }}"
},
{
"id": "id-2",
"name": "nextWeekStart",
"type": "string",
"value": "={{ new Date(Date.now() + 7 * 24 * 60 * 60 * 1000).toISOString().split('T')[0] }}"
},
{
"id": "id-3",
"name": "nextWeekEnd",
"type": "string",
"value": "={{ new Date(Date.now() + 14 * 24 * 60 * 60 * 1000).toISOString().split('T')[0] }}"
},
{
"id": "id-4",
"name": "slackChannel",
"type": "string",
"value": "C09FB9QQQTX"
},
{
"id": "id-5",
"name": "calendarId",
"type": "string",
"value": "user@example.com"
},
{
"id": "b5660f95-e978-4a1a-9c33-225dfa4e9552",
"name": "countryCodes",
"type": "array",
"value": "[\"US\", \"GB\", \"DE\", \"IN\", \"CN\", \"KR\", \"HK\"]"
}
]
},
"includeOtherFields": true
},
"typeVersion": 3.4
},
{
"id": "6472f288-c7e2-49da-bd32-61b91d9528b3",
"name": "Merge and Filter Next Week Holidays",
"type": "n8n-nodes-base.code",
"position": [
144,
224
],
"parameters": {
"jsCode": "// Get the next week date range from Workflow Configuration\nconst nextWeekStart = new Date($('Workflow Configuration').first().json.nextWeekStart);\nconst nextWeekEnd = new Date($('Workflow Configuration').first().json.nextWeekEnd);\n\n// Combine all holiday data from the loop\nconst allHolidays = [];\n\n// Process all input items (which are the results from each loop iteration)\nfor (const item of $input.all()) {\n // The HTTP Request node in the loop outputs the holiday array in item.json\n if (Array.isArray(item.json)) {\n for (const holiday of item.json) {\n allHolidays.push(holiday);\n }\n }\n}\n\n// Filter holidays that fall within next week range\nconst nextWeekHolidays = allHolidays.filter(holiday => {\n const holidayDate = new Date(holiday.date);\n return holidayDate >= nextWeekStart && holidayDate <= nextWeekEnd;\n});\n\n// Map to desired output format\nconst formattedHolidays = nextWeekHolidays.map(holiday => ({\n date: holiday.date,\n name: holiday.name || holiday.localName,\n country: holiday.countryCode === 'US' ? 'United States' : \n holiday.countryCode === 'GB' ? 'United Kingdom' : \n holiday.countryCode === 'DE' ? 'Germany' : \n holiday.countryCode === 'IN' ? 'India' : \n holiday.countryCode === 'CN' ? 'China' : \n holiday.countryCode === 'KR' ? 'South Korea' : \n holiday.countryCode === 'HK' ? 'Hong Kong' : holiday.countryCode,\n countryCode: holiday.countryCode\n}));\n\n// Return all filtered holidays as a single item\nreturn [{\n json: {\n holidays: formattedHolidays\n }\n}];"
},
"typeVersion": 2
},
{
"id": "524535d0-6d38-4da8-a5c9-e775f4ff4e50",
"name": "Get Next Week Calendar Events",
"type": "n8n-nodes-base.googleCalendar",
"position": [
-304,
272
],
"parameters": {
"options": {},
"timeMax": "={{ $('Workflow Configuration').first().json.nextWeekEnd }}T23:59:59Z",
"timeMin": "={{ $('Workflow Configuration').first().json.nextWeekStart }}T00:00:00Z",
"calendar": {
"__rl": true,
"mode": "id",
"value": "={{ $('Workflow Configuration').first().json.calendarId }}"
},
"operation": "getAll",
"returnAll": true
},
"typeVersion": 1.3
},
{
"id": "5ed5e1c2-726a-43bd-8f49-ed40f3eac9fa",
"name": "Detect Holiday Conflicts",
"type": "n8n-nodes-base.code",
"position": [
480,
224
],
"parameters": {
"jsCode": "// Get the single item containing the list of all holidays from the first input\nconst holidayList = $input.all(0)[0].json.holidays || [];\n\n// Get all items containing calendar events from the second input\nconst calendarEventItems = $input.all(1);\n\n// Create a map of holiday dates for quick lookup\nconst holidayMap = new Map();\nholidayList.forEach(holiday => {\n const date = holiday.date;\n if (!holidayMap.has(date)) {\n holidayMap.set(date, []);\n }\n holidayMap.get(date).push({\n name: holiday.name,\n country: holiday.countryCode || holiday.country\n });\n});\n\n// Detect conflicts by iterating through each calendar event item\nconst conflicts = [];\ncalendarEventItems.forEach(eventItem => {\n const event = eventItem.json;\n const eventStart = event.start?.dateTime || event.start?.date;\n if (!eventStart) return; // Skip if no start time\n \n const eventDate = eventStart.split('T')[0];\n \n // Check if this date has any holidays\n if (holidayMap.has(eventDate)) {\n const holidaysOnDate = holidayMap.get(eventDate);\n const eventTime = eventStart.includes('T') ? eventStart.split('T')[1].substring(0, 5) : 'All day';\n const attendees = event.attendees ? event.attendees.map(a => a.email) : [];\n \n conflicts.push({\n eventName: event.summary || 'Untitled Event',\n eventDate: eventDate,\n eventTime: eventTime,\n holidayName: holidaysOnDate.map(h => h.name).join(', '),\n affectedCountries: [...new Set(holidaysOnDate.map(h => h.country))].join(', '),\n attendees: attendees,\n eventId: event.id\n });\n }\n});\n\n// Return all found conflicts in a SINGLE item to ensure subsequent nodes run only once\nreturn [{\n json: {\n conflicts: conflicts,\n totalConflicts: conflicts.length,\n checkDate: new Date().toISOString()\n }\n}];"
},
"typeVersion": 2
},
{
"id": "32c956cf-781f-4548-96a9-9d758a7f22a3",
"name": "Check If Conflicts Found",
"type": "n8n-nodes-base.if",
"position": [
752,
224
],
"parameters": {
"options": {},
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "id-1",
"operator": {
"type": "array",
"operation": "notEmpty"
},
"leftValue": "={{ $('Detect Holiday Conflicts').item.json.conflicts }}"
}
]
}
},
"typeVersion": 2.2
},
{
"id": "fd84ad56-55c4-49c4-9e60-368977208c0c",
"name": "Generate Reschedule Suggestions",
"type": "n8n-nodes-base.code",
"position": [
1072,
224
],
"parameters": {
"jsCode": "const conflicts = $input.first().json.conflicts || [];\n\n// Get the holiday list from the 'Merge and Filter' node which ran before the conflict detection\nconst holidays = $('Merge and Filter Next Week Holidays').first().json.holidays.map(h => h.date);\n\nfunction isHoliday(dateStr) {\n return holidays.includes(dateStr);\n}\n\nfunction isWeekend(date) {\n const day = date.getDay();\n return day === 0 || day === 6; // Sunday or Saturday\n}\n\nfunction findNextAvailableDate(startDate) {\n let currentDate = new Date(startDate);\n currentDate.setDate(currentDate.getDate() + 1);\n \n for (let i = 0; i < 30; i++) {\n const dateStr = currentDate.toISOString().split('T')[0];\n if (!isWeekend(currentDate) && !isHoliday(dateStr)) {\n const originalTime = startDate.toTimeString().split(' ')[0].substring(0, 5);\n return { date: dateStr, time: originalTime };\n }\n currentDate.setDate(currentDate.getDate() + 1);\n }\n \n return {\n date: currentDate.toISOString().split('T')[0],\n time: startDate.toTimeString().split(' ')[0].substring(0, 5)\n };\n}\n\nconst enhancedConflicts = conflicts.map(conflict => {\n const eventDateTime = conflict.eventTime === 'All day' ? new Date(conflict.eventDate) : new Date(`${conflict.eventDate}T${conflict.eventTime}`);\n const suggestion = findNextAvailableDate(eventDateTime);\n \n return {\n ...conflict,\n suggestedDate: suggestion.date,\n suggestedTime: suggestion.time,\n originalDate: conflict.eventDate,\n originalTime: conflict.eventTime\n };\n});\n\nreturn [{\n json: {\n conflicts: enhancedConflicts,\n totalConflicts: enhancedConflicts.length\n }\n}];"
},
"typeVersion": 2
},
{
"id": "4bb37c98-7400-4e4c-9519-4cafa5555067",
"name": "Format Slack Digest",
"type": "n8n-nodes-base.code",
"position": [
1376,
224
],
"parameters": {
"jsCode": "// Get the conflicts data from the previous node\nconst conflictsData = $input.first().json;\nconst conflicts = conflictsData.conflicts || [];\n\n// Use a single backslash \\n for newlines\nlet slackMessage = ':warning: *Holiday Conflict Alert* :warning:\\n\\n';\n\nif (conflicts.length === 0) {\n slackMessage += 'No conflicts detected for next week. All clear! :white_check_mark:';\n} else {\n slackMessage += `Found *${conflicts.length}* meeting(s) scheduled during public holidays next week:\\n\\n`;\n \n conflicts.forEach((conflict, index) => {\n slackMessage += `*${index + 1}. ${conflict.eventName}*\\n`;\n slackMessage += `:calendar: *Date:* ${conflict.originalDate}\\n`;\n slackMessage += `:clock3: *Time:* ${conflict.originalTime}\\n`;\n slackMessage += `:earth_americas: *Affected Countries:* ${conflict.affectedCountries}\\n`;\n slackMessage += `:pushpin: *Holiday:* ${conflict.holidayName}\\n`;\n \n // Add suggestion if it exists\n if (conflict.suggestedDate) {\n slackMessage += `:bulb: *Suggestion:* Reschedule to ${conflict.suggestedDate} at ${conflict.suggestedTime}\\n`;\n }\n \n slackMessage += '\\n---\\n\\n';\n });\n \n slackMessage += ':point_right: Please review and reschedule these meetings to accommodate team members in affected regions.';\n}\n\n// Return the formatted message\nreturn [{\n json: {\n slackMessage: slackMessage\n }\n}];"
},
"typeVersion": 2
},
{
"id": "d01a7818-dc28-4490-ad5b-e5963cd22cbd",
"name": "Post Slack Digest",
"type": "n8n-nodes-base.slack",
"position": [
1680,
224
],
"parameters": {
"text": "={{ $json.slackMessage }}",
"select": "channel",
"channelId": {
"__rl": true,
"mode": "id",
"value": "={{ $('Workflow Configuration').first().json.slackChannel }}"
},
"otherOptions": {},
"authentication": "oAuth2"
},
"typeVersion": 2.3
},
{
"id": "d04fe2ce-a6f3-49d5-a384-39a3a201fa37",
"name": "Loop Over Items",
"type": "n8n-nodes-base.splitInBatches",
"position": [
-304,
80
],
"parameters": {
"options": {}
},
"typeVersion": 3
},
{
"id": "74320878-4302-440c-99ce-5aefefcb1ca2",
"name": "Fetch Public Holidays",
"type": "n8n-nodes-base.httpRequest",
"position": [
-80,
32
],
"parameters": {
"url": "=https://date.nager.at/api/v3/PublicHolidays/{{ $('Workflow Configuration').first().json.currentYear }}/{{ $json.countryCodes }}",
"options": {}
},
"typeVersion": 4.2
},
{
"id": "392bf796-1880-4a89-ad6b-7838a7a19351",
"name": "Note: Daily Check",
"type": "n8n-nodes-base.stickyNote",
"position": [
-816,
64
],
"parameters": {
"color": "white",
"height": 144,
"content": "**Purpose:** Triggers the workflow once every weekday morning.\n**Key:** Runs at 09:00 server time (adjust in node).\n**Tip:** Change to weekly if you only need Monday runs."
},
"typeVersion": 1
},
{
"id": "0541544f-2e8e-41a7-b899-76de44829cca",
"name": "Note: Workflow Configuration",
"type": "n8n-nodes-base.stickyNote",
"position": [
-544,
-208
],
"parameters": {
"color": "white",
"width": 256,
"height": 208,
"content": "**Purpose:** Central place to define variables.\n**Fields:** `currentYear`, `nextWeekStart`, `nextWeekEnd`, `countryCodes`, `slackChannel`, `calendarId`.\n**Tip:** Edit country list and calendar/channel IDs here only."
},
"typeVersion": 1
},
{
"id": "02931af5-a167-422c-ae32-53412ed0f4f4",
"name": "Note: Merge and Filter Next Week Holidays",
"type": "n8n-nodes-base.stickyNote",
"position": [
80,
48
],
"parameters": {
"color": "white",
"content": "**Purpose:** Merges API results and filters to next week only.\n**Output:** `holidays[]` with `date`, `name`, `countryCode`."
},
"typeVersion": 1
},
{
"id": "79ac359f-12a3-43ba-bf75-b987f37f482e",
"name": "Note: Get Next Week Calendar Events",
"type": "n8n-nodes-base.stickyNote",
"position": [
-368,
480
],
"parameters": {
"color": "white",
"content": "**Purpose:** Reads all events in next week\u2019s window from Google Calendar.\n**Config:** Uses `calendarId` and `timeMin/Max` from the Set node.\n**Note:** Re-connect your own Google credential in n8n."
},
"typeVersion": 1
},
{
"id": "165237d2-da5e-408f-bb46-77be9bf4c38c",
"name": "Note: Detect Holiday Conflicts",
"type": "n8n-nodes-base.stickyNote",
"position": [
384,
48
],
"parameters": {
"color": "white",
"content": "**Purpose:** Compares event dates with holiday dates to find conflicts.\n**Output:** Single item `{ conflicts[], totalConflicts }` so downstream runs once."
},
"typeVersion": 1
},
{
"id": "3b55ac06-aa21-44de-a6e0-37051e02cfa1",
"name": "Note: Check If Conflicts Found",
"type": "n8n-nodes-base.stickyNote",
"position": [
688,
48
],
"parameters": {
"color": "white",
"content": "**Purpose:** Guards the branch; continues only when conflicts exist.\n**Condition:** Array not empty."
},
"typeVersion": 1
},
{
"id": "fc01dc46-773b-4881-b646-2bcf21c7bc1f",
"name": "Note: Generate Reschedule Suggestions",
"type": "n8n-nodes-base.stickyNote",
"position": [
992,
48
],
"parameters": {
"color": "white",
"content": "**Purpose:** Suggests next business day that is not a holiday/weekend.\n**Logic:** Looks ahead up to 30 days, preserving original start time."
},
"typeVersion": 1
},
{
"id": "cec6277f-583d-4ba9-8063-2e956395bff5",
"name": "Note: Format Slack Digest",
"type": "n8n-nodes-base.stickyNote",
"position": [
1296,
48
],
"parameters": {
"color": "white",
"content": "**Purpose:** Creates a readable Slack message.\n**Includes:** Event, date/time, affected countries, holiday, suggestion."
},
"typeVersion": 1
},
{
"id": "6d766a0d-bea6-4bbb-a824-127b53b9dc45",
"name": "Note: Post Slack Digest",
"type": "n8n-nodes-base.stickyNote",
"position": [
1600,
48
],
"parameters": {
"color": "white",
"content": "**Purpose:** Posts the digest to Slack.\n**Config:** Uses `slackChannel` from the Set node.\n**Note:** Re-connect your own Slack OAuth in n8n (left unconfigured)."
},
"typeVersion": 1
},
{
"id": "8461d7cc-e39d-4c86-9c6f-96a7aa55ba4f",
"name": "Note: Loop Over Items",
"type": "n8n-nodes-base.stickyNote",
"position": [
-208,
-160
],
"parameters": {
"color": "white",
"content": "**Purpose:** Iterates through each country code.\n**Flow:** Splits the array to call the holiday API per country, then loops back."
},
"typeVersion": 1
},
{
"id": "f857f854-7ed9-43c1-af05-7e596d8369dc",
"name": "Note: Fetch Public Holidays",
"type": "n8n-nodes-base.stickyNote",
"position": [
96,
-192
],
"parameters": {
"color": "white",
"width": 288,
"height": 192,
"content": "**Purpose:** Calls Nager.Date API for public holidays.\n**URL:** `https://date.nager.at/api/v3/PublicHolidays/{year}/{country}`.\n**Security:** No API key needed. Keep credentials empty."
},
"typeVersion": 1
},
{
"id": "48d8def6-1e2e-4e03-b963-a44f9e2c0de1",
"name": "Template Overview",
"type": "n8n-nodes-base.stickyNote",
"position": [
-1632,
-448
],
"parameters": {
"color": "yellow",
"width": 688,
"height": 752,
"content": "## What this template does\nDetects global public-holiday conflicts in **next week\u2019s** meetings and posts a Slack digest with suggested reschedule dates.\n\n## Who it\u2019s for\nRemote and distributed teams that want to avoid scheduling meetings on regional holidays.\n\n## How it works\n1) Fetch public holidays for selected country codes. \n2) Pull next week\u2019s events from Google Calendar. \n3) Detect date overlaps and generate a reschedule suggestion (next weekday that isn\u2019t a holiday). \n4) Post a single Slack summary message.\n\n## How to set up\n- Configure **countryCodes**, **calendarId**, and **slackChannel** in the **Workflow Configuration** (Set) node. \n- Connect your own Google Calendar and Slack credentials in n8n (left as unauthenticated by design).\n\n## Requirements\n- n8n (self-hosted or Cloud) \n- Slack app with chat:write and channel access \n- Google Calendar with read access\n\n## Customize\n- Change the time window by editing `nextWeekStart/End` in **Workflow Configuration**. \n- Adjust the suggestion logic in **Generate Reschedule Suggestions**."
},
"typeVersion": 1
}
],
"active": false,
"settings": {
"executionOrder": "v1"
},
"versionId": "c01575e0-ea13-45a5-bb0a-c771b4107645",
"connections": {
"Daily Check": {
"main": [
[
{
"node": "Workflow Configuration",
"type": "main",
"index": 0
}
]
]
},
"Loop Over Items": {
"main": [
[
{
"node": "Merge and Filter Next Week Holidays",
"type": "main",
"index": 0
}
],
[
{
"node": "Fetch Public Holidays",
"type": "main",
"index": 0
}
]
]
},
"Format Slack Digest": {
"main": [
[
{
"node": "Post Slack Digest",
"type": "main",
"index": 0
}
]
]
},
"Fetch Public Holidays": {
"main": [
[
{
"node": "Loop Over Items",
"type": "main",
"index": 0
}
]
]
},
"Workflow Configuration": {
"main": [
[
{
"node": "Loop Over Items",
"type": "main",
"index": 0
},
{
"node": "Get Next Week Calendar Events",
"type": "main",
"index": 0
}
]
]
},
"Check If Conflicts Found": {
"main": [
[
{
"node": "Generate Reschedule Suggestions",
"type": "main",
"index": 0
}
]
]
},
"Detect Holiday Conflicts": {
"main": [
[
{
"node": "Check If Conflicts Found",
"type": "main",
"index": 0
}
]
]
},
"Generate Reschedule Suggestions": {
"main": [
[
{
"node": "Format Slack Digest",
"type": "main",
"index": 0
}
]
]
},
"Merge and Filter Next Week Holidays": {
"main": [
[
{
"node": "Detect Holiday Conflicts",
"type": "main",
"index": 0
}
]
]
}
}
}
For the full experience including quality scoring and batch install features for each workflow upgrade to Pro
About this workflow
Who’s it for
Source: https://n8n.io/workflows/10144/ — 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.
Busy professionals who want a quick daily update combining their calendar, weather, and top news.
This workflow is an automated employee time tracking and reporting system that monitors weekly work hours via TMetric, then delivers personalized summaries directly to each team member on Slack. It co
Import Productboard Notes Companies And Features Into Snowflake. Uses stickyNote, httpRequest, splitOut, snowflake. Scheduled trigger; 35 nodes.
Import Productboard Notes, Companies and Features into Snowflake. Uses stickyNote, httpRequest, splitOut, snowflake. Scheduled trigger; 35 nodes.
This workflow imports Productboard data into Snowflake, automating data extraction, mapping, and updates for features, companies, and notes. It supports scheduled weekly updates, data cleansing, and S