Your first workflow: today's calendar in Slack, every morning at eight
Everything so far has been reading — other people's templates, other people's workflows, the four families a node can belong to. This part is the first time you start with an empty gray grid and finish with something that runs without you. At 8:00 every morning, n8n reads today's Google Calendar and drops the titles and times into your Slack. Three nodes do it. And the shape those three nodes make — trigger, then fetch, then send — is the skeleton under every workflow you will build after this one.
A first workflow you will still be running in two years
The question that has been sitting there since Part 3 is “so when do I actually get to build one myself?” Now. And “push the calendar to Slack in the morning” is a deliberate choice rather than a convenient toy, for four reasons.
- You will actually use it. It is the thing you glance at over the first coffee of the day. The author of the source guide has been running theirs for over two years.
- Three nodes are enough. Schedule → Google Calendar → Slack. Those three happen to cover all three pieces of the pattern: the trigger, the fetch, the send.
- Both services are ones your company already has. No odd third-party account to sign up for. Your existing Google Workspace and company Slack are all this needs.
- Failure does no damage. This workflow only reads. It never writes anything back. The worst case is that Slack gets no message; nothing can be deleted by accident.
flowchart LR A["When it runs
Schedule Trigger, every day at 8:00 am"] --> B["What data to fetch
Google Calendar · Event · Get Many"] B --> C["What to do with it
Slack · Message · Send Message"]
If you have never set up a Slack or a Google credential, this article walks you through both as they come up. If you would rather deal with credentials in one sitting first, go to Part 10, connect both accounts there, and come back.
Settle three things before you touch the canvas
Before building any workflow, answer three questions — on paper, or just in your head. This is the single most useful habit in the whole guide. Once you have it, anything you want to automate breaks down into an n8n skeleton in about thirty seconds.
It is the three things a courier firm settles before a van moves. When do we drive? What are we picking up? Where does it get dropped off? Nobody plans a delivery by arguing about the van. The route is the answer, and the van is whatever is parked outside.
| The question | What that is in n8n | The answer this time |
|---|---|---|
| When should it run? | Which Trigger node to use | Same time every day, so a Schedule Trigger set to 08:00 |
| What data does it need? | The SaaS node in the middle | Today's calendar, so the Google Calendar node with Resource Event and Operation Get Many |
| What should it do? | The last action node | Send a message to myself, so the Slack node with Resource Message and Operation Send Message |
Answer the three and the design work is finished. You already know which nodes to add and which operation each one needs. The rest is filling in blanks.
Three preparations, too, before the first click. Make sure you are signed in to n8n.woowtech.io. Make sure the Google account you will authorize can actually see your own Calendar. And have a Slack channel that only you can see — or plan to use your own direct message — so your test messages land somewhere harmless.
Eleven steps from an empty grid
Work down these in order. Each one leaves the canvas in the state the next one expects, and the warnings sit at the steps where they bite rather than collected at the end.
-
Step 1
Open the Workflows list and click Add workflow
After you sign in you land on the Workflows list by default. If you do not see it, click Workflows in the left sidebar. At the top right of the list is a
+ Add workflowbutton. Click it.The screen switches to a blank canvas — a gray grid with a single large
+ Add first stepbutton in the middle of it. That empty grid is where every workflow in n8n begins. -
Step 2
Name it, then save before you build anything
At the top of the screen sits a default name,
My workflow. Click it and change it to something you can find again —Daily calendar digestworks. Next to the name is Save (orCtrl+S). Save once now, so that a browser crash halfway through does not cost you the lot.A naming habit worth adopting on your very first workflow: verb, object, timing.
Daily calendar digest,Hourly RSS fetch,Slack order to Notion. Once you have thirty of these, the name is the only thing telling you what each one does. -
Step 3
Add the first step and choose Schedule Trigger
Click
+ Add first stepin the middle of the canvas. The nodes panel opens on the right, and the first few groups in it are the Trigger types. Find On a schedule — or just typescheduleinto the search box — and click it.A node with a clock icon appears in the middle of the canvas. That is your Schedule Trigger.
-
Step 4
Set it to every day at eight
Double-click the node you just added to open its parameters panel, and set four fields:
Trigger Interval — pickDaysfrom the dropdownDays Between Triggers —1Trigger at Hour — pick8amfrom the dropdownTrigger at Minute —0Two of those trip people up. The interval dropdown really does say
Days; there is no “Every Day” option, so do not go looking for one. And Trigger at Hour is a dropdown rather than a number box — its options readMidnight / 1am / 2am / ... / Noon / 1pm ..., so you choose8amfrom the list. Trigger at Minute is the one that really is a number box, taking 0–59.When you are done, click
← Back to canvasat the top left of the panel, or pressEsc. A schedule summary now appears under the node, along the lines ofEvery day at 8:00 am. -
Step 5
Add Google Calendar and set the operation
Move the mouse to the right of the Schedule Trigger and a small
+appears. Click it, typegoogle calendarinto the search box in the panel that opens, and click the Google Calendar node.In the node panel, set Resource to
Eventand Operation toGet Many. Together they mean “fetch several calendar events”. The dropdown says exactlyGet Many—Eventis the resource name, and the n8n interface never spells the operation out as “Get Many Events”, so do not hunt for that wording. -
Step 6
Connect a Google credential
If the Credential to connect with field at the top of the panel is empty, open its dropdown and choose Create new credential. An OAuth window opens, you pick a Google account, and you allow n8n to read your Calendar. Once you allow it, the window closes by itself and the credential is connected.
Google will warn you that the app is not verified. That warning is expected here: Woow n8n is a self-hosted instance rather than an app that has been through Google's public verification. ClickAdvanced→Go to n8n.woowtech.io (unsafe)and continue. What you are authorizing is your own company's server, which is not the same proposition as authorizing a stranger's public app — but do read the account name on that screen before you click, because this is the step where people authorize the wrong Google account. -
Step 7
Choose your calendar and narrow it to today
Back in the Google Calendar node panel, keep filling in:
Calendar — pick your own from the dropdown, usually shown as your email address orPrimaryReturn All —ON, or turn it off and set a Limit instead, say50Calendar is a resource locator, which is n8n's name for a field you can fill in more than one way:
From listpicks from a dropdown,By IDtakes a pasted calendar ID.Then the two fields that bound the query to today. Both sit directly on the panel — not tucked away under Options. Each has a small
fxicon to its right that switches the field into Expression mode, and each needs one expression:After —{{ $now.startOf('day') }}Before —{{ $now.endOf('day') }}The After field defaults to
{{ $now }}, and is calledtimeMininternally; Before istimeMax. Together the two expressions mean “from 00:00 this morning to 23:59:59.999 tonight”. An expression is n8n's syntax for a value worked out at run time rather than typed in advance, and Part 10 takes the syntax apart properly.If you are following an older tutorial, the field names have moved. Plenty of guides — including early versions of the official n8n documentation — call these two fieldsTime MinandTime Max, which are the underlying Google Calendar API names. What the interface shows today isAfterandBefore. Not finding “Time Min” is normal, and it is not hiding under Options. -
Step 8
Run just this node, and look at what came back
At the top right of the Google Calendar panel is Execute step — some versions label the same button
Test steporExecute Node. Click it. This runs that one node only; nothing downstream of it is triggered.The OUTPUT panel on the right then lists one n8n item per event. Five events today means five items. Each item carries
summary(the title),start.dateTime,end.dateTimeand the other event fields at its top level — which is why, inside the Slack node,$json.summarygives you the title of the current item rather than all of them.Seeing that list is the confirmation that the data came through. If the list is empty, check whether you actually have anything in the calendar today before you assume something is broken.
-
Step 9
Add Slack — and an Aggregate node before it
Click the
+to the right of the Google Calendar node, searchslack, and click Slack. Set Resource toMessageand Operation toSend Message. The credential works exactly as the Google one did: the first time, Create new credential and run through OAuth once to authorize your Slack workspace.Before you wire Slack straight on, though, it is well worth inserting an Aggregate node between Google Calendar and Slack, with Fields to Aggregate set to
summary. That folds “N events” into “one item holding an array”, and Slack then sends one message instead of one per event. Step 10 assumes you added it. -
Step 10
Pick a safe channel and build the message text
Three fields on the Slack node:
Send Message To —Channel(the dropdown also offersUser)Channel — a resource locator:From list, orBy nameand type#daily-me, orBy IDand pasteC0XXXXXXXMessage Text — some older versions label this fieldTextDo not test against#general. Point this at a channel only you can see, or direct-message yourself by switching Send Message To toUserand picking your own name. A first workflow gets run and re-run while you fix expressions, and every one of those runs is a real Slack message to whoever is in that channel.Now switch Message Text into Expression mode with the small
fxto its right, and enter this as two lines — the first line, then the second beneath it:Good morning! You have {{ $json.summary.length }} events today:{{ $json.summary.join(', ') }}That reads as “after Aggregate,
summaryis already an array, so take its length and join it directly”. If you skipped the Aggregate node, use this pair instead, where the Slack node looks at the whole batch of upstream items itself:Good morning! You have {{ $input.all().length }} events today:{{ $input.all().map(i => i.json.summary).join(', ') }}Then click Execute step for a manual test and switch to Slack. The message should be sitting there.
-
Step 11
Save, and then flip Inactive to Active
Click Save, or
Ctrl+S. Then the step that decides whether any of this was worth doing: flip the Inactive / Active switch at the top right of the screen, immediately beside Save at the right-hand end of the header bar that carries the workflow name.Leave that switch alone and the Schedule Trigger stays asleep. The time comes round, nothing fires, and the only way to run the thing is to click Execute Workflow yourself — which rather defeats the point.
That is the whole build. Tomorrow at 8:00 it reads your calendar and posts to your Slack with nothing further from you.
Saving and activating are two different acts, and this is where nine out of ten first workflows quietly die. Saving is writing the shift on the rota and pinning it to the wall. Activating is telling somebody to actually turn up. A perfectly written rota that nobody was told about produces exactly the same result as no rota at all: an empty shop at eight in the morning.
Why one event became five messages, and three layouts worth stealing
Before the cosmetics, the thing that surprises everybody on their first build. The Google Calendar node does not hand the next node one object with a list inside it. It expands every event into its own n8n item. Five events today means five items travelling down the wire, and by default an action node runs once for each item it receives.
The Calendar node gives you five separate envelopes, not one folder with five sheets in it. Slack does what it is told with each envelope it is handed, which is five messages in your channel before breakfast. The Aggregate node is the step where you put the five sheets into one folder first.
flowchart TD A["Google Calendar · Get Many"] --> B["5 events today become
5 separate n8n items"] B --> C{"Is there an Aggregate node
before the Slack node?"} C -->|"no"| D["Slack runs once per item,
its default behavior"] D --> E["5 messages in the channel"] C -->|"yes, Fields to Aggregate
set to summary"| F["5 items folded into 1 item
holding an array"] F --> G["Slack runs once"] G --> H["1 message in the channel"]
items field, so never write $json.items.map(...). The shape with an items array is the raw Google API response, and n8n has already flattened it away. Your two working choices are $json.xxx, which sees only the current item, and $input.all(), which gets you every upstream item — or the Aggregate node, which changes the shape so that $json.summary is itself the array.With that settled, the message itself. Step 10 used the bare minimum. Slack supports its own flavor of markdown, called mrkdwn, and a little emoji makes a morning notification look like something rather than a log line. Three layouts cover most needs. All three assume the Aggregate node is in place with Fields to Aggregate set to summary, start; without it, replace $json.summary with $input.all().map(i => i.json.summary).
| Layout | What to put in Message Text | When to use it |
|---|---|---|
| Bare minimum | {{ $json.summary.length }} events today |
You want the number, not the detail. Good on heavy meeting days, so Slack does not get swamped |
| Bulleted | Today's schedule:\n{{ $json.summary.map((s, i) => '• ' + s + ' @ ' + $json.start[i].dateTime).join('\n') }} |
You want to see exactly when each one is. The everyday workhorse |
| Markdown | *Today's schedule* :calendar:\n> {{ $json.summary.map((s, i) => s + ' (' + $json.start[i].dateTime + ')').join('\n> ') }} |
Formal enough to quote and pass on. It looks more presentable when a colleague sees it |
The Slack syntax those layouts lean on, in short:
*bold*, _italic_, ~strikethrough~`inline code`, ```code block```> quote — a > at the start of each line:calendar:, :coffee:, :tada:\n — inside an expression that really is the two characters, backslash and nThree buttons that look alike and are not
Two buttons on the canvas and one switch in the header do genuinely different jobs, and confusing them is how people conclude that n8n is unpredictable.
| Control | Where it is | What it actually does |
|---|---|---|
| Execute Node | Top right of a node's panel | Runs that one node. Nothing downstream is triggered. Use it right after configuring a node, to check the data is what you expected |
| Execute Workflow | Bottom center of the canvas, or top right | Runs from the Trigger through to the end, but writes nothing to the execution log — some versions do write it, depending on the settings. This is your end-to-end test while you build |
| Active toggle, switched on | Top right of the screen | Puts the Schedule Trigger into its live state: wait for the time, then run. This is the hand-over, not a test |
In one line: Execute Workflow is for you to watch. Active hands it over to the clock. Use the first while you build, switch the second on when you are finished.
stateDiagram-v2 [*] --> Draft Draft --> Saved: Save, or Ctrl-S Saved --> Inactive: the toggle is still off Inactive --> Active: you flip Inactive to Active Inactive --> Running: only when you click Execute Workflow yourself Active --> Running: the scheduled time arrives and the Trigger fires Running --> Success: every node finished Running --> Error: one node threw Success --> Active: waiting for tomorrow Error --> Active: still active, and now a red row on the list
Once a workflow runs on its own, the only way to answer “did it run yesterday, did it succeed, what did the data look like” is the Executions page.
It is the till roll. Every run leaves a line on it whether anyone reads it or not, and when a customer says the machine short-changed them at half past eight, the roll is the only thing in the building that can say what actually happened. Screenshots of a Slack channel cannot.
- Two ways in. Click Executions in the sidebar and you get the runs of every workflow on the account. Or open a workflow and switch to its Executions tab at the top, which shows only that workflow's runs. The first time you go looking, the second route keeps everyone else's records out of the way.
- Read the list by color. Green
Successmeans everything went through. RedErrormeans a node somewhere blew up. YellowWaitingmeans paused, and it barely comes up in this course material. The time column shows when the run happened and how long it took. - Open a run to see each node's data. Inside, the view is almost identical to the editing canvas, except that every node also shows what it took in and what it put out on that particular run. Click the Google Calendar node and you are looking at the event JSON it actually fetched at 8:00 that morning.
- The broken node has a red border. When a run fails, the node that failed is outlined in red; click it and the error message appears below. This is roughly ten times faster than reading logs. If the message means nothing to you, paste the whole thing into a search engine or send it to IT — that usually finds the cause in a couple of moves.
Same skeleton, different parts
Once the shape makes sense, everything else is swapping components. The same “push the calendar in the morning” logic becomes a different tool entirely when you change one or two things. These five are variations the source author has actually run.
| What you want | What to change | How to change it |
|---|---|---|
| Workdays only, not every day | Schedule Trigger | Change Trigger Interval to Days of the week and tick Mon–Fri |
| Tomorrow's schedule before you leave, too | A second Schedule | Duplicate the whole workflow, set the Schedule to 17:30, and change the Google Calendar time bounds to {{ $now.plus({days:1}).startOf('day') }} through .endOf('day') |
| Push to the team, not just yourself | Slack Channel | Change Channel from #daily-me to #team-daily. Reading from a shared team calendar makes the content more useful too |
| Push to LINE instead of Slack | Swap the node | Delete the Slack node and add a LINE Notify node, or call LINE's webhook with an HTTP Request node. Part 8 covers the integrations |
| No push on days with nothing scheduled | An IF node in the middle | Wire an IF after Google Calendar, with a condition testing that the event count is not equal to 0, and connect Slack to the True branch only. IF gets a part of its own later |
{{ $json.items.length }} not equal to 0 — but the same chapter states plainly, twice, that the Google Calendar node's output has no items field, because n8n has already flattened the raw API response. Both cannot be true. Build the condition from a shape you have actually seen in the OUTPUT panel: {{ $input.all().length }} without an Aggregate node, or {{ $json.summary.length }} with one. Run the node, look at what came back, and write the expression against that rather than against any guide's table — this one included.Notice that none of the five is “build another workflow”. They are all “the same skeleton, different parts”. That is what n8n is best at, and it is why the pattern is worth more to you than the specific workflow.
Strip out every detail from this article and one thing is left: Trigger → Action → Action. Every workflow you meet from here, however complicated it looks on the canvas, has those bones. There is one trigger, then zero or more actions in the middle, and usually at least one action at the end. The later parts only ever expand the middle.
Data has to branch
Drop an IF or a Switch into the middle. Both are core nodes rather than service integrations, so nothing needs authorizing.
Several sources at once
Use Merge to bring two streams back together before the action that consumes them.
Data needs cleaning or adding up
Use an Edit Fields (Set) or a Code node to reshape it. Aggregate, which you met in Step 9, belongs to this family.
A different service entirely
Swap out Google Calendar, swap out Slack. The pattern does not change, and neither does anything you learned building this one.
The faults almost everyone hits once
Work down these in order rather than at random. Most first-morning silences are the first item on the list.
flowchart TD
A["No message arrived this morning"] --> B{"Does the header say
Active rather than Inactive?"}
B -->|"no"| B1["Flip it. Saving and activating
are two separate acts"]
B -->|"yes"| C{"Is there a row for last night
on the Executions page?"}
C -->|"no row at all"| C1["Check the workflow timezone,
whether you saved
before leaving, and whether
the Trigger node was
deactivated and turned gray"]
C -->|"a red row"| D["Open the run. The failing node
has a red border, with its
error message underneath"]
C -->|"a green row"| E{"Did Slack print
the double braces
as literal text?"}
E -->|"yes"| E1["Message Text was never switched
to Expression mode with the fx icon"]
E -->|"no"| F["Check which channel
you targeted, and whether
today had any events"]
- The Calendar dropdown is empty after the Google OAuth. Usually the Calendar scope was not granted during authorization, or the wrong Google account was used. Go back to the credential page, delete that credential, run Create new credential again, and check carefully which Google account you pick. The consent screen has to show the line about seeing the events on your calendars.
- The Slack node turns red with
401,not_authedorinvalid_auth. The credential is not set up properly, or its token has expired. On the credential page: confirm the Slack workspace is the right one, run OAuth again, and if your company Slack requires admin approval for app installs, you may need IT to approve the n8n app before you can connect at all. - The Schedule Trigger did not run the next morning. Ninety percent of the time the Active toggle was never switched on. Otherwise: you forgot to save before leaving, so the schedule never took effect; the Trigger node was deactivated by right-clicking it and choosing Deactivate, which turns it gray and stops the whole workflow; or the timezone is not what you assumed.
- The Executions page has no records at all. The time may not have come round yet, so the schedule has never fired. The Trigger node may be disabled — check whether it is gray. You may have run it by hand while the workflow's save-data setting was off, in which case set
Workflow settings → Save execution progresstoOn. Or you are looking at the account-wide Executions list and your run is buried several pages down. - Slack received the message, but the body is the literal text
{{ $json.items.length }}. The Message Text field was never switched into Expression mode. Click the smallfxto the right of the field; only after that does{{ ... }}get evaluated instead of treated as plain text. Once switched,{{ $json.summary.length }}should preview as a number straight away. - The message went out, but an event title shows
undefined. The Google Calendar API sometimes returns events with nosummaryfield — an empty all-day entry, or certain calendar privacy settings. Supply a fallback with||: write{{ $input.all().map(i => i.json.summary || '(untitled event)').join(', ') }}, or with Aggregate in place,{{ $json.summary.map(s => s || '(untitled event)').join(', ') }}.
America/New_York — so the offset you actually get depends on where you are and on daylight saving. Fix it per workflow with Workflow settings → Timezone set to your IANA zone, for example Europe/London, or ask an administrator to set the instance's GENERIC_TIMEZONE. The same applies to {{ $now.startOf('day') }}: it uses the workflow's timezone and falls back to the instance's. Confirm the effective zone on every newly created workflow rather than assuming it matches your location.If it still will not work, copy the URL of that run from the Executions page and send it to a colleague or to IT. The heavier machinery — error workflows, retries — is opened up in Part 12.
If a run fails, will anything tell me?
Is running this every day a waste of resources?
Can I run many workflows like this at once? Will they interfere?
Can I send to a direct message instead of a channel?
User rather than Channel, then pick yourself, or someone else, from the User dropdown. The message arrives in the Slackbot DM or in the direct message with that person. For a daily reminder only you need to see, a DM is usually tidier than creating a dedicated channel for it.If I edit a workflow and forget to save, does Active keep running the old version?
Can the Google Calendar node read a calendar a colleague shared with me?
The labels on my screen do not match the ones printed here
Execute step against Test step against Execute Node, Message Text against Text, After and Before against the older Time Min and Time Max. Recognize the shape of the control rather than the exact word, and when your screen and this article disagree, go by your screen and the official documentation.Where to go from here
You have built one. Now find out what was moving through it.
The five-events-became-five-messages surprise in this part is the thing people misunderstand most in n8n. Part 7 takes the item data flow apart properly, then works through every way a workflow can be started — Webhook, service triggers, manual — so you can design your second and third workflow without following anybody's steps.
Open the full guidePart 6 of the Woow n8n Onboarding Guide series on the Apporo blog.
Adapted from the Woow n8n Onboarding Guide, produced by WoowTech and released under CC BY 4.0. This adaptation is published by Apporo under the same licence.
Light · Air · Water · Control · apporo