Daily Digest
Sends each accountable person a Slack DM with the KPI readings due today and logs each attempt in Communication.
- Platform
- n8n · Open in n8n
- Status
- Active
- Workflow ID
V8YNeLy4FbFiPvsM- Nodes
- 36
- n8n version
cf9165c2-706b-4bfb-8658-c62f1bb4dac5- Last saved in n8n
- 2026-11-06T19:41:05.769Z
Communication — Daily Digest asks each accountable person, in a Slack direct message, for the KPI readings due that day. Every morning it finds the Active Recurrence Configurations whose cadence is due, removes the readings already recorded, groups the rest by accountable membership, finds each person’s Slack account in Tooling and Access, sends one checkbox digest per membership and logs each attempt as a Message in Communication. The answers come back through the separate workflow Communication — Reading Reply, which handles the checkboxes.
The KPIs & OKRs maintainer owns the schedules and the due logic; the Communication maintainer reviews the Message rows; the Tooling and Access maintainer keeps people bound to their Slack accounts.
Flow
Generated from the n8n version in the box above: every box is a node and every arrow is a connection in n8n. Pink pills are triggers, yellow hexagons are If and Switch nodes with their outputs on the arrows, blue cylinders read (Airtable, Data Table, HTTP GET), green cylinders write, purple boxes call another workflow, and dashed arrows are error outputs.
Mermaid source
flowchart TD
subgraph g0["Start and schema"]
n6(["Every day 09:00"]):::trigger
n33(["Webhook"]):::trigger
n34(["When clicking ‘Execute workflow’"]):::trigger
n4["Config"]:::logic
n0["Airtable Schema"]:::logic
n20[("Meta (Operation)<br/><small>getSchema KPIs base</small>")]:::read
n19[("Meta (Communication)<br/><small>getSchema Communication base</small>")]:::read
n21[("Meta (Tooling)<br/><small>getSchema Tooling base</small>")]:::read
n27["Resolve Schema"]:::logic
n30{{"Schema OK"}}:::decision
end
subgraph g1["Due readings"]
n17[("List Due Schedules<br/><small>search KPIs Recurrence Configuration</small>")]:::read
n10[("Get Cadences<br/><small>search KPIs Cadence</small>")]:::read
n3["Compute Due Items"]:::logic
n16{{"Is OK"}}:::decision
n1["Build Answered Filter"]:::logic
n7[("Find Answered<br/><small>search KPIs Observation</small>")]:::read
n14["Group by Recipient"]:::logic
n15{{"Has Digests"}}:::decision
n18["Log Nothing To Do"]:::logic
end
subgraph g2["Slack recipient"]
n13[("Get Slack Accounts<br/><small>search Tooling Tool Account</small>")]:::read
n11[("Get Identity Bindings<br/><small>search Tooling Identity Binding</small>")]:::read
n12[("Get Persons (Tooling)<br/><small>search Tooling Person</small>")]:::read
n29["Resolve Slack Recipient"]:::logic
n25{{"Recipient Resolved?"}}:::decision
n9["Get Cached Notification Bot<br/><small>Slack — Workspace Token Cache</small>"]:::subflow
n28["Resolve Slack Account"]:::logic
end
subgraph g3["Send and log"]
n2["Build Slack Blocks"]:::logic
n32{{"User Found"}}:::decision
n22{{"Not Dry Run"}}:::decision
n23[("Open DM<br/><small>POST Slack conversations.open</small>")]:::write
n24[("Post Digest<br/><small>POST Slack chat.postMessage</small>")]:::write
n8[("Find Membership (Communication)<br/><small>search Communication Membership</small>")]:::read
n31["Shape Message Row"]:::logic
n5[("Create Message<br/><small>create Communication Message</small>")]:::write
n26["Report"]:::logic
end
n6 --> n4
n4 --> n0
n0 --> n20
n20 --> n19
n19 --> n21
n21 --> n27
n27 --> n30
n17 --> n10
n10 --> n3
n3 --> n16
n1 --> n7
n7 --> n14
n14 --> n15
n13 --> n11
n11 --> n12
n12 --> n29
n2 --> n32
n23 --> n24
n24 --> n8
n8 --> n31
n31 --> n5
n5 --> n26
n30 -->|true| n17
n30 -->|false| n18
n16 -->|true| n1
n16 -->|false| n18
n15 -->|true| n13
n15 -->|false| n18
n32 -->|true| n22
n32 -->|false| n8
n22 -->|true| n23
n22 -->|false| n8
n34 --> n4
n33 --> n4
n29 --> n25
n25 -->|true| n9
n25 -->|false| n28
n9 --> n28
n28 --> n2
classDef trigger fill:#fde7f3,stroke:#d6409f,color:#111
classDef decision fill:#fff7e0,stroke:#c99a00,color:#111
classDef read fill:#e8f1ff,stroke:#3b82f6,color:#111
classDef write fill:#e7f8ee,stroke:#16a34a,color:#111
classDef subflow fill:#f3e8ff,stroke:#9333ea,color:#111
classDef wait fill:#f1f1f1,stroke:#888,color:#111
classDef logic fill:#ffffff,stroke:#999,color:#111Systems it touches
| System | Table | Reads | Writes |
|---|---|---|---|
| KPIs & OKRs | Recurrence Configuration | List Due Schedules | Nothing |
| KPIs & OKRs | Cadence (mirror) | Get Cadences | Nothing |
| KPIs & OKRs | Observation | Find Answered | Nothing |
| Tooling and Access | Tool Account | Get Slack Accounts | Nothing |
| Tooling and Access | Identity Binding | Get Identity Bindings | Nothing |
| Tooling and Access | Person (mirror) | Get Persons (Tooling) | Nothing |
| Communication | Membership (mirror) | Find Membership (Communication) | Nothing |
| Communication | Message | Nothing | Create Message |
Meta (Operation), Meta (Communication) and Meta (Tooling) also read the table and field list of the three bases. The Message row links the Channel Slack DM and the Notification Rule Reading due today by record ID, and the tag kpis-okrs-daily-digest-n8n.
Outside Airtable, Get Cached Notification Bot calls the workflow Slack — Workspace Token Cache for the bot token of each recipient’s workspace, and Open DM and Post Digest call the Slack Web API.
The Airtable nodes are native Airtable nodes with the credential Airtable PAT (Fully Scoped). They read and write by column name, but every name comes from Resolve Schema, which translates the field IDs kept in Airtable Schema into the current names on each run. Renaming a column does not break the flow; deleting one stops it at Schema OK.
See Daily Digest for the reading side in KPIs & OKRs and Daily Digest delivery for the Message rows in Communication.
Triggers and schedule
Three triggers start the same run at Config:
| Trigger | When |
|---|---|
| Every day 09:00 | Cron 0 9 * * * in the n8n instance’s time zone; the workflow sets none. Message rows from scheduled runs are created at about 12:00 UTC |
| Webhook | A GET on a fixed path, with no authentication. It answers at once. ?runDate=YYYY-MM-DD runs the digest for that date |
| When clicking ‘Execute workflow’ | Manual run from the n8n editor |
Config sets the run’s constants:
| Setting | Value |
|---|---|
| timezone | America/Chicago, used for the weekday, the period key and the date label |
| dryRun | false |
| dmChannelRecordId | The Channel Slack DM |
| ruleRecordId | The Notification Rule Reading due today |
| biweeklyAnchorIsoWeekParity | 0 (biweekly cadences fire on even ISO weeks) |
| maxItemsPerDigest | 25 |
| workspaceByOrganization | Slack workspace per organization: Snack Prompt, Corp; Sons Design , LLC; Sons Of Liberty Coffee, LLC; Score Milk (Top Score), LLC; Nexa Roofing, LLC; REFS Labs, Inc. |
| defaultWorkspaceTeamId | The Snack Prompt, Corp workspace |
| runDate | runDate from the webhook query, otherwise today’s date on the n8n clock |
Schema check
Airtable Schema holds the base, table and field IDs the flow uses in KPIs & OKRs, Communication and Tooling and Access. Meta (Operation), Meta (Communication) and Meta (Tooling) read each base’s schema, and Resolve Schema maps every ID to its current name. When a table or field is missing, Resolve Schema returns “Schema drift — not found in Airtable: …” and Schema OK sends the run to Log Nothing To Do, which ends it.
Due readings
-
Schedules. List Due Schedules reads the Recurrence Configurations with Lifecycle Status* Active and a Cadence (Key). Get Cadences reads the whole Cadence (mirror) catalog.
-
Due today. Compute Due Items skips a schedule whose cadence key is not in the catalog, whose cadence is not Active, or that has no Accountable Membership*. It keeps the ones due on the run date:
Cadence key Due when daily Every day daily-working-days The weekday is in the cadence’s By Day weekly Monday biweekly Monday of an even ISO week monthly Day 1 quarterly Day 1 of January, April, July and October semiannual Day 1 of January and July annual January 1 on-demand, or any other key Never Each due item gets a period key from the cadence’s Period Key Format and an occurrence key
<Recurrence Configuration record ID>:<period key>. Its recipients are the schedule’s accountable memberships with their name, e-mail, Membership # and organization. The name comes from Accountable Display Name; when that is a bare record ID, it is taken from Accountable (Membership #), which reads “number - name - organization”. -
Already answered. Build Answered Filter builds Occurrence Key formulas in chunks of 40, and Find Answered reads those Observations. Group by Recipient drops a membership’s item when an Observation has the same Occurrence Key, lists that membership in Observation About (Membership), and has Collection Status empty or Recorded.
-
One digest per membership. Group by Recipient groups what is left by accountable membership, keeps the first 25 items and counts the rest as overflow. A recipient without a name is set aside as unreachable. Has Digests ends the run at Log Nothing To Do when nothing is left.
Slack recipient
Get Slack Accounts reads the Tool Accounts whose Kind is human (Name, External Identifier, Kind and Workspace (External Identifier) only; never Access Token). Get Identity Bindings and Get Persons (Tooling) read both tables whole. For each digest, Resolve Slack Recipient:
- finds the Person (mirror) whose Full name* equals the recipient’s name, ignoring case and extra spaces;
- keeps the Identity Bindings of that person whose Valid Through is empty or in the future, and their human Tool Accounts;
- picks the account in the workspace mapped to the membership’s organization, else the only account, else the account in the default workspace;
- checks that the workspace ID starts with T and the Slack user ID with U.
Each miss becomes an error on the item (“Person not found in Tooling mirror: …”, “No Slack account bound to …”, “No Slack account for … in workspace …”, INVALID_WORKSPACE, INVALID_SLACK_USER). Recipient Resolved? sends resolved items to Get Cached Notification Bot, which runs Slack — Workspace Token Cache with the workspace ID, cacheOnly false, forceRefresh false and refreshAll false, and waits for it. Resolve Slack Account accepts the answer only when it is ok, is for the same workspace, carries a bot token and bot user ID, and has not expired; otherwise the item gets TOKEN_CACHE_UNAVAILABLE, TOKEN_WORKSPACE_MISMATCH or TOKEN_CACHE_INVALID.
Send and log
Build Slack Blocks writes the digest: a header “<date> · 0 of N responses recorded”, then sections of up to 10 checkboxes with the action reading_reply, one per reading (name, description, goal), each with the value <occurrence key>|<membership record ID>|<Slack user ID>. Overflow adds the line “N additional readings are not shown in this message.”
| Item state | Path |
|---|---|
| Error from routing or the token cache | User Found false → Find Membership (Communication) → Message Failed |
| Dry run | Not Dry Run false → Find Membership (Communication) → Message Sent with an External Message Id that starts with dry-run- |
| Ready | Open DM (conversations.open) → Post Digest (chat.postMessage with the blocks and the metadata event reading_digest) → Find Membership (Communication) → Message Sent or Failed from Slack’s answer |
Open DM and Post Digest use the n8n credential Systems Slack Automation - Dynamic, which takes the bot token resolved for the item. Find Membership (Communication) looks up Membership (mirror) by Membership # (exact, or starting with “<number> - “). Shape Message Row builds the row and Create Message saves it. Report counts digests, sent and failed for the execution.
Fields written
Communication Message (Create Message)
| Field | Value |
|---|---|
| Subject | “Daily digest · <person> · <run date>” |
| Body | “<date>: N readings awaiting a response.” |
| Status | Sent when Slack (or the dry run) answered ok, Failed otherwise |
| Sent At | Time of saving, when Sent |
| External Message Id | Slack message timestamp, or dry-run-…, when Sent |
| Failure Reason | The error, when Failed |
| Channel | Slack DM |
| Rule | Reading due today |
| Recipients (Membership) | The membership found by Find Membership (Communication), when found |
| About Keys | One occurrence key per reading in the digest, one per line |
| Tags | kpis-okrs-daily-digest-n8n |
The flow writes nothing else: no Observation, no Recurrence Configuration and nothing in Tooling and Access.
Nodes
The workflow has 36 nodes: 35 that run and one sticky note. For an If node, output 0 is true and output 1 is false.
| Node | Type | What it does |
|---|---|---|
| Every day 09:00 | scheduleTrigger | Starts the run at 09:00 instance time |
| Webhook | webhook | Starts the run from a GET; optional runDate |
| When clicking ‘Execute workflow’ | manualTrigger | Starts the run from the editor |
| Config | set | Run constants |
| Airtable Schema | code | Base, table and field IDs used by the flow |
| Meta (Operation) | airtable | Reads the KPIs & OKRs schema |
| Meta (Communication) | airtable | Reads the Communication schema |
| Meta (Tooling) | airtable | Reads the Tooling and Access schema |
| Resolve Schema | code | Maps field IDs to current names; reports drift |
| Schema OK | if | Schema resolved or not |
| List Due Schedules | airtable | Active Recurrence Configurations with a cadence key |
| Get Cadences | airtable | The Cadence (mirror) catalog |
| Compute Due Items | code | Due schedules, period keys, occurrence keys and recipients |
| Is OK | if | Schedules found or not |
| Build Answered Filter | code | Occurrence Key formulas in chunks of 40 |
| Find Answered | airtable | Observations for those occurrence keys |
| Group by Recipient | code | Drops answered items; one digest per membership, up to 25 items |
| Has Digests | if | Anything left to send or not |
| Log Nothing To Do | code | Ends a run with nothing to send |
| Get Slack Accounts | airtable | Human Tool Accounts |
| Get Identity Bindings | airtable | All Identity Bindings |
| Get Persons (Tooling) | airtable | All Person (mirror) rows |
| Resolve Slack Recipient | code | Person → binding → Slack account and workspace |
| Recipient Resolved? | if | Routing succeeded or not |
| Get Cached Notification Bot | executeWorkflow | Runs Slack — Workspace Token Cache for the workspace |
| Resolve Slack Account | code | Validates the cached bot token |
| Build Slack Blocks | code | Checkbox digest blocks and summary text |
| User Found | if | Item without error or not |
| Not Dry Run | if | Real send or dry run |
| Open DM | httpRequest | Opens the direct message in Slack |
| Post Digest | httpRequest | Posts the digest |
| Find Membership (Communication) | airtable | Communication membership by Membership # |
| Shape Message Row | code | Builds the Message row |
| Create Message | airtable | Saves the Message row |
| Report | code | Run summary |
| Sticky Note | stickyNote | Notes on the canvas; does not run |
Connections
| From | Output | To |
|---|---|---|
| Every day 09:00 | 0 | Config |
| Config | 0 | Airtable Schema |
| Airtable Schema | 0 | Meta (Operation) |
| Meta (Operation) | 0 | Meta (Communication) |
| Meta (Communication) | 0 | Meta (Tooling) |
| Meta (Tooling) | 0 | Resolve Schema |
| Resolve Schema | 0 | Schema OK |
| List Due Schedules | 0 | Get Cadences |
| Get Cadences | 0 | Compute Due Items |
| Compute Due Items | 0 | Is OK |
| Build Answered Filter | 0 | Find Answered |
| Find Answered | 0 | Group by Recipient |
| Group by Recipient | 0 | Has Digests |
| Get Slack Accounts | 0 | Get Identity Bindings |
| Get Identity Bindings | 0 | Get Persons (Tooling) |
| Get Persons (Tooling) | 0 | Resolve Slack Recipient |
| Build Slack Blocks | 0 | User Found |
| Open DM | 0 | Post Digest |
| Post Digest | 0 | Find Membership (Communication) |
| Find Membership (Communication) | 0 | Shape Message Row |
| Shape Message Row | 0 | Create Message |
| Create Message | 0 | Report |
| Schema OK | 0 | List Due Schedules |
| Schema OK | 1 | Log Nothing To Do |
| Is OK | 0 | Build Answered Filter |
| Is OK | 1 | Log Nothing To Do |
| Has Digests | 0 | Get Slack Accounts |
| Has Digests | 1 | Log Nothing To Do |
| User Found | 0 | Not Dry Run |
| User Found | 1 | Find Membership (Communication) |
| Not Dry Run | 0 | Open DM |
| Not Dry Run | 1 | Find Membership (Communication) |
| When clicking ‘Execute workflow’ | 0 | Config |
| Webhook | 0 | Config |
| Resolve Slack Recipient | 0 | Recipient Resolved? |
| Recipient Resolved? | 0 | Get Cached Notification Bot |
| Recipient Resolved? | 1 | Resolve Slack Account |
| Get Cached Notification Bot | 0 | Resolve Slack Account |
| Resolve Slack Account | 0 | Build Slack Blocks |
Settings and error handling
| Setting | Value |
|---|---|
| Execution order | v1 |
| Error workflow | None |
| Retry on fail | Off on every node |
| On error | Continue on Get Cached Notification Bot, Open DM, Post Digest, Find Membership (Communication) and Create Message; stop the workflow on the others |
| Save successful executions | No |
| Save failed executions | No |
| Save manual executions | No |
| Save execution progress | No |
| Caller policy | Workflows from the same owner |
| Available in MCP | Yes |
Since 2026-11-06 n8n saves every execution of this workflow, successful, failed and manual, with progress. Before that date nothing was saved, so for older runs the Message rows are the only record.
Known gaps
These come from the node configuration and from the Message rows the flow has written.
| Gap | Effect |
|---|---|
| The Webhook has no authentication | Anyone with its address starts a full real run for any runDate |
| The schedule runs at 09:00 instance time, while Config and the Notification Rule say America/Chicago | Runs land at about 12:00 UTC; the weekday and period key use the Chicago date, the run date uses the instance clock |
| On a day with nothing due, Build Answered Filter returns an error item that nothing checks | Find Answered runs with an empty formula and reads the whole Observation table; the run then ends saying everything is already answered |
| Open DM continues on error | Post Digest still runs, and the Message keeps invalid_arguments instead of the original Slack error such as user_not_found |
| Create Message continues on error | A digest can reach Slack with no Message row; since 2026-11-06 the n8n execution still shows it |
| Unreachable recipients (no name) are only counted in Report | They get no Message row; Report is visible only in the saved execution |
| A record ID can come through as the person’s name | The digest is logged Failed with “Person not found in Tooling mirror: rec…”; three such rows were written on 2026-11-03 |
| The person is matched by full name only | Two people with the same name in Person (mirror) resolve to the first one; Valid From of a binding is ignored |
| Resolve Schema also requires Tool Account Access Token, which the flow never reads | Deleting that column stops every digest |
| No delivery idempotency | A second run on the same day sends the unanswered items again; a dry run still writes Message rows |
| The sticky note says the test restriction is on | Config has no testMembershipId or testScheduleId, so it is off; Build Slack Blocks still hard-codes one Slack user for it |
| A person with two memberships | Gets two digests |
| More than 25 items for one membership | The rest are counted, not sent |
Recover a partial write
Look at the Message rows of the run before you start another one: a rerun sends again to everyone with something unanswered, and there is no rerun for a single recipient.
| What happened | What to do |
|---|---|
| Message Failed with a routing error | Fix the Person (mirror), Identity Binding or Tool Account in Tooling and Access; the next scheduled run tries again |
| Message Failed with a Slack or token error | Check the workspace’s bot in Slack — Workspace Token Cache; the next scheduled run tries again |
| Digest in Slack but no Message row | Add the Message by hand from the Slack DM if the delivery must be counted |
| Run stopped at the schema check | Restore or remap the missing column, then wait for the next run or start one by hand knowing it sends to everyone |
Keep this page in sync
The box at the top shows the n8n version this page describes. Open the workflow in n8n: when its last save is newer than the date in the box, this page is out of date until the next review. Every node and every connection of that version is listed in Nodes and Connections.