

How to Send Weekly Todoist Reports to Slack with Pipedream
Every Monday morning, Pipedream queries the Todoist API for the past 7 days of completed tasks, aggregates counts by project, and posts a formatted summary message to a Slack channel for leadership review.
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 who track work in Todoist and need a consistent Monday morning status post in Slack without someone manually compiling it.
Not ideal for
Teams needing real-time task alerts — use a webhook-based Todoist-to-Slack trigger instead of this scheduled digest approach.
Sync type
scheduledUse case type
reportingReal-World Example
A 12-person product team at a B2B SaaS company tracks sprint tasks in Todoist across 4 projects. Before this workflow, the team lead spent 20-30 minutes every Monday pulling completion numbers and writing a Slack update by hand. Now Pipedream runs at 8:00 AM every Monday, pulls the previous week's completed tasks from the Todoist API, groups them by project, and posts a structured summary to #leadership-updates automatically.
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 Pipedream
Copy the pre-built Pipedream blueprint and paste it straight into Pipedream. 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 Completion Date | completed_at | |
| Project ID | project_id | |
| Slack Channel ID | ||
| Slack Block Kit Payload | ||
| Report Date Range (Since / Until) | ||
3 optional fields▸ show
| Task Content | content |
| Task ID | id |
| Assignee User ID | user_id |
Step-by-Step Setup
pipedream.com > Workflows > New Workflow
Create a new Pipedream Workflow
Go to pipedream.com and click 'Workflows' in the left sidebar. Click the green 'New Workflow' button in the top right corner. Pipedream will open a blank workflow canvas with an empty trigger slot at the top. Give the workflow a name — something like 'Weekly Todoist Report to Slack' — by clicking the default name at the top of the canvas.
- 1Log in at pipedream.com
- 2Click 'Workflows' in the left navigation
- 3Click the green 'New Workflow' button
- 4Click the workflow name at the top and rename it to 'Weekly Todoist Report to Slack'
Workflow Canvas > Add Trigger > Schedule > Cron Scheduler
Set a Schedule Trigger
Click the 'Add Trigger' block. In the trigger selector panel that slides in from the right, search for 'Schedule' and select it. Choose 'Cron Scheduler' as the trigger type. Enter the cron expression '0 8 * * 1' — this fires every Monday at 8:00 AM UTC. Adjust the timezone field if your leadership team is not on UTC.
- 1Click the 'Add Trigger' block
- 2Type 'Schedule' in the search box
- 3Select 'Schedule' from the results
- 4Choose 'Cron Scheduler' as the schedule type
- 5Enter '0 8 * * 1' in the cron expression field
Workflow Canvas > + Add Step > App > Todoist > Get Completed Tasks
Connect Your Todoist Account
Click '+ Add Step' below the trigger and select 'App' from the step type options. Search for 'Todoist' in the app selector. Choose the action 'Get Completed Tasks' (also listed as 'Get all completed items' depending on the component version). In the step configuration panel, click 'Connect Todoist Account' and follow the OAuth flow. Todoist will ask you to authorize Pipedream — accept all requested scopes.
- 1Click '+ Add Step' below the Schedule trigger
- 2Select 'App' as the step type
- 3Search for 'Todoist' and select it
- 4Choose the action 'Get Completed Tasks'
- 5Click 'Connect Todoist Account' and complete the OAuth flow in the popup
Workflow Canvas > Todoist Step > Configure > Since / Until fields
Configure the Todoist Date Range Parameters
In the Todoist step, you need to pass dynamic date values for 'Since' and 'Until' so the query always covers the last 7 days. Click the 'Since' field and switch to expression mode (click the '{{ }}' toggle). Enter a JavaScript expression to calculate last Monday's date. Leave 'Project ID' blank to fetch completions across all projects, or enter a specific project ID to narrow the scope. Set 'Limit' to 200 to avoid missing tasks in busy weeks.
- 1Click the 'Since' field in the Todoist step
- 2Click the '{{ }}' expression toggle to enable dynamic values
- 3Enter the expression for 7 days ago (handled in the code step next — leave this as a static value for now: the prior Monday's ISO date)
- 4Leave 'Project ID' blank for all projects or paste a specific project ID
- 5Set 'Limit' to 200
Workflow Canvas > + Add Step > Run Node.js code
Add a Node.js Code Step to Aggregate Task Data
Click '+ Add Step' and select 'Run Node.js code'. This is where you transform the raw Todoist API response into a structured summary grouped by project. Paste the code from the Pro Tip section into the code editor. The script pulls the completed task array from the previous step, groups tasks by project name, counts completions per project, calculates a total, and assembles the Slack message payload. Click 'Test' to run it against real data before moving on.
- 1Click '+ Add Step' below the Todoist step
- 2Select 'Run Node.js code'
- 3Delete the placeholder code in the editor
- 4Paste the aggregation code from the Pro Tip section
- 5Click 'Test' to validate the output
Paste this into the Node.js code step (Step 3 in your workflow). It pulls the completed tasks array from the Todoist step output, resolves project IDs to names using a second Todoist API call, groups and counts by project, then builds a Slack Block Kit payload ready to drop into the Slack step's Blocks field.
JavaScript — Code Stepimport axios from 'axios';▸ Show code
import axios from 'axios';
export default defineComponent({
async run({ steps, $ }) {... expand to see full code
import axios from 'axios';
export default defineComponent({
async run({ steps, $ }) {
// Pull completed tasks from previous Todoist step
const completedItems = steps.todoist.$return_value?.items || [];
if (completedItems.length === 0) {
// Post a fallback message rather than an empty report
return {
totalCompleted: 0,
byProject: [],
slackBlocks: [
{
type: 'header',
text: { type: 'plain_text', text: '📋 Weekly Progress Report', emoji: true }
},
{
type: 'section',
text: { type: 'mrkdwn', text: 'No tasks were completed in Todoist this week.' }
}
]
};
}
// Fetch project names from Todoist API to resolve IDs to labels
const todoist_token = process.env.TODOIST_API_TOKEN;
let projectMap = {};
try {
const projectsResp = await axios.get('https://api.todoist.com/rest/v2/projects', {
headers: { Authorization: `Bearer ${todoist_token}` }
});
for (const project of projectsResp.data) {
projectMap[project.id] = project.name;
}
} catch (err) {
console.error('Failed to fetch Todoist projects:', err.message);
// Continue with raw IDs if lookup fails
}
// Group completions by project
const projectCounts = {};
for (const item of completedItems) {
const projectName = projectMap[item.project_id] || `Project ${item.project_id}`;
projectCounts[projectName] = (projectCounts[projectName] || 0) + 1;
}
const totalCompleted = completedItems.length;
const byProject = Object.entries(projectCounts)
.sort((a, b) => b[1] - a[1])
.map(([name, count]) => ({ name, count }));
// Build date range label
const now = new Date();
const lastMonday = new Date(now);
lastMonday.setDate(now.getDate() - 7);
const dateLabel = `${lastMonday.toLocaleDateString('en-US', { month: 'short', day: 'numeric' })}–${now.toLocaleDateString('en-US', { month: 'short', day: 'numeric', year: 'numeric' })}`;
// Build Slack Block Kit payload
const projectLines = byProject
.map(p => `• *${p.name}* — ${p.count} task${p.count !== 1 ? 's' : ''} completed`)
.join('\n');
const slackBlocks = [
{
type: 'header',
text: { type: 'plain_text', text: `📋 Weekly Progress Report — ${dateLabel}`, emoji: true }
},
{ type: 'divider' },
{
type: 'section',
text: { type: 'mrkdwn', text: projectLines }
},
{ type: 'divider' },
{
type: 'section',
text: { type: 'mrkdwn', text: `*Total completed this week: ${totalCompleted} task${totalCompleted !== 1 ? 's' : ''}*` }
},
{
type: 'context',
elements: [{ type: 'mrkdwn', text: `Generated automatically by Pipedream · ${new Date().toISOString()}` }]
}
];
return { totalCompleted, byProject, slackBlocks };
}
});
Workflow Canvas > + Add Step > App > Slack > Send Message
Connect Your Slack Account
Click '+ Add Step' and search for 'Slack'. Select the 'Send Message' action (or 'Send Block Kit Message' if you want rich formatting). Click 'Connect Slack Account' — this opens a Slack OAuth window asking you to select the workspace and approve the bot's permissions. You need at minimum the 'chat:write' scope. If you want the bot to post to private channels, you must invite it to those channels manually in Slack first.
- 1Click '+ Add Step' below the code step
- 2Search for 'Slack' and select it
- 3Choose 'Send Message (Block Kit)' for formatted output
- 4Click 'Connect Slack Account'
- 5Select your workspace in the OAuth popup and click Allow
Workflow Canvas > Slack Step > Configure > Channel / Blocks fields
Configure the Slack Message Destination
In the Channel field, type the name of your target channel — for example '#leadership-updates'. You can also paste a channel ID (starts with 'C') for more reliability. In the Blocks field, reference the output from your code step using the expression '{{steps.code.$return_value.slackBlocks}}'. Set the 'Text' fallback field to something like 'Weekly Todoist Progress Report' for clients that don't render Block Kit.
- 1Type your channel name (e.g. '#leadership-updates') in the Channel field
- 2Click the Blocks field and switch to expression mode with '{{ }}'
- 3Enter '{{steps.code.$return_value.slackBlocks}}' to reference the formatted payload
- 4Set the Text fallback field to 'Weekly Todoist Progress Report'
Workflow Canvas > Run Now button (top right)
Test the Full Workflow End-to-End
Click 'Run Now' at the top of the workflow canvas to trigger a manual test run (this bypasses the Monday schedule and fires immediately). Watch each step turn green in sequence. Click the Todoist step to inspect the raw API response — confirm you see completed tasks in the 'items' array. Click the code step to verify the aggregated output. Click the Slack step to confirm it shows a 200 OK response from Slack.
- 1Click the 'Run Now' button at the top right of the canvas
- 2Watch each step indicator — green means success, red means error
- 3Click the Todoist step result to inspect the raw 'items' array
- 4Click the code step result to verify 'totalCompleted' and 'byProject' values
- 5Check your Slack channel to confirm the message appeared
Workflow Canvas > Node.js Code Step > Edit code
Add Error Handling to the Code Step
Go back to your Node.js code step. Wrap the main logic in a try/catch block. In the catch block, use $.send.http or log the error — but more practically, add a fallback Slack notification so leadership knows the report failed instead of just silently not appearing. Update the step to export an error flag if the Todoist response is empty or malformed. This prevents silent failures on weeks where the API is slow.
- 1Click the code step to open the editor
- 2Wrap your main logic in a try/catch block
- 3In the catch block, throw a descriptive error with $.flow.exit() or re-throw to halt the workflow
- 4Add a check for empty 'items' array and return an early 'No completed tasks this week' message
- 5Click 'Test' again to confirm the updated code still passes
Paste this into the Node.js code step (Step 3 in your workflow). It pulls the completed tasks array from the Todoist step output, resolves project IDs to names using a second Todoist API call, groups and counts by project, then builds a Slack Block Kit payload ready to drop into the Slack step's Blocks field.
JavaScript — Code Stepimport axios from 'axios';▸ Show code
import axios from 'axios';
export default defineComponent({
async run({ steps, $ }) {... expand to see full code
import axios from 'axios';
export default defineComponent({
async run({ steps, $ }) {
// Pull completed tasks from previous Todoist step
const completedItems = steps.todoist.$return_value?.items || [];
if (completedItems.length === 0) {
// Post a fallback message rather than an empty report
return {
totalCompleted: 0,
byProject: [],
slackBlocks: [
{
type: 'header',
text: { type: 'plain_text', text: '📋 Weekly Progress Report', emoji: true }
},
{
type: 'section',
text: { type: 'mrkdwn', text: 'No tasks were completed in Todoist this week.' }
}
]
};
}
// Fetch project names from Todoist API to resolve IDs to labels
const todoist_token = process.env.TODOIST_API_TOKEN;
let projectMap = {};
try {
const projectsResp = await axios.get('https://api.todoist.com/rest/v2/projects', {
headers: { Authorization: `Bearer ${todoist_token}` }
});
for (const project of projectsResp.data) {
projectMap[project.id] = project.name;
}
} catch (err) {
console.error('Failed to fetch Todoist projects:', err.message);
// Continue with raw IDs if lookup fails
}
// Group completions by project
const projectCounts = {};
for (const item of completedItems) {
const projectName = projectMap[item.project_id] || `Project ${item.project_id}`;
projectCounts[projectName] = (projectCounts[projectName] || 0) + 1;
}
const totalCompleted = completedItems.length;
const byProject = Object.entries(projectCounts)
.sort((a, b) => b[1] - a[1])
.map(([name, count]) => ({ name, count }));
// Build date range label
const now = new Date();
const lastMonday = new Date(now);
lastMonday.setDate(now.getDate() - 7);
const dateLabel = `${lastMonday.toLocaleDateString('en-US', { month: 'short', day: 'numeric' })}–${now.toLocaleDateString('en-US', { month: 'short', day: 'numeric', year: 'numeric' })}`;
// Build Slack Block Kit payload
const projectLines = byProject
.map(p => `• *${p.name}* — ${p.count} task${p.count !== 1 ? 's' : ''} completed`)
.join('\n');
const slackBlocks = [
{
type: 'header',
text: { type: 'plain_text', text: `📋 Weekly Progress Report — ${dateLabel}`, emoji: true }
},
{ type: 'divider' },
{
type: 'section',
text: { type: 'mrkdwn', text: projectLines }
},
{ type: 'divider' },
{
type: 'section',
text: { type: 'mrkdwn', text: `*Total completed this week: ${totalCompleted} task${totalCompleted !== 1 ? 's' : ''}*` }
},
{
type: 'context',
elements: [{ type: 'mrkdwn', text: `Generated automatically by Pipedream · ${new Date().toISOString()}` }]
}
];
return { totalCompleted, byProject, slackBlocks };
}
});
Workflow Canvas > Active/Inactive toggle (top right)
Activate the Workflow
Toggle the workflow from 'Inactive' to 'Active' using the switch in the top right of the canvas. Pipedream will confirm activation and show the next scheduled run time. Verify the next run shows the upcoming Monday at your configured time. You can also check the 'Runs' tab to see historical executions once the first scheduled run fires.
- 1Click the toggle switch at the top right of the canvas from 'Inactive' to 'Active'
- 2Confirm the dialog if prompted
- 3Check the trigger block — it should display the next scheduled run time
- 4Navigate to the 'Runs' tab to monitor future executions
Scaling Beyond 200+ tasks completed per week across all Todoist projects+ Records
If your volume exceeds 200+ tasks completed per week across all Todoist projects records, apply these adjustments.
Paginate the Todoist API Manually
The Todoist completed tasks endpoint returns at most 200 items per call. Replace the single Todoist step with a Node.js code step that loops — fetching 200 items, incrementing an offset, and continuing until the response returns fewer than 200. This is the only reliable way to capture full week data for active teams.
Filter by Project Before Aggregating
If your team uses 10+ projects but leadership only cares about 4, pass a project_id filter to each paginated API call. This reduces response size and speeds up the code step. Fetch each project in parallel using Promise.all() rather than sequential await calls.
Cache Project Name Lookups
For teams with many projects, hitting the /projects endpoint on every weekly run is wasteful. Store the project ID-to-name map in a Pipedream Data Store (pipedream.com > Data Stores) and refresh it only when a lookup fails. This cuts one full API call per run.
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 Pipedream for this if your team has someone who can write or read basic JavaScript. The scheduled trigger is reliable — Pipedream's cron infrastructure has better uptime than Make's scheduler in practice — and the Node.js code step lets you call the Todoist API directly, paginate if needed, and build a proper Block Kit payload without fighting a visual interface. The one case where you'd pick something else: if nobody on the team touches code at all, Make's visual aggregator modules are easier to reason about without writing a line.
Cost math: this workflow runs once per week = 52 times per year. Each run touches 3 steps (Schedule trigger + Todoist fetch + Slack post) plus your code step = roughly 4-5 credits per run. That's 260 credits per year. Pipedream's free tier gives you 10,000 credits per month. You will not pay a cent for this workflow on Pipedream's free plan. Make's free tier allows 1,000 operations per month and charges per operation, so the same workflow costs 4-5 operations/run × 52 runs = ~260 operations/year — also free, but Make's free tier caps at 1,000/month total across all workflows, so if you have other automations running, you'll hit that wall faster.
Make handles this use case with a visual aggregator module that non-developers can configure — no code required to group tasks by project. Zapier has a 'Digest by Zapier' action that batches items, but it's clunky and limited to one digest per trigger, which makes cross-project aggregation painful. n8n's 'Summarize' node handles grouping cleanly and the self-hosted version is free at any volume, making it the better pick for privacy-conscious teams or those running 50+ workflows. Power Automate can do this but requires SharePoint or Dataverse for intermediate data storage, which adds unnecessary complexity. Pipedream wins here specifically because the code step handles the aggregation logic cleanly in one place, and the Slack Block Kit output is trivial to assemble in JavaScript compared to any visual tool.
Three things you'll hit after setup: First, Todoist's completed tasks API returns project_id as a numeric string, not a project name — if you forget to resolve it, your Slack report shows 'Project 2301847562' instead of 'Marketing'. Always make the secondary /projects call. Second, if your team uses Todoist's recurring tasks feature, completed recurring tasks appear in the API with the same task ID on each completion — so if someone completes the same recurring daily task 5 times in a week, it shows as one ID, not five. Use completed_at timestamps to count actual completions. Third, Slack's Block Kit has a 50-block limit per message. If you have more than ~20 projects, your blocks array will silently truncate — test with real data before going live.
Ideas for what to build next
- →Add Per-Person Task Breakdown — Extend the code step to group completions by Todoist user ID, then resolve user IDs to names via the Todoist /users endpoint. This gives leadership a per-person completion count alongside the project breakdown.
- →Archive Reports to a Notion or Google Sheet — Add a fourth step after the Slack post that writes the weekly totals (date range, project counts, total) to a Google Sheet or Notion database. After 12 weeks you'll have a trend dataset showing team velocity over time.
- →Send a Friday Reminder if the Week Looks Light — Add a second Pipedream workflow that runs every Friday afternoon, checks the week-to-date completion count, and posts a nudge to the team Slack channel if the count is below a threshold you define.
Related guides
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
How to Assign Todoist Tasks from Slack Mentions with Power Automate
~15 min setup