

How to Automate Daily Standups from Asana to Slack with Pipedream
Every morning at a set time, Pipedream pulls completed and upcoming tasks from Asana and posts a formatted standup report to a Slack channel.
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 5-30 people who track work in Asana and want async standup reports posted to Slack automatically each morning.
Not ideal for
Teams that need interactive standup prompts where each person responds individually — use Geekbot or Standuply for that.
Sync type
scheduledUse case type
reportingReal-World Example
A 12-person product team at a B2B SaaS company had a daily 9 AM standup that ran 25 minutes because people couldn't remember what they finished the day before. They set up this workflow to post a standup digest to #product-standup at 8:50 AM every weekday — tasks completed in the last 24 hours and tasks due today, grouped by assignee. The meeting dropped to 10 minutes within the first week.
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 Name | name | |
| Assignee Name | assignee.name | |
| Completed At | completed_at | |
| Due On | due_on | |
| Permalink URL | permalink_url | |
3 optional fields▸ show
| Project Name | memberships[0].project.name |
| Task Notes / Description | notes |
| Custom Status / Section | memberships[0].section.name |
Step-by-Step Setup
pipedream.com > Workflows > New Workflow
Create a new Pipedream workflow
Go to pipedream.com and log in. Click 'Workflows' in the left sidebar, then click the blue 'New Workflow' button in the top right. You'll land on a blank canvas with a trigger slot at the top and a '+ Add a step' button below it. Give your workflow a name immediately — something like 'Daily Asana Standup to Slack' — by clicking the pencil icon next to 'Untitled Workflow' at the top.
- 1Log in at pipedream.com
- 2Click 'Workflows' in the left sidebar
- 3Click the blue 'New Workflow' button
- 4Click the pencil icon next to 'Untitled Workflow' and type your workflow name
- 5Press Enter to save the name
Workflow Canvas > Add a trigger > Schedule > Cron Expression
Set a daily schedule trigger
Click the 'Add a trigger' slot. In the trigger selector panel that opens on the right, type 'Schedule' in the search box and select the 'Schedule' source. Choose 'Cron expression' from the interval options so you have exact control. Enter a cron expression for your target time — for 8:50 AM Monday through Friday UTC, use '50 8 * * 1-5'. Pipedream runs cron in UTC, so adjust for your team's timezone before saving.
- 1Click the 'Add a trigger' slot on the canvas
- 2Type 'Schedule' in the search field in the right panel
- 3Select 'Schedule' from the results
- 4Click 'Cron expression' as the trigger type
- 5Enter your cron string (e.g. '50 8 * * 1-5' for 8:50 AM UTC weekdays)
- 6Click 'Save and continue'
Workflow Canvas > + Add a step > Run Node.js code
Connect Asana and fetch completed tasks
Click '+ Add a step' below the trigger and select 'Run Node.js code'. This step will call the Asana API directly to pull tasks completed in the last 24 hours. You'll use Asana's search endpoint with a 'completed_since' filter set to yesterday's ISO timestamp. Pipedream's Node.js steps support async/await natively, so you can use fetch or the axios package that's pre-installed.
- 1Click '+ Add a step' on the canvas
- 2Select 'Run Node.js code' from the step options
- 3Click the step title and rename it to 'Fetch Completed Tasks'
- 4Paste your Asana API code into the code editor (see pro tip below)
- 5Click 'Test' to run the step and verify the output
Step Panel > Connected Accounts tab > + Add account > Asana
Add your Asana Connected Account
Inside the code step, you referenced a personal access token. The cleaner approach is to use Pipedream's Connected Accounts so the token is stored securely and not hardcoded. Click the 'Connected Accounts' tab at the top of the step panel, then click '+ Add account' and select Asana. Pipedream will open an OAuth flow — click 'Allow' to grant access. After connecting, Pipedream injects your credentials as environment variables you can reference in the code.
- 1Click the 'Connected Accounts' tab inside your Node.js step
- 2Click '+ Add account'
- 3Search for and select 'Asana'
- 4Complete the OAuth prompt in the popup window by clicking 'Allow'
- 5Confirm the account appears with a green connected badge in the step
Workflow Canvas > + Add a step > Run Node.js code
Fetch upcoming tasks due today
Add a second Node.js code step to fetch tasks due today from Asana. This is a separate API call using the tasks endpoint filtered by due_on equal to today's date in YYYY-MM-DD format. Keep this as a separate step rather than combining it with the completed tasks fetch — it makes debugging much easier when one call fails.
- 1Click '+ Add a step' below the completed tasks step
- 2Select 'Run Node.js code'
- 3Rename the step to 'Fetch Due Today Tasks'
- 4Paste the due-today API call code into the editor
- 5Click 'Test' and verify the output contains today's due tasks
Workflow Canvas > + Add a step > Run Node.js code
Format the standup message
Add a third Node.js code step to combine the completed and due-today arrays into a formatted Slack Block Kit message. Reference the previous steps' outputs using steps['Fetch Completed Tasks'].$return_value and steps['Fetch Due Today Tasks'].$return_value. Group tasks by assignee name and build a blocks array with header, divider, and section blocks. Slack's Block Kit renderer handles markdown in mrkdwn fields — use *bold* for names and bullet points for task lists.
- 1Click '+ Add a step'
- 2Select 'Run Node.js code'
- 3Rename the step to 'Format Standup Message'
- 4Reference previous step outputs using steps['step-name'].$return_value
- 5Build and return a Slack blocks array from the task data
- 6Click 'Test' and inspect the blocks output in the return value panel
Workflow Canvas > + Add a step > Slack > Send a Message
Connect Slack and post the message
Add a new step and this time search for 'Slack' in the app list instead of using a code step. Select the 'Send a Message' action. Pipedream will prompt you to connect your Slack workspace via OAuth — click 'Connect Slack Account' and authorize the app. Once connected, set the Channel field to your standup channel (e.g. #product-standup), set Blocks to reference your formatted blocks from the previous step, and add a fallback Text field for notification previews.
- 1Click '+ Add a step'
- 2Search for 'Slack' in the app selector
- 3Select 'Slack' and then choose 'Send a Message' action
- 4Click 'Connect Slack Account' and complete the OAuth flow
- 5Set Channel to your standup Slack channel ID or name
- 6Set Blocks to {{steps['Format Standup Message'].$return_value.blocks}}
- 7Set Text to a fallback string like 'Daily standup report'
- 8Click 'Test' to post a test message to Slack
channel: {{channel}}
ts: {{ts}}
Workflow Canvas > + Add a step > Run Node.js code
Add error handling for empty task lists
Add a Node.js code step between the formatting step and the Slack step to check whether both task arrays are empty. If no tasks were completed yesterday and nothing is due today — common on Mondays after a holiday — you can either skip posting or send a short 'No updates today' message. Use $.flow.exit() to halt the workflow gracefully without triggering an error alert.
- 1Click '+ Add a step' between the format step and the Slack step
- 2Select 'Run Node.js code'
- 3Rename it to 'Guard: Skip if No Tasks'
- 4Add a check: if both arrays are empty, call $.flow.exit('No tasks to report')
- 5Click 'Test' with empty mock data to confirm the exit fires correctly
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 an immediate test run outside the schedule. Watch each step execute in sequence in the event inspector at the bottom of the screen. Click on each step to expand its input and output. Verify that the Asana task data flows correctly into the formatter and that the Slack message in your channel looks exactly as expected with correct names and task links.
- 1Click 'Run Now' in the top right of the workflow canvas
- 2Watch the step execution progress in the event inspector at the bottom
- 3Click each step row to expand and inspect its input/output
- 4Check your Slack channel to confirm the message posted correctly
- 5Verify assignee names, task links, and date groupings are accurate
Workflow Canvas > Deploy button > Logs tab
Deploy and monitor the workflow
Click the 'Deploy' button in the top right of the workflow canvas. Pipedream will activate the cron schedule and the workflow will run automatically at your specified time. Navigate to the 'Logs' tab to monitor future runs. Pipedream retains logs for 30 days on the free tier and shows you the exact error if any step fails. Set up an email or Slack error notification under Settings > Notifications so you know immediately if a run fails.
- 1Click 'Deploy' in the top right corner
- 2Confirm the workflow status shows 'Active' with your cron schedule listed
- 3Click the 'Logs' tab to see the event history
- 4Go to Settings > Notifications and add an email or Slack alert for workflow errors
- 5Check back the next morning to confirm the first scheduled run succeeded
Paste this code into two separate Node.js steps — 'Fetch Completed Tasks' and 'Fetch Due Today Tasks' — then paste the formatting function into a third step called 'Format Standup Message'. The final export.default in the formatting step returns the complete Slack blocks payload ready to pass directly into the Slack Send Message action.
JavaScript — Code Step// ── Step 1: Fetch Completed Tasks (paste into 'Fetch Completed Tasks' step) ──▸ Show code
// ── Step 1: Fetch Completed Tasks (paste into 'Fetch Completed Tasks' step) ──
export default async function({ steps, auths, $ }) {
const token = auths.asana.oauth_access_token;... expand to see full code
// ── Step 1: Fetch Completed Tasks (paste into 'Fetch Completed Tasks' step) ──
export default async function({ steps, auths, $ }) {
const token = auths.asana.oauth_access_token;
const projectGid = process.env.ASANA_PROJECT_GID;
const since = new Date(Date.now() - 86400000).toISOString(); // 24h ago UTC
const url = `https://app.asana.com/api/1.0/projects/${projectGid}/tasks` +
`?completed_since=${encodeURIComponent(since)}` +
`&opt_fields=name,assignee.name,completed_at,permalink_url,memberships.project.name` +
`&limit=50`;
const res = await fetch(url, {
headers: { Authorization: `Bearer ${token}` }
});
if (!res.ok) {
const err = await res.text();
throw new Error(`Asana API error ${res.status}: ${err}`);
}
const { data } = await res.json();
const completed = data.filter(t => t.completed_at !== null);
return completed;
}
// ── Step 2: Fetch Due Today Tasks (paste into 'Fetch Due Today Tasks' step) ──
export default async function({ steps, auths, $ }) {
const token = auths.asana.oauth_access_token;
const projectGid = process.env.ASANA_PROJECT_GID;
const today = new Date().toISOString().split('T')[0]; // YYYY-MM-DD
const url = `https://app.asana.com/api/1.0/projects/${projectGid}/tasks` +
`?opt_fields=name,assignee.name,due_on,permalink_url,memberships.project.name` +
`&limit=50`;
const res = await fetch(url, {
headers: { Authorization: `Bearer ${token}` }
});
if (!res.ok) {
const err = await res.text();
throw new Error(`Asana API error ${res.status}: ${err}`);
}
const { data } = await res.json();
const dueToday = data.filter(t => t.due_on === today && !t.completed_at);
return dueToday;
}
// ── Step 3: Format Standup Message (paste into 'Format Standup Message' step) ──
export default async function({ steps, $ }) {
const completed = steps['Fetch Completed Tasks'].$return_value || [];
const dueToday = steps['Fetch Due Today Tasks'].$return_value || [];
if (completed.length === 0 && dueToday.length === 0) {
$.flow.exit('No tasks to report today');
}
// Group by assignee
const groupByAssignee = (tasks) => tasks.reduce((acc, task) => {
const name = task.assignee?.name || 'Unassigned';
if (!acc[name]) acc[name] = [];
acc[name].push(task);
return acc;
}, {});
const completedByPerson = groupByAssignee(completed);
const dueByPerson = groupByAssignee(dueToday);
const today = new Date().toLocaleDateString('en-US', { weekday: 'long', month: 'long', day: 'numeric' });
const blocks = [
{
type: 'header',
text: { type: 'plain_text', text: `🗓 Daily Standup — ${today}`, emoji: true }
},
{ type: 'divider' }
];
if (completed.length > 0) {
blocks.push({
type: 'section',
text: { type: 'mrkdwn', text: '*✅ Completed in the last 24 hours*' }
});
for (const [person, tasks] of Object.entries(completedByPerson)) {
const lines = tasks.map(t => {
const project = t.memberships?.[0]?.project?.name || '';
return `• <${t.permalink_url}|${t.name}>${project ? ` — _${project}_` : ''}`;
}).join('\n');
blocks.push({
type: 'section',
text: { type: 'mrkdwn', text: `*${person}*\n${lines}` }
});
}
blocks.push({ type: 'divider' });
}
if (dueToday.length > 0) {
blocks.push({
type: 'section',
text: { type: 'mrkdwn', text: '*📅 Due today*' }
});
for (const [person, tasks] of Object.entries(dueByPerson)) {
const lines = tasks.map(t => {
const project = t.memberships?.[0]?.project?.name || '';
return `• <${t.permalink_url}|${t.name}>${project ? ` — _${project}_` : ''}`;
}).join('\n');
blocks.push({
type: 'section',
text: { type: 'mrkdwn', text: `*${person}*\n${lines}` }
});
}
}
return { blocks };
}Scaling Beyond 50+ tasks completed per day or 5+ Asana projects being monitored+ Records
If your volume exceeds 50+ tasks completed per day or 5+ Asana projects being monitored records, apply these adjustments.
Paginate Asana Results
Asana caps task responses at 100 records per request. If your team closes 100+ tasks daily, implement pagination using the next_page.offset token returned in the API response. Loop through pages in your Node.js step until next_page is null, then merge all results before formatting.
Cap Section Length in Block Kit
Slack section blocks have a 3000-character limit. For large teams, don't list every task per person — instead show the count and link to the Asana project. For example: 'Maya Patel completed 12 tasks — view in Asana' with a button block linking to the project filter view.
Split by Project Into Multiple Messages
If you're aggregating 5+ Asana projects, a single Slack message will be overwhelming. Post one message per project to its own channel using separate Slack steps in the same workflow. Each Slack API call counts as one Pipedream credit, so 5 projects = 5 extra credits 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 even one developer who can write basic JavaScript. The scheduled trigger fires with sub-second accuracy, and the Node.js code steps mean you can handle Asana's pagination, group tasks by assignee, and format Block Kit messages in a single workflow without duct-taping together five no-code actions. The alternative to reach for is Make — if nobody on your team is comfortable in code, Make's Asana and Slack modules handle the API calls visually, though you'll hit the 1000-operations/month limit fast on a daily workflow.
Cost math: this workflow runs one scheduled trigger plus four steps (fetch completed, fetch due today, format, post to Slack) = roughly 5 Pipedream credits per run. At 5 runs/week (weekdays only), that's 25 credits/week or ~100 credits/month. Pipedream's free tier gives you 10,000 credits/month, so this workflow costs essentially nothing. Even if you add three more Slack channels and post to all of them, you're at 400 credits/month — still well inside the free tier. Compare that to Zapier, where a multi-step Zap running daily hits the 1000-task/month free cap in under two months and then costs $19.99/month minimum.
Zapier handles this use case but you'll write more workarounds: its Asana trigger for completed tasks polls every 15 minutes, not on a schedule, so you'd need a Zapier Schedule trigger chained with a Search action — clunky and fragile when Asana rate limits the polling. n8n is a strong alternative if you're self-hosting — the Asana node supports all the same API params and the Cron trigger is identical in behavior to Pipedream's. Make has a built-in Asana 'Search Tasks' module that handles filtering more visually than raw API calls, which is genuinely easier for non-developers. Power Automate can do this workflow but Asana has no official Power Automate connector — you'd use HTTP actions for all Asana calls, which defeats the point of using a no-code tool. Pipedream wins here because the combination of exact cron scheduling, Asana OAuth built-in, and flexible Node.js formatting is hard to beat for developer teams.
Three things you'll hit after setup. First, Asana's completed_since filter uses the task's completed_at timestamp in UTC — if a team member marks a task done at 11 PM their local time, it may appear in the next day's report or not at all depending on your UTC offset math. Test with real tasks and real timestamps before trusting the filter. Second, Slack's Block Kit has a hard limit of 50 blocks per message. A team that closes 40 tasks daily spread across 10 people can easily hit this — you'll see Slack silently truncate the message with no error in Pipedream. Add a block count check before posting. Third, Asana throttles API requests to 150 per minute per token. A workflow that fetches 5 projects in parallel can hit this instantly — add a 500ms delay between project fetch steps or use a single search endpoint call scoped to the workspace with project filters.
Ideas for what to build next
- →Add a Weekly Summary Variant — Duplicate the workflow and change the cron to run every Monday morning with a 7-day lookback window instead of 24 hours. This gives teams a weekly digest in addition to the daily standup and takes about 10 minutes to configure.
- →Include Blocked Tasks from Asana — Add a third Asana API call that fetches tasks tagged as 'Blocked' or sitting in a 'Blocked' section. Post them as a separate section in the standup message so blockers are visible without anyone having to remember to mention them.
- →Route Reports to Per-Project Channels — If your team uses multiple Asana projects with separate Slack channels, add conditional routing in the formatting step to map each project GID to its corresponding Slack channel ID. One workflow run can post tailored summaries to multiple channels simultaneously.
Related guides
How to Share Notion Meeting Notes to Slack with Pipedream
~15 min setup
How to Share Notion Meeting Notes to Slack with Power Automate
~15 min setup
How to Share Notion Meeting Notes to Slack with n8n
~20 min setup
How to Send Notion Meeting Notes to Slack with Zapier
~8 min setup
How to Share Notion Meeting Notes to Slack with Make
~12 min setup
How to Create Notion Tasks from Slack with Pipedream
~15 min setup