- n8n v1.121+ (Cron node v1.4 timezone-aware required)
- Notion workspace with internal integration token. Each team database must be shared with the integration
- The Notion database IDs for each team you want to digest (5 in our case)
- Anthropic API key with Claude Sonnet 4.5 access
- Slack workspace, bot with chat:write and a dedicated channel like #daily-pulse
- A Notion "team" property convention — each page tagged with a Team property so we know which bucket it belongs to
Asia/Kolkata to avoid the daylight-saving confusion that bit us in 2025.
{
"parameters": {
"rule": {
"interval": [
{ "field": "cronExpression", "expression": "0 30 8 1-5" }
]
},
"options": {
"timezone": "Asia/Kolkata"
}
},
"id": "cron-0830-ist",
"name": "Daily 8:30 IST",
"type": "n8n-nodes-base.scheduleTrigger",
"typeVersion": 1.4,
"position": [240, 300]
}0 30 8 1-5 is "minute 30 of hour 8, every weekday." We tried 9:00 first; people read better at 8:30 before checking the rest of Slack.
## Step 2 — Notion crawl with filter
For each of the 5 team databases, an HTTP Request node hits Notion's /v1/databases/{id}/query endpoint with a filter:
{
"filter": {
"timestamp": "last_edited_time",
"last_edited_time": {
"on_or_after": "{{ DateTime.now().minus({ hours: 24 }).toISO() }}"
}
},
"page_size": 100,
"sorts": [{ "timestamp": "last_edited_time", "direction": "descending" }]
}GET /v1/blocks/{page_id}/children) to get the actual content, not just titles. We flatten the blocks into a clean text representation in a Code node:
function flattenBlocks(blocks) {
return blocks.map(b => {
const t = b.type;
const rich = b[t]?.rich_text?.map(r => r.plain_text).join('') || '';
if (t === 'heading_1') return '# ' + rich;
if (t === 'heading_2') return '## ' + rich;
if (t === 'heading_3') return '### ' + rich;
if (t === 'bulleted_list_item') return '- ' + rich;
if (t === 'numbered_list_item') return '1. ' + rich;
if (t === 'to_do') return (b.to_do.checked ? '[x] ' : '[ ] ') + rich;
if (t === 'callout') return '> ' + rich;
if (t === 'code') return '\\\\n' + rich + '\n\\\';
return rich;
}).filter(Boolean).join('\n');
}
const pages = $input.all();
const grouped = {};
for (const p of pages) {
const team = p.json.properties?.Team?.select?.name || 'Unassigned';
if (!grouped[team]) grouped[team] = [];
grouped[team].push({
title: p.json.properties?.Name?.title?.[0]?.plain_text || 'Untitled',
url: p.json.url,
last_edited_by: p.json.last_edited_by?.id,
text: flattenBlocks(p.json.children || [])
});
}
return Object.entries(grouped).map(([team, items]) => ({ json: { team, items } }));You are writing a daily digest for the {{ $json.team }} team at Softechinfra. The reader is the rest of the company — leadership and other teams. Voice: direct, no hype, no buzzwords.
Produce exactly this Slack-flavoured markdown:
🟢 Shipped
- bullet (max 4 bullets, each one line max 100 chars)
🟡 In Progress
- bullet (max 4 bullets)
🔴 Blocked / Needs Help
- bullet, prefix with @teamname if action needed (max 3 bullets)
❓ One question for this team
One short question that surfaces a decision someone needs to make today.
Rules:
- Skip empty sections (do not output the header if no items).
- Group similar items into one bullet, do not repeat.
- If no pages had any "blocked" content, write nothing under that section.
- Banned phrases: "leverage", "robust", "synergy", "synergies", "deep dive".
- Max total: 280 words.Team: {{ $json.team }}
Date range: last 24 hours
Pages updated:
{{ $json.items.map(p => '— ' + p.title + ' ([link](' + p.url + '))\n' + p.text.slice(0, 1500)).join('\n\n---\n\n') }}const date = new Date().toLocaleDateString('en-IN', { weekday: 'long', day: 'numeric', month: 'short' });
const summaries = $input.all().map(i => i.json); // each has { team, summary }
const blocks = [
{ type: 'header', text: { type: 'plain_text', text: '☀️ Daily Pulse — ' + date } },
{ type: 'context', elements: [{ type: 'mrkdwn', text: '_Auto-generated from Notion updates in the last 24h. Reply in-thread with corrections._' }] },
{ type: 'divider' }
];
for (const s of summaries) {
blocks.push({ type: 'section', text: { type: 'mrkdwn', text: '' + s.team + '\n' + s.summary } });
blocks.push({ type: 'divider' });
}
blocks.push({ type: 'context', elements: [{ type: 'mrkdwn', text: ' · :notion: ' }] });
return { json: { blocks } }; blocks to #daily-pulse with the bot user identity.
## Sample digest output (real, anonymised)
This is the digest from Monday 21 April 2026, posted at 8:31 IST:
> ☀️ Daily Pulse — Monday, 21 Apr
> _Auto-generated from Notion updates in the last 24h. Reply in-thread with corrections._
>
> ---
>
> Engineering
> 🟢 Shipped
> - n8n receptionist workflow live for Magpie Logistics — 47 nodes, soak test passed
> - Hetzner CX22 → CPX31 migration done, n8n RAM headroom doubled
>
> 🟡 In Progress
> - Tally XML schema refactor for 6.1 compatibility (Anjali leading, ETA Wed)
>
> 🔴 Blocked / Needs Help
> - @design Calendly brief workflow needs final logo SVG for Slack header — blocker since Fri
>
> ❓ One question for this team
> Do we lock the new "production checklist" template before Wed's release, or after the Magpie post-mortem?
>
> ---
>
> Design
> 🟢 Shipped
> - 3 hero illustrations for the blog refresh, exported in WebP + AVIF
>
> 🟡 In Progress
> - Sales deck v3 — 60% done, waiting on case-study photo from Karthik
>
> ❓ One question for this team
> Are we matching the new brand teal across the n8n workflow screenshots, or keeping product-default?
>
> ---
>
> Sales
> 🟢 Shipped
> - 4 demos done last week (3 inbound, 1 referral). 2 advanced to Tier-A
> - HubSpot ICP rubric v2.1 deployed
>
> 🔴 Blocked / Needs Help
> - @engineering need a 5-minute demo recording of the Calendly brief workflow for tomorrow's call
>
> ---
>
> Ops
> 🟢 Shipped
> - April invoices closed, ITR-3 prep started
> - DPDP Act addendum signed with Anthropic
>
> ---
>
> _View execution · :notion: Open team space_
That digest is 280 words. Every person in #daily-pulse reads it in under 2 minutes. The cross-team @-mentions ("@design needs SVG") actually get replied in-thread, which is half the magic.
## Cost comparison vs the standup
For a 12-person team meeting daily 9:00-9:22 IST:
The standup cost is the loaded hour-rate × 22 minutes × 12 people × 22 working days. Even if you discount it 50% for "people would be slacking off anyway," the digest still wins by an order of magnitude.
## When NOT to do this
You have a remote-first team that has never met. Standups have a social bonding function the digest cannot replace. Keep a Monday + Thursday human call; replace Tue/Wed/Fri with digests.
Your team does not document in Notion. If your work lives in Linear tickets, GitHub PRs, Figma, and Slack DMs scattered across surfaces, the digest is incomplete by design. Build the documentation discipline first (or replace the Notion crawl with multi-source — Linear + GitHub + Figma — which is 3× the nodes and probably 60+ on n8n).
Your team is under 5 people. A 5-person team can run a useful standup in 4 minutes. Below that headcount the digest infra is overkill.
last_edited_by as an ID. Resolve it to a name + Slack handle via Slack's users.list, then feed the mapping to Claude in the system prompt. Otherwise it hallucinates "Priya edited this" when Anjali actually did.
Mistake 3 — Trying to summarise too far back. 24 hours is the right window. 48-72 hours creates a wall of text and loses the "what is fresh today" signal.
Mistake 4 — Skipping the empty-section rule. Without "skip empty sections" in the prompt, Claude writes filler like "🔴 Blocked — Nothing blocked today!" which becomes noise. Strip it.
Mistake 5 — Running it on weekends. Sunday-night digests are demoralising. Cron 1-5 (Mon-Fri) only.
## FAQ
### Can I run this from Asana / Linear / Jira instead of Notion?
Yes. Each has a REST API. Linear's GraphQL is the cleanest — one query returns all issues updated in last 24h with comments and status changes. Workflow logic stays identical; only step 2 changes.
### How do I handle confidential pages?
Two options. Most accurate: tag confidential pages with a Notion property include_in_digest = false and filter them out. Lazier: scope your Notion integration to only the databases you want digested; private pages outside those scopes are invisible to the crawl anyway.
### Can the digest include action item assignments?
Yes. Claude can output an explicit actions array, and a downstream branch creates Linear tasks or Notion to-dos for each. We have not added this in production yet — too easy to spam your tracker.
### What about Hindi-only or bilingual teams?
Add a language parameter to the Claude prompt: "Output in {{ team.preferred_language }}". For our team it is English; for one client team we run it in Hinglish (their request, our team prefers it).
### Does it work with Notion's new "AI blocks"?
Yes — AI block content is part of the regular block tree and gets flattened normally. We get cleaner summaries than from the native Notion AI assistant because Sonnet 4.5 has more context and the rules are tighter.
### Can leadership get a different version of the digest?
Yes. Branch after the per-team summaries: send the full digest to #daily-pulse, send a leadership-only top-line ("3 shipments, 1 blocker, 2 questions") to #leadership-only. Adds 2 nodes and ₹0.50/day.
### What if the cron run fails?
Two safeguards. n8n's "Error Workflow" feature pings a separate error workflow that DMs the ops lead. We also send a heartbeat ping to Better Uptime at the end of the digest workflow — no ping in 10 minutes after expected fire = page.
Want this digest built for your team?
We ship the full 14-node workflow above, wired to your Notion (or Linear / Asana / Jira), your Slack, and tuned to your team's tone in a 30-min workshop with your team lead. Typical cost: ₹35,000–₹55,000. Suitable if you have 8+ people doing daily standup and the meeting is no longer earning the time. No slides — bring a week of standup notes and we will show you what the digest would have looked like.
Book a 20-min Call
