BDS0.1

Daily Digest

Sends each accountable person a Slack DM with the KPI readings due today and logs each attempt in Communication.

Reviewed 2026-11-06Markdown
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.

Send and log

Slack recipient

Due readings

Start and schema

true

false

true

false

true

false

true

false

true

false

true

false

Every day 09:00

Webhook

When clicking ‘Execute workflow’

Config

Airtable Schema

Meta (Operation)
getSchema KPIs base

Meta (Communication)
getSchema Communication base

Meta (Tooling)
getSchema Tooling base

Resolve Schema

Schema OK

List Due Schedules
search KPIs Recurrence Configuration

Get Cadences
search KPIs Cadence

Compute Due Items

Is OK

Build Answered Filter

Find Answered
search KPIs Observation

Group by Recipient

Has Digests

Log Nothing To Do

Get Slack Accounts
search Tooling Tool Account

Get Identity Bindings
search Tooling Identity Binding

Get Persons (Tooling)
search Tooling Person

Resolve Slack Recipient

Recipient Resolved?

Get Cached Notification Bot
Slack — Workspace Token Cache

Resolve Slack Account

Build Slack Blocks

User Found

Not Dry Run

Open DM
POST Slack conversations.open

Post Digest
POST Slack chat.postMessage

Find Membership (Communication)
search Communication Membership

Shape Message Row

Create Message
create Communication Message

Report

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:#111

Systems 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

  1. Schedules. List Due Schedules reads the Recurrence Configurations with Lifecycle Status* Active and a Cadence (Key). Get Cadences reads the whole Cadence (mirror) catalog.

  2. 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”.

  3. 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.

  4. 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:

  1. finds the Person (mirror) whose Full name* equals the recipient’s name, ignoring case and extra spaces;
  2. keeps the Identity Bindings of that person whose Valid Through is empty or in the future, and their human Tool Accounts;
  3. picks the account in the workspace mapped to the membership’s organization, else the only account, else the account in the default workspace;
  4. 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.