

How to Post Todoist Standups to Slack with n8n
A scheduled n8n workflow fetches today's Todoist tasks for each team member each morning and posts a formatted summary to a designated Slack channel, replacing manual standup reporting.
Steps and UI details are based on platform versions at time of writing — check each platform for the latest interface.
Best for
Engineering or product teams of 3–20 people who track daily work in Todoist and run async or semi-sync standups in Slack.
Not ideal for
Teams that need two-way sync or want people to update their standup status directly inside Slack — use a dedicated standup bot like Geekbot for that.
Sync type
scheduledUse case type
reportingReal-World Example
A 10-person product team tracks sprint tasks in Todoist with due dates set to each day. Before this workflow, the team lead spent 5–10 minutes each morning pinging people for updates and copying task names into Slack manually. Now at 9:00 AM the workflow pulls every task due today per assignee, builds a grouped summary, and posts it to #standup — the team lead opens Slack and the report is already there.
What Will This Cost?
Drag the slider to your expected monthly volume.
Each platform counts differently — Zapier: 1 task per trigger. Make: 1 operation per module per record. n8n: 1 execution per run.





Prices shown for annual billing. Based on published pricing as of April 2026.
Estimated ROI
1000
min saved/mo
$583
labor value/mo
Free
no platform cost
Based on ~2 min manual effort per operation at $35/hr fully loaded labor cost.
Implementation
Import this workflow directly into n8n
Copy the pre-built n8n blueprint and paste it straight into n8n. All modules, filters, and field mappings are already configured — you just need to connect your accounts.
Before You Start
Make sure you have everything ready.
Field Mapping
Map these fields between your apps.
| Field | API Name | |
|---|---|---|
| Required | ||
| Task Content | content | |
| Due Date | due.date | |
| Slack Channel ID | ||
| Slack Message Text | ||
5 optional fields▸ show
| Assignee ID | assignee_id |
| Project ID | project_id |
| Priority | priority |
| Task URL | url |
| Task Labels | labels |
Step-by-Step Setup
n8n Dashboard > Workflows > + New Workflow
Create a new n8n workflow
Open your n8n instance and click the '+ New Workflow' button in the top right of the Workflows dashboard. Give it a descriptive name like 'Daily Todoist Standup to Slack'. You'll land on the canvas with an empty workflow and a prompt to add your first node. This is where the scheduled trigger goes.
- 1Click '+ New Workflow' in the top right corner
- 2Click the workflow title at the top and rename it to 'Daily Todoist Standup to Slack'
- 3Click '+ Add first step' in the center of the canvas
Canvas > Node Picker > Schedule Trigger
Add a Schedule trigger node
Search for 'Schedule Trigger' in the node picker and select it. In the node configuration panel, set the trigger interval to 'Cron Expression' for precise control. Enter the cron expression for your standup time — for 9:00 AM Monday through Friday, use '0 9 * * 1-5'. Click 'Save' on the node.
- 1Type 'Schedule' in the node search box
- 2Click 'Schedule Trigger' from the results
- 3Set 'Trigger Interval' dropdown to 'Custom (Cron)'
- 4Enter '0 9 * * 1-5' in the Cron Expression field
- 5Click 'Save' to close the node panel
Canvas > + Node > Todoist > Task > Get Many
Connect Todoist and fetch today's tasks
Add a Todoist node after the Schedule Trigger. In the node picker, search 'Todoist' and select the Todoist node. Set the operation to 'Get Many' under the Task resource. You need to create a Todoist credential first — click 'Create New Credential', which opens an OAuth2 flow. Authorize n8n in the Todoist browser window that pops up. Once connected, set the Filter to 'Today' to pull only tasks due today.
- 1Click the '+' connector on the Schedule Trigger node
- 2Search 'Todoist' in the node picker and select it
- 3Set Resource to 'Task' and Operation to 'Get Many'
- 4Click 'Credential for Todoist API' > 'Create New Credential'
- 5Complete the Todoist OAuth2 authorization in the popup window
- 6Under Filters, set 'Filter' to 'Today'
Canvas > + Node > Code
Add a Code node to group tasks by assignee
The Todoist node returns a flat array of tasks. You need to group them by assignee so each person's tasks appear together in the Slack message. Add a Code node after Todoist. Paste the JavaScript from the Pro Tip section below into the code editor. This node outputs one item per team member with their tasks bundled, which the Slack node will iterate over.
- 1Click '+' after the Todoist node
- 2Search 'Code' in the node picker and select it
- 3Set 'Mode' to 'Run Once for All Items'
- 4Paste the grouping script into the code editor
- 5Click 'Test Step' to verify the output structure
Paste this into the first Code node (set to 'Run Once for All Items'). It pulls the collaborators list from the previous HTTP Request node, builds an ID-to-name map, groups today's tasks by assignee, and returns one item per person with a pre-formatted Slack message string including priority emoji and clickable task links. The second part of the script (the message formatter) replaces the need for a separate formatting Code node.
JavaScript — Code Node// Node: Code — 'Group and Format Standup Messages'▸ Show code
// Node: Code — 'Group and Format Standup Messages' // Mode: Run Once for All Items // Expects: items from Todoist node, collaborators from HTTP Request node
... expand to see full code
// Node: Code — 'Group and Format Standup Messages'
// Mode: Run Once for All Items
// Expects: items from Todoist node, collaborators from HTTP Request node
const tasks = $('Todoist').all().map(item => item.json);
const collaborators = $('HTTP Request').all().map(item => item.json);
// Build ID → display name lookup
const nameMap = {};
for (const person of collaborators) {
nameMap[String(person.id)] = person.name;
}
// Priority emoji map (Todoist: 4=urgent, 1=normal)
const priorityEmoji = { '4': '🔴', '3': '🟠', '2': '🟡', '1': '' };
// Group tasks by assignee_id
const grouped = {};
for (const task of tasks) {
const assigneeKey = task.assignee_id ? String(task.assignee_id) : 'unassigned';
if (!grouped[assigneeKey]) {
grouped[assigneeKey] = [];
}
grouped[assigneeKey].push(task);
}
// Build one output item per assignee
const output = [];
for (const [assigneeId, assigneeTasks] of Object.entries(grouped)) {
if (assigneeId === 'unassigned') continue; // skip unassigned tasks
const displayName = nameMap[assigneeId] || `User ${assigneeId}`;
// Sort by priority descending (4 first)
const sorted = assigneeTasks.sort((a, b) => (b.priority || 1) - (a.priority || 1));
// Format each task line
const lines = sorted.map(task => {
const emoji = priorityEmoji[String(task.priority)] || '';
const link = task.url ? ` (<${task.url}|view>)` : '';
const labels = task.labels && task.labels.length > 0
? ` [${task.labels.join(', ')}]`
: '';
return `• ${emoji} ${task.content}${labels}${link}`.trim();
});
const message = `*${displayName}*\n${lines.join('\n')}`;
output.push({
json: {
assignee_id: assigneeId,
assignee_name: displayName,
task_count: assigneeTasks.length,
message
}
});
}
// Sort output alphabetically by name for consistent Slack ordering
output.sort((a, b) => a.json.assignee_name.localeCompare(b.json.assignee_name));
return output;Canvas > + Node > HTTP Request
Resolve assignee IDs to display names
If your team uses assignee_id in Todoist, add an HTTP Request node before the Code node to fetch project collaborators. Call GET https://api.todoist.com/rest/v2/projects/{project_id}/collaborators with your Todoist API token in the Authorization header. This returns an array of objects with id and name. Pass this data into the Code node so you can build the ID-to-name lookup map there.
- 1Click '+' between the Todoist node and the Code node
- 2Search 'HTTP Request' and select it
- 3Set Method to 'GET'
- 4Set URL to 'https://api.todoist.com/rest/v2/projects/YOUR_PROJECT_ID/collaborators'
- 5Under Authentication, choose 'Header Auth' and add 'Authorization: Bearer YOUR_TODOIST_TOKEN'
- 6Click 'Test Step' to confirm you receive a list of collaborators
Paste this into the first Code node (set to 'Run Once for All Items'). It pulls the collaborators list from the previous HTTP Request node, builds an ID-to-name map, groups today's tasks by assignee, and returns one item per person with a pre-formatted Slack message string including priority emoji and clickable task links. The second part of the script (the message formatter) replaces the need for a separate formatting Code node.
JavaScript — Code Node// Node: Code — 'Group and Format Standup Messages'▸ Show code
// Node: Code — 'Group and Format Standup Messages' // Mode: Run Once for All Items // Expects: items from Todoist node, collaborators from HTTP Request node
... expand to see full code
// Node: Code — 'Group and Format Standup Messages'
// Mode: Run Once for All Items
// Expects: items from Todoist node, collaborators from HTTP Request node
const tasks = $('Todoist').all().map(item => item.json);
const collaborators = $('HTTP Request').all().map(item => item.json);
// Build ID → display name lookup
const nameMap = {};
for (const person of collaborators) {
nameMap[String(person.id)] = person.name;
}
// Priority emoji map (Todoist: 4=urgent, 1=normal)
const priorityEmoji = { '4': '🔴', '3': '🟠', '2': '🟡', '1': '' };
// Group tasks by assignee_id
const grouped = {};
for (const task of tasks) {
const assigneeKey = task.assignee_id ? String(task.assignee_id) : 'unassigned';
if (!grouped[assigneeKey]) {
grouped[assigneeKey] = [];
}
grouped[assigneeKey].push(task);
}
// Build one output item per assignee
const output = [];
for (const [assigneeId, assigneeTasks] of Object.entries(grouped)) {
if (assigneeId === 'unassigned') continue; // skip unassigned tasks
const displayName = nameMap[assigneeId] || `User ${assigneeId}`;
// Sort by priority descending (4 first)
const sorted = assigneeTasks.sort((a, b) => (b.priority || 1) - (a.priority || 1));
// Format each task line
const lines = sorted.map(task => {
const emoji = priorityEmoji[String(task.priority)] || '';
const link = task.url ? ` (<${task.url}|view>)` : '';
const labels = task.labels && task.labels.length > 0
? ` [${task.labels.join(', ')}]`
: '';
return `• ${emoji} ${task.content}${labels}${link}`.trim();
});
const message = `*${displayName}*\n${lines.join('\n')}`;
output.push({
json: {
assignee_id: assigneeId,
assignee_name: displayName,
task_count: assigneeTasks.length,
message
}
});
}
// Sort output alphabetically by name for consistent Slack ordering
output.sort((a, b) => a.json.assignee_name.localeCompare(b.json.assignee_name));
return output;Canvas > + Node > Code (second instance)
Format the Slack message with a Function node
After the Code node that groups tasks, add another Code node to build the final Slack Block Kit message payload. This step takes each grouped assignee item and converts it into a Slack-formatted string with bold names and bulleted task lists. Run this node in 'Run Once for Each Item' mode so it processes one assignee at a time and outputs one Slack message per person.
- 1Click '+' after the grouping Code node
- 2Add another Code node
- 3Set Mode to 'Run Once for Each Item'
- 4Write the message formatting logic (see Pro Tip code)
- 5Click 'Test Step' and inspect one output item to confirm the message string looks correct
Canvas > + Node > Slack > Message > Send
Add a Slack node to post each message
Add a Slack node after the formatting Code node. Set the resource to 'Message' and operation to 'Send'. Create a Slack credential using OAuth2 — click 'Create New Credential' and authorize n8n inside your Slack workspace. Set the Channel field to your standup channel ID (not the display name — use the channel ID starting with 'C'). Map the 'Text' field to the 'message' output from the previous Code node using the expression editor.
- 1Click '+' after the second Code node
- 2Search 'Slack' and select it
- 3Set Resource to 'Message', Operation to 'Send'
- 4Click 'Credential for Slack API' > 'Create New Credential' and complete OAuth2
- 5In the Channel field, click the expression toggle (the '=' icon) and enter your channel ID
- 6In the Text field, click the expression toggle and select 'message' from the previous node output
channel: {{channel}}
ts: {{ts}}
Canvas > + Node > Slack > Message > Send (header)
Add a header message to open the standup thread
Before the per-person Slack messages, post a single header message to the channel to frame the standup. Add a Slack node immediately after the Schedule Trigger (parallel to the Todoist fetch, or in sequence before it). Set the text to something like ':clipboard: *Daily Standup — {{$now.toFormat('EEEE, MMMM d')}}*'. This gives the thread a clear timestamp and makes it easy to search in Slack history.
- 1Click '+' after the Schedule Trigger to insert a new node before Todoist
- 2Add a Slack node set to Message > Send
- 3In the Text field, enter ':clipboard: *Daily Standup — {{$now.toFormat('EEEE, MMMM d')}}*'
- 4Use the same channel ID as the per-person messages
- 5Connect the output of this Slack node to the Todoist node
Canvas > + Node > IF
Handle the no-tasks edge case
If a team member has no tasks due today, the grouping Code node may produce an empty tasks array for them. Add an IF node after the grouping Code node to check whether tasks.length is greater than 0. Route the 'true' branch to the formatting and Slack nodes. Route the 'false' branch to a No-Op node or skip entirely. This prevents sending empty standup entries like '*Maria Chen*\n(no tasks today)' unless you intentionally want that.
- 1Click '+' after the grouping Code node
- 2Search 'IF' and select the IF node
- 3Set Condition: Value 1 = '{{$json.tasks.length}}', Operation = 'Greater Than', Value 2 = '0'
- 4Connect the 'true' output to the formatting Code node
- 5Connect the 'false' output to a No Operation node to terminate that branch cleanly
Canvas > Test Workflow > Active Toggle
Activate and test the full workflow
Click 'Test Workflow' at the bottom of the canvas to run the entire flow end-to-end with live data. Watch the execution log — each node should show a green checkmark and the execution count. Open Slack and verify the standup messages arrived in the correct channel. Once confirmed, toggle the workflow to 'Active' using the switch in the top right corner of the canvas. The workflow will now fire automatically every weekday at your configured time.
- 1Click 'Test Workflow' at the bottom toolbar
- 2Watch each node highlight green as it executes
- 3Open Slack and confirm messages appear in your standup channel
- 4Click the 'Inactive' toggle in the top right to switch it to 'Active'
- 5Confirm the toggle shows 'Active' in green
Scaling Beyond Teams with 50+ members or 500+ tasks per day+ Records
If your volume exceeds Teams with 50+ members or 500+ tasks per day records, apply these adjustments.
Batch Slack posts to avoid rate limits
Slack's chat.postMessage endpoint has a rate limit of 1 message per second per channel. For teams with 20+ members, posting one message per person back-to-back hits this limit. Add a 'Wait' node set to 1.1 seconds between Slack nodes, or consolidate all messages into a single multi-section message using Slack Block Kit.
Use Todoist's pagination for large task sets
The Todoist REST API returns up to 200 tasks per request. Teams with heavy task volumes may hit this silently — you'll get exactly 200 tasks with no indication more exist. Add an HTTP Request node using Todoist's sync API with incremental sync tokens to reliably fetch all tasks regardless of count.
Split large teams into sub-channels
A single Slack channel with 30+ standup messages becomes unreadable fast. At 15+ members, route task summaries into team-specific channels (#frontend-standup, #backend-standup) using a Switch node on project_id or a team membership lookup. Keeps each channel's standup under 8 messages.
Going live
Production Checklist
Before you turn this on for real, confirm each item.
Troubleshooting
Common errors and how to fix them.
Frequently Asked Questions
Common questions about this workflow.
Analysis
Use n8n for this if your team self-hosts or has data residency requirements — all Todoist task data stays on your own infrastructure and never passes through a third-party automation vendor's servers. It's also the right call if you want to customize the standup format significantly: grouping by project, priority sorting, conditional sections for blockers, clickable links. The Code node gives you real JavaScript, not a watered-down expression language. The one scenario where you'd skip n8n: if your team is non-technical and nobody wants to maintain JavaScript in a workflow. Use Make instead — same logic, visual interface, no code required.
n8n Cloud starts at $20/month for 2,500 executions. This workflow uses 1 execution per run regardless of team size. At 5 days/week × 4 weeks = 20 executions/month, you're nowhere near that limit. Self-hosted n8n is free with no execution cap — your only cost is the server (~$5–10/month on a basic VPS). Compare that to Zapier, where each task-to-message pair counts as a separate Zap step. A 10-person team with 5 tasks each = 50 task items processed = 50 Zap tasks per morning run × 20 days = 1,000 Zap tasks/month. That's fine on Zapier's $19.99 Starter plan (750 tasks) — actually, you'd need the $49 Professional plan. n8n self-hosted costs you $5/month in server fees for the same output.
Make handles the grouping logic more visually — its Array Aggregator and Iterator modules avoid custom code for the task-grouping step, which is genuinely easier to configure than a Code node. Zapier has a native Todoist integration with a cleaner 'New Task Due Today' trigger, but you can't group tasks across multiple items without Code by Zapier, which requires their Professional plan. Power Automate has a Todoist connector but it's a premium connector requiring a Power Automate Per User license ($15/user/month), which makes it the most expensive option here for any team. Pipedream offers the closest feature parity to n8n at similar cost, but its Todoist trigger is event-based (new task created) rather than schedule-based, so you'd need extra logic to filter for today's tasks at standup time. n8n's Schedule Trigger plus Code node combination is the most direct path to exactly this output.
Three things you'll hit after setup. First: Todoist's 'Today' filter is timezone-sensitive. If your n8n server clock is in UTC and your team is in UTC-8, you'll get yesterday's tasks in the morning report for several months of the year — always set an explicit timezone in workflow settings and use a date expression filter instead of the built-in 'Today' filter. Second: Slack's chat.postMessage will silently truncate messages longer than 4,000 characters — if someone has 40 tasks due today, their standup block will be cut off with no error returned. Add a character count check in the Code node and split into multiple messages if needed. Third: Todoist doesn't fire a webhook when due dates are set or changed, so if someone reassigns a task to today at 8:55 AM, the 9:00 AM standup will pick it up only if the timing works out — there's no way to guarantee real-time accuracy with a scheduled pull approach.
Ideas for what to build next
- →Add a blockers section from Todoist labels — Tag any task with a 'blocked' label in Todoist and extend the Code node to append a separate *Blockers* section to each person's standup message, making impediments immediately visible without extra reporting.
- →Post a weekly digest on Fridays — Add a second Schedule Trigger set to Friday at 4:00 PM that pulls all tasks completed this week using Todoist's completed tasks API endpoint and posts a weekly wins summary to a separate #team-wins Slack channel.
- →Route messages to per-project Slack channels — If your team has multiple projects with separate Slack channels, extend the workflow with a Switch node that maps each Todoist project_id to a corresponding Slack channel ID, so frontend tasks go to #frontend-standup and backend tasks go to #backend-standup.
Related guides
How to Send Weekly Todoist Reports to Slack with Pipedream
~15 min setup
How to Send Weekly Todoist Reports to Slack with Power Automate
~15 min setup
How to Send Weekly Todoist Reports to Slack with n8n
~20 min setup
How to Send Weekly Todoist Reports to Slack with Zapier
~8 min setup
How to Send Weekly Todoist Reports to Slack with Make
~12 min setup
How to Assign Todoist Tasks from Slack Mentions with Pipedream
~15 min setup