

How to Send Asana Milestone Updates to Slack with n8n
Polls Asana every 5 minutes for completed milestones or approaching deadlines and posts a formatted status message to the relevant Slack channel automatically.
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 running multiple Asana projects who want channel-specific Slack alerts when milestones complete or deadlines are 24–48 hours out — without anyone writing a status update manually.
Not ideal for
Teams that need sub-minute Slack alerts — Asana's API does not support webhooks for task completion events in free plans, so if you need true real-time you'll need Asana Business or Enterprise with webhook support and a self-hosted n8n instance with a public URL.
Sync type
scheduledUse case type
notificationReal-World Example
A 22-person product team at a B2B SaaS company runs 6 active projects in Asana simultaneously. Before this workflow, the PM posted manual Slack summaries every Friday — milestone completions from Tuesday went unnoticed until the weekend update. Now n8n polls Asana every 5 minutes and drops a formatted message into #product-updates the moment a milestone is marked complete, including the assignee name and due date.
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 Name | name | |
| Task GID | gid | |
| Resource Subtype | resource_subtype | |
| Completed | completed | |
| Due Date | due_on | |
| Project Name | memberships[0].project.name | |
| Slack Channel ID | ||
3 optional fields▸ show
| Assignee Name | assignee.name |
| Completion Date | completed_at |
| Notes / Description | notes |
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-left of the Workflows dashboard. Give it a clear name like 'Asana Milestones → Slack'. You'll land on a blank canvas with a single 'Add first step' node in the center. This is where you'll drop the trigger.
- 1Click '+ New Workflow' in the top-left corner
- 2Click the pencil icon next to 'My workflow' and rename it 'Asana Milestones → Slack'
- 3Click the large '+ Add first step' node on the canvas
Canvas > Add first step > Search 'Schedule' > Schedule Trigger
Add the Schedule trigger
In the node search panel, type 'Schedule' and select 'Schedule Trigger'. Set the interval to 'Every 5 Minutes'. This is your polling clock — every 5 minutes n8n will run the rest of the workflow to check Asana for changes. Asana's REST API does not push events to n8n without a publicly reachable webhook endpoint, so polling is the practical default for most self-hosted setups.
- 1Type 'Schedule' in the node search box
- 2Click 'Schedule Trigger' from the results
- 3Set 'Trigger Interval' to 'Minutes'
- 4Set 'Minutes Between Triggers' to 5
- 5Click 'Save' in the top-right
Canvas > + > Asana > Credentials > Create New > Personal Access Token
Connect your Asana account
Click the '+' button after the Schedule Trigger to add a new node. Search for 'Asana' and select it. Click 'Create New Credential' in the node panel. You'll be prompted to choose between OAuth2 and Personal Access Token — use Personal Access Token for self-hosted n8n unless you have a public callback URL configured for OAuth. Copy your Asana PAT from Asana > My Profile Settings > Apps > Manage Developer Apps.
- 1Click the '+' connector after the Schedule Trigger node
- 2Search 'Asana' and select the Asana node
- 3In the node panel, click the 'Credential for Asana API' dropdown
- 4Click 'Create New Credential'
- 5Paste your Asana Personal Access Token and click 'Save'
Asana Node > Resource: Task > Operation: Get Many > Filters
Configure the Asana node to fetch tasks
In the Asana node, set Resource to 'Task' and Operation to 'Get Many'. Set 'Return All' to true only if your projects have fewer than 500 tasks — above that, enable pagination manually. Under Filters, add 'Project' and enter your Asana project GID (found in the URL of the project: app.asana.com/0/PROJECT_GID/list). Add a second filter: 'Completed Since' set to a dynamic expression that looks back 6 minutes so you catch anything completed since the last poll.
- 1Set 'Resource' to 'Task'
- 2Set 'Operation' to 'Get Many'
- 3Toggle 'Return All' to true
- 4Under 'Additional Fields', click 'Add Field' and select 'Project'
- 5Enter your Asana project GID in the Project field
- 6Click 'Add Field' again and select 'Completed Since', then enter the expression: {{ new Date(Date.now() - 6 * 60 * 1000).toISOString() }}
Canvas > + after Asana node > Filter
Filter for milestone tasks only
Asana milestones are tasks where 'resource_subtype' equals 'milestone'. Add a Filter node after the Asana node to keep only those records. Without this filter, every completed task — subtasks, regular tasks, all of them — will post to Slack, which gets noisy fast. You can also add a second condition here for deadline proximity if you want 'approaching deadline' alerts alongside completion alerts.
- 1Click '+' after the Asana node and search 'Filter'
- 2Select 'Filter' from the list
- 3Click 'Add Condition'
- 4Set Field to {{ $json.resource_subtype }}
- 5Set Condition to 'equals'
- 6Set Value to 'milestone'
- 7Click 'Add Condition' again, set Field to {{ $json.completed }}, Condition to 'equals', Value to true
Canvas > + parallel from Asana node > Filter > Merge node
Add a deadline proximity branch
To also catch milestones with deadlines approaching in the next 48 hours, add an 'OR' branch to your filter — or add a second Filter node connected in parallel from the Asana node. Set the condition to check if 'due_on' is within 2 days of today. Use the expression: {{ Math.ceil((new Date($json.due_on) - new Date()) / (1000 * 60 * 60 * 24)) }} and check if it is less than or equal to 2. Merge both branches with a Merge node before the Slack step.
- 1Click the '+' on the Asana node again to add a second parallel path
- 2Add a second Filter node
- 3Set condition: {{ $json.resource_subtype }} equals 'milestone'
- 4Add condition: {{ $json.completed }} equals false
- 5Add condition: {{ Math.ceil((new Date($json.due_on) - new Date()) / (1000*60*60*24)) }} less than or equal to 2
- 6Add a 'Merge' node after both Filter nodes and connect both branches to it
Canvas > + after Merge node > Code
Add a Code node to format the Slack message
Add a Code node after the Merge node. This is where you construct the Slack Block Kit message payload. You have access to all Asana task fields here: name, due_on, assignee, completed status, and the project name from the memberships array. The Code node runs once per item, so each milestone gets its own formatted message. Paste the code from the pro tip section below.
- 1Click '+' after the Merge node and search 'Code'
- 2Select the 'Code' node
- 3Set 'Mode' to 'Run Once For Each Item'
- 4Paste the JavaScript from the pro tip section into the code editor
- 5Click 'Test Step' to verify the output contains a formatted 'blocks' array
Paste this into the Code node in Step 7. Set Mode to 'Run Once For Each Item'. It builds the full Slack Block Kit payload, resolves the correct Slack channel from a project-to-channel map, handles null assignees, formats dates, and flags whether this is a completion alert or a deadline warning — so your Slack messages are immediately readable without any extra config.
JavaScript — Code Node// n8n Code Node — Run Once For Each Item▸ Show code
// n8n Code Node — Run Once For Each Item // Maps Asana milestone data to a formatted Slack Block Kit message const task = $input.item.json;
... expand to see full code
// n8n Code Node — Run Once For Each Item
// Maps Asana milestone data to a formatted Slack Block Kit message
const task = $input.item.json;
// Map Asana project GIDs to Slack channel IDs
// Update these with your actual project GIDs and channel IDs
const PROJECT_CHANNEL_MAP = {
'1199000000001234': 'C045ABCDE12', // Q1 Product Launch → #product-updates
'1199000000005678': 'C067FGHIJ34', // Platform Reliability → #eng-reliability
'default': 'C089KLMNO56' // #general-updates fallback
};
// Resolve project info from memberships array
const membership = task.memberships && task.memberships.length > 0
? task.memberships[0]
: null;
const projectName = membership?.project?.name || 'Unknown Project';
const projectGid = membership?.project?.gid || 'default';
// Resolve Slack channel
const channel = PROJECT_CHANNEL_MAP[projectGid] || PROJECT_CHANNEL_MAP['default'];
// Resolve assignee safely
const assigneeName = task.assignee?.name || 'Unassigned';
// Format dates
const formatDate = (dateStr) => {
if (!dateStr) return 'No date set';
const d = new Date(dateStr);
return d.toLocaleDateString('en-US', { month: 'short', day: 'numeric', year: 'numeric' });
};
// Calculate days until due
const daysUntilDue = task.due_on
? Math.ceil((new Date(task.due_on) - new Date()) / (1000 * 60 * 60 * 24))
: null;
// Determine alert type
const isComplete = task.completed === true;
const headerText = isComplete ? '✅ Milestone Complete' : `⚠️ Milestone Due in ${daysUntilDue} Day${daysUntilDue === 1 ? '' : 's'}`;
const dateLabel = isComplete
? `📅 Completed: ${formatDate(task.completed_at)}`
: `📅 Due: ${formatDate(task.due_on)}`;
// Build Block Kit blocks array
const blocks = [
{
type: 'header',
text: { type: 'plain_text', text: headerText, emoji: true }
},
{
type: 'section',
text: {
type: 'mrkdwn',
text: `*${task.name}*\n📁 Project: ${projectName}\n👤 Owner: ${assigneeName}\n${dateLabel}`
}
}
];
// Add notes if present (truncate to 200 chars)
if (task.notes && task.notes.trim().length > 0) {
const truncatedNotes = task.notes.length > 200
? task.notes.substring(0, 197) + '...'
: task.notes;
blocks.push({
type: 'section',
text: { type: 'mrkdwn', text: truncatedNotes }
});
}
// Add divider
blocks.push({ type: 'divider' });
return { channel, blocks, taskGid: task.gid, projectName, isComplete };channel: {{channel}}
ts: {{ts}}
Canvas > + after Code node > Slack > Credentials > Create New > OAuth2
Connect your Slack account
Add a Slack node after the Code node. Click 'Create New Credential' and choose OAuth2. You'll need to create a Slack App at api.slack.com/apps with the scopes: chat:write, channels:read, and groups:read. Once your app is installed to your workspace, copy the Bot OAuth Token and paste it into n8n. The bot must be manually invited to any private channel you want to post to.
- 1Click '+' after the Code node and search 'Slack'
- 2Select the Slack node
- 3Click 'Credential for Slack API' dropdown and select 'Create New Credential'
- 4Paste your Slack Bot OAuth Token (starts with xoxb-)
- 5Click 'Save'
Slack Node > Resource: Message > Operation: Post
Configure the Slack message action
In the Slack node, set Resource to 'Message' and Operation to 'Post'. For 'Channel', use the expression {{ $json.channel }} to pull the channel ID from your Code node output. Set 'Message Type' to 'Block'. In the 'Blocks' field, reference {{ $json.blocks }} from the Code node. This sends the fully formatted Block Kit message, including bold project name, milestone title, assignee, and due date.
- 1Set 'Resource' to 'Message'
- 2Set 'Operation' to 'Post'
- 3Set 'Channel' to expression mode and enter {{ $json.channel }}
- 4Set 'Message Type' to 'Block'
- 5Set 'Blocks' to expression mode and enter {{ JSON.stringify($json.blocks) }}
Canvas > + after Schedule Trigger > Code (before Asana node)
Add deduplication with n8n static data
Without deduplication, every poll will re-fetch and re-post any milestone that was completed in the last 6 minutes AND is still within the polling overlap window. Add a Code node before your Filter nodes. Use n8n's built-in static workflow data to store GIDs of milestones already posted. On each run, skip any GID already in that store and add new ones after posting.
- 1Click '+' after the Schedule Trigger to insert a Code node before the Asana node
- 2Set Mode to 'Run Once For All Items'
- 3Paste the deduplication initialization code (see pro tip)
- 4After the Slack node, add another Code node to write the posted GID to static data
- 5Connect this final Code node back to nothing — it's a terminal write step
Canvas > Activate toggle (top right) > Left sidebar > Executions
Activate the workflow and verify
Click the toggle in the top-right of the canvas to activate the workflow. n8n will immediately begin polling on your 5-minute schedule. Watch the 'Executions' tab in the left sidebar — you should see a new execution entry every 5 minutes. Click any execution to inspect what data came through each node. Run a test by marking an Asana milestone complete and waiting up to 5 minutes for the Slack message to appear.
- 1Click the grey toggle switch in the top-right corner of the canvas
- 2Confirm the toggle turns green and shows 'Active'
- 3Click 'Executions' in the left sidebar
- 4Mark a test Asana milestone complete
- 5Wait 5 minutes and confirm the Slack message appears in the target channel
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 you are self-hosting your automation stack, want to avoid per-task pricing, or need to run custom JavaScript to handle Asana's nested data structures (memberships arrays, subtype filtering, null assignees). n8n handles all of that in Code nodes without workarounds. It is also the right call if you are monitoring 10+ projects simultaneously — you pay nothing extra per execution volume on self-hosted. The one scenario where you'd pick something else: if your company already runs Make and the whole team knows it, the Asana module in Make handles this same flow with less setup. Make's Asana integration includes built-in subtype filtering in the UI so you don't need to write filter expressions by hand.
n8n Cloud costs $20/month for the Starter plan, which includes 2,500 executions/month. This workflow fires once every 5 minutes — that's 288 executions per day, or roughly 8,640 per month. You'll hit the Starter limit in under 9 days. You need the Pro plan at $50/month (10,000 executions) or self-hosting, which runs on a $6/month VPS and has no execution cap. Make handles the equivalent workflow for $9/month on the Core plan, which gives 10,000 operations/month — and each Make scenario run counts as roughly 3-4 operations (watch, filter, HTTP, Slack), so you get about 2,500-3,000 effective runs per month. For pure cost at this polling frequency, self-hosted n8n wins by a wide margin.
Make has a native Asana 'Watch Tasks' module that uses long-polling under the hood — slightly faster response than n8n's schedule trigger and no code required for basic filtering. Zapier's Asana integration includes a 'New Completed Task' trigger but it does not natively filter by resource_subtype, so you'd need a Formatter step to filter out non-milestones, and at 5-minute polling intervals you burn through Zap tasks fast. Power Automate has an Asana connector but it is shallow — no milestone subtype filtering and limited to basic task fields. Pipedream's Asana source trigger is solid and supports true webhooks if you configure the Asana Events API, making it faster than n8n's polling approach. n8n is still the right call here if you need the Code node flexibility, want zero per-task costs, and are comfortable self-hosting — Pipedream edges it out only on real-time latency.
Three things you'll hit after going live: First, Asana's 'completed_at' timestamp is in UTC with millisecond precision, but 'due_on' is a plain date string with no timezone — your deadline proximity math will be off by up to 24 hours in certain timezones if you don't normalize both to the same reference. Set your date comparisons to use noon UTC on the due_on date. Second, the Asana API paginates at 100 tasks per request by default. If a project has 200+ tasks, 'Return All' in the n8n Asana node fires multiple API calls per execution — this counts against your rate limit faster than you expect. Third, Slack's Block Kit silently truncates section text at 3,000 characters. If your Asana task notes are long and you pass them raw, Slack drops the tail with no error — your messages just look truncated. Always cap notes at 200 characters in the Code node before sending.
Ideas for what to build next
- →Add a daily digest summary — Instead of (or alongside) per-milestone alerts, add a second workflow on a 9am daily schedule that fetches all milestones due this week and posts a single summarized digest to a leadership Slack channel — one message instead of 10 individual pings.
- →Route alerts by team using Asana custom fields — If your Asana projects use a custom 'Team' or 'Squad' field, update the Code node to read that field and extend the PROJECT_CHANNEL_MAP to route messages to squad-specific Slack channels like #squad-payments or #squad-growth automatically.
- →Log milestones to a Google Sheet for reporting — After the Slack node, add a Google Sheets node that appends each posted milestone to a tracking sheet with columns: Project, Milestone, Completed Date, Assignee. Gives you a permanent audit trail and makes it easy to build a monthly milestone completion report.
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