This workflow corresponds to n8n.io template #17074 — we link there as the canonical source.
This workflow follows the Chainllm → OpenAI Chat 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": "YOeDNnjmjtyThben",
"name": "Focus Time Defender \u2014 Auto-Block Deep Work Slots In Next Week's Calendar (Google Calendar \u2192 Slack)",
"tags": [],
"nodes": [
{
"id": "84f08fd6-b515-4b60-a824-5fa942563dd9",
"name": "Sticky Note",
"type": "n8n-nodes-base.stickyNote",
"position": [
-608,
-96
],
"parameters": {
"width": 480,
"height": 620,
"content": "## \ud83e\uddd8 Focus Time Defender\n\nEvery Sunday evening this workflow scans **next week's calendar**, finds the free gaps of 2+ hours inside your working hours, and books them as **\ud83e\uddd8 Focus Time** events before meetings can eat them. You get a Slack DM with your personal deep-work plan \u2014 or a warning if your week is already too fragmented.\n\n### \ud83d\udd27 Setup\n1. Connect **Google Calendar**, **OpenAI** and **Slack** credentials.\n2. Tune **\u2699\ufe0f My Preferences**: working hours, minimum block length, max blocks per week.\n3. Set your Slack handle in both Slack nodes.\n4. Activate \u2014 runs every **Sunday at 18:00**.\n\n### \ud83d\udca1 Why it's useful\n\"I'll find time for deep work\" never survives contact with other people's calendars. Booking the slots first reverses the default."
},
"typeVersion": 1
},
{
"id": "97624354-0dbb-4c28-aad7-56eb5d872932",
"name": "Sticky Note1",
"type": "n8n-nodes-base.stickyNote",
"position": [
368,
80
],
"parameters": {
"color": 4,
"width": 700,
"height": 420,
"content": "### 1\ufe0f\u20e3 Map next week\nLoad all events for the coming week and compute every free gap inside working hours (weekdays only)."
},
"typeVersion": 1
},
{
"id": "5664b582-910a-4cc0-954e-bfac44d5f2a6",
"name": "Sticky Note2",
"type": "n8n-nodes-base.stickyNote",
"position": [
1104,
80
],
"parameters": {
"color": 5,
"width": 660,
"height": 560,
"content": "### 2\ufe0f\u20e3 Claim the gaps\nIf usable gaps exist, book the best ones as \ud83e\uddd8 Focus Time events. If not, skip straight to the fragmentation warning."
},
"typeVersion": 1
},
{
"id": "f11518be-c644-48fb-835e-e041a0b9db93",
"name": "Sticky Note3",
"type": "n8n-nodes-base.stickyNote",
"position": [
1792,
80
],
"parameters": {
"color": 6,
"width": 656,
"height": 516,
"content": "### 3\ufe0f\u20e3 Your deep-work plan\nThe LLM writes a short personal plan suggesting what kind of work fits each protected block."
},
"typeVersion": 1
},
{
"id": "5a9031e8-97ec-4a55-83a3-10c4cc314205",
"name": "Every Sunday 18:00",
"type": "n8n-nodes-base.scheduleTrigger",
"position": [
-64,
336
],
"parameters": {
"rule": {
"interval": [
{
"field": "weeks",
"triggerAtHour": 18
}
]
}
},
"typeVersion": 1.2
},
{
"id": "5f0a20d0-8f67-4896-b30b-6b24bdfcaef1",
"name": "\u2699\ufe0f My Preferences",
"type": "n8n-nodes-base.set",
"position": [
144,
336
],
"parameters": {
"options": {},
"assignments": {
"assignments": [
{
"id": "pref-1",
"name": "workStartHour",
"type": "number",
"value": 9
},
{
"id": "pref-2",
"name": "workEndHour",
"type": "number",
"value": 18
},
{
"id": "pref-3",
"name": "minBlockHours",
"type": "number",
"value": 2
},
{
"id": "pref-4",
"name": "maxBlocksPerWeek",
"type": "number",
"value": 5
}
]
}
},
"typeVersion": 3.4
},
{
"id": "f8bd3aeb-7e2b-4c64-b5d0-6143cc50afd7",
"name": "Get Next Week's Events",
"type": "n8n-nodes-base.googleCalendar",
"position": [
432,
336
],
"parameters": {
"options": {},
"calendar": {
"__rl": true,
"mode": "list",
"value": ""
},
"operation": "getAll",
"returnAll": true
},
"typeVersion": 1.3
},
{
"id": "e6f5df43-ef4a-4609-b833-50ab1f783a74",
"name": "Find Free Gaps",
"type": "n8n-nodes-base.code",
"position": [
672,
336
],
"parameters": {
"jsCode": "// Find free gaps of at least `minBlockHours` inside working hours, Monday\u2013Friday\nconst prefs = $('\u2699\ufe0f My Preferences').first().json;\nconst events = $input.all()\n .map(i => i.json)\n .filter(e => e.start?.dateTime && e.status !== 'cancelled')\n .map(e => ({ start: new Date(e.start.dateTime), end: new Date(e.end.dateTime) }))\n .sort((a, b) => a.start - b.start);\n\nconst gaps = [];\nconst now = new Date();\n\nfor (let d = 1; d <= 6; d++) {\n const day = new Date(now);\n day.setDate(now.getDate() + d);\n const dow = day.getDay();\n if (dow === 0 || dow === 6) continue; // weekdays only\n\n const dayStart = new Date(day); dayStart.setHours(prefs.workStartHour, 0, 0, 0);\n const dayEnd = new Date(day); dayEnd.setHours(prefs.workEndHour, 0, 0, 0);\n\n const dayEvents = events.filter(e => e.start < dayEnd && e.end > dayStart);\n let cursor = dayStart;\n\n for (const e of [...dayEvents, { start: dayEnd, end: dayEnd }]) {\n const gapHours = (e.start - cursor) / 36e5;\n if (gapHours >= prefs.minBlockHours) {\n gaps.push({\n found: true,\n start: cursor.toISOString(),\n end: new Date(Math.min(e.start, cursor.getTime() + 3 * 36e5)).toISOString(),\n durationHours: Number(Math.min(gapHours, 3).toFixed(1)),\n weekday: day.toLocaleDateString('en-US', { weekday: 'long' })\n });\n }\n if (e.end > cursor) cursor = e.end;\n }\n}\n\nif (gaps.length === 0) {\n return [{ json: { found: false } }];\n}\n\nreturn gaps.map(g => ({ json: g }));"
},
"typeVersion": 2
},
{
"id": "7075c982-4012-4c21-ba50-49918598fbd7",
"name": "Any Focus Slots?",
"type": "n8n-nodes-base.if",
"position": [
912,
336
],
"parameters": {
"options": {},
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "loose"
},
"combinator": "and",
"conditions": [
{
"id": "if-1",
"operator": {
"type": "boolean",
"operation": "true",
"singleValue": true
},
"leftValue": "={{ $json.found }}",
"rightValue": true
}
]
}
},
"typeVersion": 2.2
},
{
"id": "26861c03-7c3e-490d-b851-6086e49cdd68",
"name": "Cap Blocks Per Week",
"type": "n8n-nodes-base.limit",
"position": [
1168,
240
],
"parameters": {
"maxItems": "={{ $('\u2699\ufe0f My Preferences').first().json.maxBlocksPerWeek }}"
},
"typeVersion": 1
},
{
"id": "1eea4e63-ad54-49ff-9c7a-790517f3441b",
"name": "Book Focus Block",
"type": "n8n-nodes-base.googleCalendar",
"position": [
1392,
240
],
"parameters": {
"end": "={{ $json.end }}",
"start": "={{ $json.start }}",
"calendar": {
"__rl": true,
"mode": "list",
"value": ""
},
"additionalFields": {
"color": "9",
"summary": "\ud83e\uddd8 Focus Time (protected by n8n)",
"description": "Deep work block booked automatically by the Focus Time Defender workflow. Decline meeting invites that collide with this!"
}
},
"typeVersion": 1.3
},
{
"id": "1bc68df0-ec22-4f91-abf0-2cdba175ec4c",
"name": "Bundle Booked Blocks",
"type": "n8n-nodes-base.aggregate",
"position": [
1616,
240
],
"parameters": {
"options": {},
"aggregate": "aggregateAllItemData",
"destinationFieldName": "blocks"
},
"typeVersion": 1
},
{
"id": "58f4b712-4810-4c19-9bba-04705310eec7",
"name": "Write My Focus Plan",
"type": "@n8n/n8n-nodes-langchain.chainLlm",
"position": [
1856,
240
],
"parameters": {
"text": "=I just protected these deep-work blocks in my calendar for next week:\n\n{{ JSON.stringify($('Cap Blocks Per Week').all().map(i => i.json), null, 2) }}\n\nWrite me a short, motivating Slack message (max 180 words):\n1. List each block (weekday + time range, human-readable)\n2. Suggest what type of work fits each slot (long blocks \u2192 hard creative work, shorter \u2192 reviews/writing)\n3. One practical tip to actually defend these blocks from meeting requests\n\nUse Slack formatting and keep it personal \u2014 this goes to one person, not a team.",
"messages": {
"messageValues": [
{
"message": "You are a supportive productivity coach who specializes in deep work."
}
]
},
"promptType": "define"
},
"typeVersion": 1.5
},
{
"id": "f1c908d3-c4c6-4e31-8e9e-89d1ef16d5a9",
"name": "OpenAI Chat Model",
"type": "@n8n/n8n-nodes-langchain.lmChatOpenAi",
"position": [
1856,
432
],
"parameters": {
"model": {
"__rl": true,
"mode": "list",
"value": "gpt-4o-mini"
},
"options": {}
},
"typeVersion": 1.2
},
{
"id": "26318297-4714-4f8e-a74b-2c4b70d72cc6",
"name": "DM My Focus Plan",
"type": "n8n-nodes-base.slack",
"position": [
2208,
240
],
"parameters": {
"text": "=\ud83e\uddd8 *Your deep-work plan for next week*\n\n{{ $json.text }}",
"user": {
"__rl": true,
"mode": "username",
"value": "your-slack-handle"
},
"select": "user",
"otherOptions": {}
},
"typeVersion": 2.3
},
{
"id": "9ec01fe1-a6be-4ff9-8836-44c101506c05",
"name": "Warn: Week Too Fragmented",
"type": "n8n-nodes-base.slack",
"position": [
1168,
448
],
"parameters": {
"text": "\u26a0\ufe0f *Focus Time Defender*: I couldn't find a single free 2-hour slot in your working hours next week. Your calendar is fully fragmented \u2014 consider declining or shortening some meetings, then re-run me.",
"user": {
"__rl": true,
"mode": "username",
"value": "your-slack-handle"
},
"select": "user",
"otherOptions": {}
},
"typeVersion": 2.3
}
],
"active": false,
"settings": {
"binaryMode": "separate",
"executionOrder": "v1"
},
"versionId": "8a4640bd-c675-4c80-b893-175b74e51e83",
"nodeGroups": [],
"connections": {
"Find Free Gaps": {
"main": [
[
{
"node": "Any Focus Slots?",
"type": "main",
"index": 0
}
]
]
},
"Any Focus Slots?": {
"main": [
[
{
"node": "Cap Blocks Per Week",
"type": "main",
"index": 0
}
],
[
{
"node": "Warn: Week Too Fragmented",
"type": "main",
"index": 0
}
]
]
},
"Book Focus Block": {
"main": [
[
{
"node": "Bundle Booked Blocks",
"type": "main",
"index": 0
}
]
]
},
"OpenAI Chat Model": {
"ai_languageModel": [
[
{
"node": "Write My Focus Plan",
"type": "ai_languageModel",
"index": 0
}
]
]
},
"Every Sunday 18:00": {
"main": [
[
{
"node": "\u2699\ufe0f My Preferences",
"type": "main",
"index": 0
}
]
]
},
"Cap Blocks Per Week": {
"main": [
[
{
"node": "Book Focus Block",
"type": "main",
"index": 0
}
]
]
},
"Write My Focus Plan": {
"main": [
[
{
"node": "DM My Focus Plan",
"type": "main",
"index": 0
}
]
]
},
"Bundle Booked Blocks": {
"main": [
[
{
"node": "Write My Focus Plan",
"type": "main",
"index": 0
}
]
]
},
"\u2699\ufe0f My Preferences": {
"main": [
[
{
"node": "Get Next Week's Events",
"type": "main",
"index": 0
}
]
]
},
"Get Next Week's Events": {
"main": [
[
{
"node": "Find Free Gaps",
"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 runs every Sunday at 18:00 to scan your Google Calendar for next week, find free gaps of at least two hours within your working hours, auto-book them as “🧘 Focus Time” events, and then DM you a personalized deep-work plan in Slack using OpenAI. Runs every Sunday at…
Source: https://n8n.io/workflows/17074/ — 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 builds an AI meeting assistant who sends information-dense pre-meeting notifications for a user's upcoming meetings. A scheduled trigger fires hourly and checks for upcoming meetings wit
This workflow accepts a URL via an n8n form, fetches and extracts the main text from the web page, uses OpenAI (GPT-4o) to generate a structured research card (summary, tags, quotes, actions, sentimen
This n8n-powered workflow automates the entire lifecycle of real estate lead intake, qualification, routing, assignment, and reporting across multiple channels. It brings WhatsApp inquiries and websit
My workflow 14. Uses rssFeedRead, chainLlm, lmChatOpenAi, openWeatherMap. Scheduled trigger; 40 nodes.
This workflow is designed for Japanese-speaking professionals, and learners who want to efficiently stay up to date with practical productivity, lifehack, and efficiency-related insights from Japanese