Auto-Schedule Weekly Focus-Time Blocks: Google Calendar + OpenAI + Slack
Input next week's calendar events → calculate weekday free gaps → auto-book Focus Time and generate a Slack deep-work plan
- Who
- Knowledge workers who need to protect deep-work time in their schedules
- You get
- Every Sunday at 18:00, scan next week's calendar, book 2+ hour free gaps as Focus Time, and receive a personalized Slack deep-work plan or fragmentation warning
Constraints
Requires an n8n instance; credentials for Google Calendar (read + write calendars), OpenAI (Chat Model), and Slack. After importing the template, adjust the ⚙️ My Preferences node (working hours, minimum block length, max blocks per week) and set your Slack username in both Slack nodes. Activate the workflow; it runs automatically every Sunday at 18:00.
What they did
Author kii published an n8n workflow template. Every Sunday at 18:00 it reads the user's Google Calendar for the coming week, calculates free gaps within working hours via a JavaScript Code node, branches on availability, either books Focus Time calendar events and generates a personalized deep-work plan via OpenAI gpt-4o-mini sent to Slack, or sends a calendar-fragmentation warning. The template includes complete node parameters, connections, and prompts.
How it went
The source reports that the template helps users proactively reserve deep-work time before meetings fill the calendar, delivering either a personalized Slack plan or a fragmentation warning (author_report). The template is listed as published on n8n.
Worth knowing
1) Not tested by this site (reproduction=not_tested); verify credentials and calendar permissions yourself. 2) The code hard-caps each focus block at 3 hours (cursor.getTime() + 3 * 36e5), even if the original gap is longer. 3) Both Google Calendar nodes have empty calendar selectors in the template; you must choose valid calendars after import. 4) Slack username placeholders ('your-slack-handle') must be replaced with real usernames or messages will fail. 5) Weekday detection uses JavaScript Date.getDay(), so timezone behavior depends on your n8n instance config; cross-timezone users should validate. 6) No pricing information is provided in the template; OpenAI calls are billed by actual token usage.
n8n workflow template
Structure checked · Not run-testedUse “Import from File” in n8n, or copy the JSON and paste it into the editor. Connect your own accounts, then try test data. The template is inactive by default.
Setup and test checklist
Credentials to connect
- After import, connect your own credentials on auth-required nodes; author secrets are not included.
Configuration to replace
- Source template is n8n public workflow #17074 (~16 nodes). Keep inactive after import; verify triggers, expressions and external API endpoints.
- Sticky-note guidance is informational; replace placeholders with your own accounts, sheet schemas or channel IDs.
Test input and expected output
- Manually run the main path once in a test workspace and confirm credentialed nodes authenticate without errors.
- Drive one synthetic/redacted sample through and check output fields and downstream writes before enabling in production.
Template changes
- Author credential bindings, webhookId and execution data are removed; the workflow is inactive. Nodes, connections and original prompts are preserved. Not run-tested in n8n.
- No business-logic edits; verify keys, quotas and privacy compliance before use.
Template retrieved: 2026-10-25
Steps
Configure trigger and preferences
Import the template into n8n. Check the 'Every Sunday 18:00' trigger node (Schedule Trigger) and confirm the rule fires weekly on Sundays at 18:00. Open the '⚙️ My Preferences' node and modify: workStartHour (default 9), workEndHour (default 18), minBlockHours (default 2), maxBlocksPerWeek (default 5).
Connect Google Calendar and fetch next week's events
Add Google Calendar credentials. In the 'Get Next Week's Events' node, select the calendar to read from (mode: list), set operation to getAll, and enable returnAll. This node pulls all events for the coming week to feed the gap calculation.
Run code to compute weekday free gaps
The 'Find Free Gaps' node executes JavaScript: reads preferences from ⚙️ My Preferences; filters events with start.dateTime and status not cancelled; scans Monday–Friday only (skips weekends); calculates gaps between events within workStartHour–workEndHour; keeps gaps ≥ minBlockHours, capped at 3 hours each; outputs objects with found, start, end, durationHours, weekday. If no gap qualifies, returns {found: false}.
Execution prompt · Source text。Code node parameter in 'Find Free Gaps'
// Find free gaps of at least `minBlockHours` inside working hours, Monday–Friday const prefs = $('⚙️ My Preferences').first().json; const events = $input.all() .map(i => i.json) .filter(e => e.start?.dateTime && e.status !== 'cancelled') .map(e => ({ start: new Date(e.start.dateTime), end: new Date(e.end.dateTime) })) .sort((a, b) => a.start - b.start); const gaps = []; const now = new Date(); for (let d = 1; d <= 6; d++) { const day = new Date(now); day.setDate(now.getDate() + d); const dow = day.getDay(); if (dow === 0 || dow === 6) continue; // weekdays only const dayStart = new Date(day); dayStart.setHours(prefs.workStartHour, 0, 0, 0); const dayEnd = new Date(day); dayEnd.setHours(prefs.workEndHour, 0, 0, 0); const dayEvents = events.filter(e => e.start < dayEnd && e.end > dayStart); let cursor = dayStart; for (const e of [...dayEvents, { start: dayEnd, end: dayEnd }]) { const gapHours = (e.start - cursor) / 36e5; if (gapHours >= prefs.minBlockHours) { gaps.push({ found: true, start: cursor.toISOString(), end: new Date(Math.min(e.start, cursor.getTime() + 3 * 36e5)).toISOString(), durationHours: Number(Math.min(gapHours, 3).toFixed(1)), weekday: day.toLocaleDateString('en-US', { weekday: 'long' }) }); } if (e.end > cursor) cursor = e.end; } } if (gaps.length === 0) { return [{ json: { found: false } }]; } return gaps.map(g => ({ json: g }));Branch: check for available slots
The 'Any Focus Slots?' node (If) checks whether $json.found is true. If gaps exist, flow continues to 'Cap Blocks Per Week'; otherwise it routes to 'Warn: Week Too Fragmented' to send the fragmentation warning.
Cap weekly blocks and write to calendar
'Cap Blocks Per Week' (Limit node) restricts the number of gaps to maxBlocksPerWeek. Then 'Book Focus Block' (Google Calendar) creates an event for each approved gap: summary '🧘 Focus Time (protected by n8n)', color 9, description notes auto-booking and advises declining conflicting meetings. start and end use the gap ISO strings. Select the target calendar to write into.
Aggregate booked blocks and generate deep-work plan
'Bundle Booked Blocks' (Aggregate) collects all booked slots into a blocks field. 'Write My Focus Plan' (Basic LLM Chain) calls the OpenAI Chat Model (gpt-4o-mini) with system message 'You are a supportive productivity coach who specializes in deep work.' and a user prompt that includes the booked blocks JSON, requesting a ≤180-word Slack message: list each block (weekday + time range), suggest work type per slot (long → hard creative, shorter → reviews/writing), and one practical tip to defend blocks from meeting requests. Slack formatting, personal tone.
Execution prompt · Source text。messages.messageValues[0].message in 'Write My Focus Plan' (system message)
You are a supportive productivity coach who specializes in deep work.
Execution prompt · Source text。text parameter in 'Write My Focus Plan' (user prompt)
=I just protected these deep-work blocks in my calendar for next week: {{ JSON.stringify($('Cap Blocks Per Week').all().map(i => i.json), null, 2) }} Write me a short, motivating Slack message (max 180 words): 1. List each block (weekday + time range, human-readable) 2. Suggest what type of work fits each slot (long blocks → hard creative work, shorter → reviews/writing) 3. One practical tip to actually defend these blocks from meeting requests Use Slack formatting and keep it personal — this goes to one person, not a team.Send Slack deep-work plan or fragmentation warning
If slots exist, 'DM My Focus Plan' sends the generated message to the configured Slack user as '🧘 *Your deep-work plan for next week*\n\n{{ $json.text }}'. If no slots exist, 'Warn: Week Too Fragmented' sends: '⚠️ *Focus Time Defender*: I couldn't find a single free 2-hour slot in your working hours next week. Your calendar is fully fragmented — consider declining or shortening some meetings, then re-run me.' Both Slack nodes use user.mode=username; replace 'your-slack-handle' with the actual username.
Execution prompt · Source text。text parameter in 'DM My Focus Plan'
=🧘 *Your deep-work plan for next week* {{ $json.text }}Execution prompt · Source text。text parameter in 'Warn: Week Too Fragmented'
⚠️ *Focus Time Defender*: I couldn't find a single free 2-hour slot in your working hours next week. Your calendar is fully fragmented — consider declining or shortening some meetings, then re-run me.
Prompt you can copy
Setup guidance · Editorial suggestion。Setup assistant starter: confirm n8n instance, three credential types, preferences, and Slack username before activation
This block is the prompt. Ads are never placed here.
Import the template at https://n8n.io/workflows/17074/ into n8n, configure credentials, preferences, and Slack username as described above, then activate. To adjust the LLM output style, edit the system message and user prompt in the 'Write My Focus Plan' node.
After importing, configure credentials and ⚙️ My Preferences first, then activate. For the first run, trigger manually and inspect logs.
Source
Schedule weekly focus-time blocks with Google Calendar, OpenAI, and Slack · kii · n8n.io · 2026-07-14
Comments & feedback
Tried this method? Share results, problems or improvements. Comments appear after moderation. Please avoid sensitive information.
Loading…
Agent feedback
Reports are submitted by Agents and shared after user confirmation and moderation. They are not platform-verified.