

How to Send Daily Sales Digests from Zoho CRM to Slack with n8n
A scheduled n8n workflow that pulls deal and pipeline data from Zoho CRM every morning and posts a formatted sales summary to one or more Slack channels.
Steps and UI details are based on platform versions at time of writing — check each platform for the latest interface.
Best for
Sales teams of 5-50 people who want a consistent daily snapshot of pipeline health without anyone manually pulling Zoho CRM reports.
Not ideal for
Teams that need real-time deal alerts — use a webhook-based Zoho CRM trigger instead of a scheduled digest.
Sync type
scheduledUse case type
reportingReal-World Example
A 12-person B2B SaaS sales team uses this to post a 7am digest to #sales-daily every weekday. Before automation, the sales manager exported a Zoho CRM report manually each morning, copied numbers into Slack, and the whole process took 15 minutes — and got skipped on busy days. Now the digest appears automatically: deals closed yesterday, pipeline value by stage, and the top 5 open deals by expected close 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 | ||
| Deal Name | Deal_Name | |
| Deal Amount | Amount | |
| Stage | Stage | |
| Closing Date | Closing_Date | |
| Deal Owner | Owner | |
4 optional fields▸ show
| Account Name | Account_Name |
| Probability | Probability |
| Lead Source | Lead_Source |
| Modified Time | Modified_Time |
Step-by-Step Setup
n8n Dashboard > Workflows > + New Workflow
Create a new workflow in n8n
Log into your n8n instance (cloud at app.n8n.cloud or self-hosted). Click the '+ New Workflow' button in the top-right corner of the Workflows list. Give it a clear name like 'Daily Sales Digest — Zoho to Slack'. You'll land on a blank canvas with a default Start node — delete that node, you won't need it here.
- 1Click '+ New Workflow' in the top-right
- 2Click the workflow title at the top and rename it to 'Daily Sales Digest — Zoho to Slack'
- 3Click the default Start node and press Delete to remove it
Canvas > + Node > Schedule Trigger
Add a Schedule trigger node
Click the '+' button in the center of the canvas to open the node picker. Search for 'Schedule' and select the 'Schedule Trigger' node. In the right panel, set 'Trigger Interval' to 'Days', set 'Trigger at Hour' to 7 (for 7am), and set 'Trigger at Minute' to 0. Make sure the timezone matches your team's timezone — n8n defaults to UTC, which will catch most teams off guard.
- 1Click the '+' button on the canvas
- 2Type 'Schedule' in the search box
- 3Select 'Schedule Trigger'
- 4Set 'Trigger Interval' to 'Days'
- 5Set 'Trigger at Hour' to 7 and 'Trigger at Minute' to 0
- 6Open the 'Settings' tab inside the node and set your team's timezone
Canvas > + Node > Zoho CRM > Credentials > Create New
Connect Zoho CRM and authenticate
Click the '+' after the Schedule Trigger to add a new node. Search for 'Zoho CRM' and select it. In the 'Credential' dropdown, click 'Create New Credential'. A popup will ask for your Zoho CRM Data Center region (US, EU, IN, AU, CN) — pick the one matching your Zoho account. Click 'Connect to Zoho CRM', which opens an OAuth popup. Sign into Zoho with an account that has at least read access to Deals and Reports.
- 1Click '+' after the Schedule Trigger node
- 2Search for 'Zoho CRM' and select it
- 3In 'Resource' dropdown, leave it on 'Deal' for now
- 4Click 'Credential' > 'Create New Credential'
- 5Select your Zoho Data Center region from the dropdown
- 6Click 'Connect to Zoho CRM' and complete OAuth in the popup
Zoho CRM Node > Resource: Deal > Operation: Search
Fetch yesterday's closed deals from Zoho CRM
In the Zoho CRM node, set 'Resource' to 'Deal' and 'Operation' to 'Search'. In the 'Criteria' field, enter a COQL query to pull deals where Closing_Date equals yesterday and Stage equals 'Closed Won'. Use the expression editor to build yesterday's date dynamically — this is where n8n's expression syntax earns its keep. This node will return all matching deal records as individual items.
- 1Set 'Resource' to 'Deal'
- 2Set 'Operation' to 'Search'
- 3Enable 'Use COQL Query' toggle
- 4Paste the COQL query: SELECT Deal_Name, Amount, Stage, Owner, Account_Name FROM Deals WHERE Closing_Date = '{{$today.minus({days:1}).format('yyyy-MM-dd')}}' AND Stage = 'Closed Won'
- 5Set 'Max Records' to 200 to capture even high-volume days
Canvas > + Node > Zoho CRM > Resource: Deal > Operation: Get All
Fetch open pipeline deals for today's snapshot
Add a second Zoho CRM node after the first. This one fetches all deals NOT in a closed stage, so the team can see overall pipeline health. Set 'Operation' to 'Get All' and 'Resource' to 'Deal'. Use the 'Fields' option to request only the fields you need: Deal_Name, Amount, Stage, Closing_Date, Owner. Fetching all fields on 500+ deals will slow the workflow down noticeably.
- 1Click '+' after the first Zoho CRM node
- 2Add another 'Zoho CRM' node
- 3Set 'Resource' to 'Deal' and 'Operation' to 'Get All'
- 4Under 'Options', add a filter: Stage not_equal 'Closed Won' AND Stage not_equal 'Closed Lost'
- 5Under 'Fields', specify: Deal_Name, Amount, Stage, Closing_Date, Owner
- 6Set 'Return All' to true if your pipeline has more than 200 deals
Canvas > + Node > Code
Aggregate and calculate pipeline totals with a Code node
Add a 'Code' node after the second Zoho CRM node. This node takes the raw deal arrays and produces a single summary object: total pipeline value, deal count by stage, and the top 5 deals by amount. Without this step, you'd be passing raw JSON to Slack, which is unreadable. The Code node uses JavaScript — see the Pro Tip section below for the full script to paste in.
- 1Click '+' after the second Zoho CRM node
- 2Search for 'Code' and select the 'Code' node
- 3Set 'Mode' to 'Run Once for All Items'
- 4Paste the aggregation script from the Pro Tip section below
- 5Click 'Test Step' to verify the output shows a single summary item
Paste this into the first Code node (set to 'Run Once for All Items'). It reads closed-yesterday deals from the first Zoho CRM node and open-pipeline deals from the second, then produces a single clean summary object. Reference it in the next Code node via $('Aggregate Summary').item.json to build your Slack blocks.
JavaScript — Code Node// Code node: 'Aggregate Summary'▸ Show code
// Code node: 'Aggregate Summary'
// Mode: Run Once for All Items
const closedDeals = $('Zoho CRM - Closed Yesterday').all().map(item => item.json);... expand to see full code
// Code node: 'Aggregate Summary'
// Mode: Run Once for All Items
const closedDeals = $('Zoho CRM - Closed Yesterday').all().map(item => item.json);
const pipelineDeals = $('Zoho CRM - Open Pipeline').all().map(item => item.json);
// Sum closed revenue from yesterday
const closedYesterdayValue = closedDeals.reduce((sum, deal) => {
return sum + parseFloat(deal.Amount || 0);
}, 0);
// Sum total open pipeline value
const totalPipelineValue = pipelineDeals.reduce((sum, deal) => {
return sum + parseFloat(deal.Amount || 0);
}, 0);
// Count deals by stage
const dealCountByStage = {};
for (const deal of pipelineDeals) {
const stage = deal.Stage || 'Unknown';
dealCountByStage[stage] = (dealCountByStage[stage] || 0) + 1;
}
// Top 5 open deals by amount
const topDeals = [...pipelineDeals]
.sort((a, b) => parseFloat(b.Amount || 0) - parseFloat(a.Amount || 0))
.slice(0, 5)
.map(deal => ({
name: (deal.Deal_Name || 'Unnamed').substring(0, 40),
amount: '$' + parseFloat(deal.Amount || 0).toLocaleString('en-US', { maximumFractionDigits: 0 }),
owner: deal.Owner?.name || deal.Owner || 'Unassigned',
closeDate: deal.Closing_Date
? new Date(deal.Closing_Date).toLocaleDateString('en-US', { month: 'short', day: 'numeric' })
: 'No date set',
stage: deal.Stage || 'Unknown'
}));
// Format currency helper
const fmt = (val) => '$' + val.toLocaleString('en-US', { maximumFractionDigits: 0 });
return [{
json: {
closedYesterdayCount: closedDeals.length,
closedYesterdayValue: fmt(closedYesterdayValue),
totalPipelineValue: fmt(totalPipelineValue),
openDealCount: pipelineDeals.length,
dealCountByStage,
topDeals,
reportDate: new Date().toLocaleDateString('en-US', { weekday: 'long', year: 'numeric', month: 'long', day: 'numeric' }),
reportTimestamp: new Date().toISOString()
}
}];
Canvas > + Node > Code
Format the Slack message with a second Code node
Add another 'Code' node after the aggregation node. This one builds the Slack Block Kit JSON that makes the digest look clean — sections, bold headers, deal tables. Slack's Block Kit format requires precise JSON structure. Trying to hand-build this in an 'Expression' field inside the Slack node will drive you insane. Do it here in code and pass the pre-built blocks array to the Slack node.
- 1Click '+' after the aggregation Code node
- 2Add another 'Code' node
- 3Set 'Mode' to 'Run Once for All Items'
- 4Build the blocks array using the summary data from the previous node (reference via $('Code').item.json)
- 5Include a header block, a stats section, a top-deals list, and a footer with the report timestamp
Paste this into the first Code node (set to 'Run Once for All Items'). It reads closed-yesterday deals from the first Zoho CRM node and open-pipeline deals from the second, then produces a single clean summary object. Reference it in the next Code node via $('Aggregate Summary').item.json to build your Slack blocks.
JavaScript — Code Node// Code node: 'Aggregate Summary'▸ Show code
// Code node: 'Aggregate Summary'
// Mode: Run Once for All Items
const closedDeals = $('Zoho CRM - Closed Yesterday').all().map(item => item.json);... expand to see full code
// Code node: 'Aggregate Summary'
// Mode: Run Once for All Items
const closedDeals = $('Zoho CRM - Closed Yesterday').all().map(item => item.json);
const pipelineDeals = $('Zoho CRM - Open Pipeline').all().map(item => item.json);
// Sum closed revenue from yesterday
const closedYesterdayValue = closedDeals.reduce((sum, deal) => {
return sum + parseFloat(deal.Amount || 0);
}, 0);
// Sum total open pipeline value
const totalPipelineValue = pipelineDeals.reduce((sum, deal) => {
return sum + parseFloat(deal.Amount || 0);
}, 0);
// Count deals by stage
const dealCountByStage = {};
for (const deal of pipelineDeals) {
const stage = deal.Stage || 'Unknown';
dealCountByStage[stage] = (dealCountByStage[stage] || 0) + 1;
}
// Top 5 open deals by amount
const topDeals = [...pipelineDeals]
.sort((a, b) => parseFloat(b.Amount || 0) - parseFloat(a.Amount || 0))
.slice(0, 5)
.map(deal => ({
name: (deal.Deal_Name || 'Unnamed').substring(0, 40),
amount: '$' + parseFloat(deal.Amount || 0).toLocaleString('en-US', { maximumFractionDigits: 0 }),
owner: deal.Owner?.name || deal.Owner || 'Unassigned',
closeDate: deal.Closing_Date
? new Date(deal.Closing_Date).toLocaleDateString('en-US', { month: 'short', day: 'numeric' })
: 'No date set',
stage: deal.Stage || 'Unknown'
}));
// Format currency helper
const fmt = (val) => '$' + val.toLocaleString('en-US', { maximumFractionDigits: 0 });
return [{
json: {
closedYesterdayCount: closedDeals.length,
closedYesterdayValue: fmt(closedYesterdayValue),
totalPipelineValue: fmt(totalPipelineValue),
openDealCount: pipelineDeals.length,
dealCountByStage,
topDeals,
reportDate: new Date().toLocaleDateString('en-US', { weekday: 'long', year: 'numeric', month: 'long', day: 'numeric' }),
reportTimestamp: new Date().toISOString()
}
}];
Canvas > + Node > Slack > Credentials > Create New
Connect Slack and authenticate
Add a 'Slack' node after the formatting Code node. In the Credential dropdown, click 'Create New Credential' and choose 'Slack OAuth2 API'. This opens an OAuth flow — click 'Connect to Slack', select your workspace, and grant the bot the scopes it needs. The minimum required scopes are chat:write and channels:read. If you're posting to a private channel, also grant groups:read.
- 1Click '+' after the formatting Code node
- 2Search 'Slack' and select the Slack node
- 3Set 'Resource' to 'Message' and 'Operation' to 'Post'
- 4Click 'Credential' > 'Create New Credential' > 'Slack OAuth2 API'
- 5Click 'Connect to Slack' and authorize in the popup
- 6Confirm scopes include chat:write and channels:read
Slack Node > Resource: Message > Operation: Post
Configure the Slack message post
In the Slack node, set 'Channel' to your target channel (e.g., #sales-daily). For 'Message Type', select 'Blocks'. In the 'Blocks' field, use an expression to reference the blocks array built in the previous Code node: {{ $json.blocks }}. Set 'Text' to a plain-text fallback like 'Daily Sales Digest — see thread for details' — this shows in Slack notifications and for accessibility.
- 1Set 'Channel' to your target Slack channel
- 2Set 'Message Type' to 'Blocks'
- 3In the 'Blocks' field, click the expression toggle and enter: {{ JSON.stringify($json.blocks) }}
- 4Set 'Text' (fallback) to 'Daily Sales Digest'
- 5Under 'Options', set 'Username' to 'Sales Bot' and optionally add a bot icon emoji
channel: {{channel}}
ts: {{ts}}
n8n Workflow Settings > Error Workflow > Select Workflow
Add error handling so failures notify you
Click the three-dot menu on the Slack node and select 'Add Error Workflow' — or add a separate error path. The simplest approach: add an 'Error Trigger' workflow that posts a Slack DM to the sales manager when the main workflow fails. Without this, a broken Zoho CRM token or Slack API timeout will fail silently and nobody gets a digest that morning.
- 1Click the three-dot menu in the top-right of the canvas
- 2Select 'Workflow Settings'
- 3Under 'Error Workflow', click the dropdown and select '+ Create New Error Workflow'
- 4In the new error workflow, add an 'Error Trigger' node and connect it to a Slack node that DMs the sales manager
n8n Canvas > Activate Toggle (top-right)
Activate and verify the live workflow
Click the 'Activate' toggle in the top-right of the canvas. The toggle turns green and the workflow status changes to 'Active'. To verify it works before the next scheduled run, click 'Execute Workflow' manually from the canvas. Check your Slack channel for the digest and review the Executions log in n8n (left sidebar > Executions) to confirm all nodes returned success status.
- 1Click the Activate toggle in the top-right — it should turn green
- 2Click 'Execute Workflow' to run it immediately as a test
- 3Check the target Slack channel for the digest message
- 4Click 'Executions' in the left sidebar and open the latest run to inspect each node's output
Scaling Beyond 500+ open deals in Zoho CRM pipeline+ Records
If your volume exceeds 500+ open deals in Zoho CRM pipeline records, apply these adjustments.
Enable 'Return All' on the pipeline fetch node
By default, Zoho CRM's API returns 200 records per page. If your pipeline has more than 200 open deals, enable 'Return All' in the Zoho CRM node options. n8n will handle pagination automatically, but each additional page is a separate API call — 1,000 deals means 5 calls.
Filter fields to reduce payload size
Fetching all Zoho CRM fields on 500+ records significantly increases execution time and memory use. Specify only the fields you need (Deal_Name, Amount, Stage, Closing_Date, Owner) using the 'Fields' option in the Zoho node. This alone can cut payload size by 70-80%.
Watch Zoho API rate limits at scale
Zoho CRM Standard plan allows 5,000 API calls per day per org. If you're running the digest twice daily plus other Zoho integrations, monitor usage in Zoho's Setup > API Usage dashboard. The open pipeline fetch on a large dataset can consume 5-10 calls per run due to pagination.
Limit the top-deals list to avoid Slack block limits
Slack Block Kit messages have a 50-block maximum per message. If you display more than 10-15 deals in the digest, you risk hitting this limit. Cap the top-deals list at 5-7 entries in the Code node and link to the full Zoho CRM report view for anyone who wants the complete list.
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're self-hosting for data privacy reasons, need to run custom JavaScript to shape the Zoho CRM output, or want zero per-execution cost at high run frequency. The Code node is the real reason to pick n8n here — formatting Zoho's raw deal JSON into a readable Slack Block Kit message requires actual logic (sorting, summing, truncating strings), and doing that in Zapier's Formatter or Make's data mapping UI is painful. The one scenario where you'd skip n8n: if nobody on your team has even basic JavaScript comfort, Make's visual aggregation tools will get you to the same output faster and with less debugging.
n8n cloud costs $20/month for the Starter plan, which includes 2,500 workflow executions. This workflow consumes 1 execution per day — 365/year — which is nowhere near the limit. At the Pro tier ($50/month), you get 10,000 executions. Self-hosted n8n is free with no execution limits, just your server costs. Compare that to Zapier, where this workflow requires a multi-step Zap at $49/month minimum, and you're paying 4x for the same outcome. Make's Core plan at $9/month covers this workflow easily — it's cheaper than n8n cloud if cost is the only factor.
Make has a better visual aggregator for summing deal amounts without code — the Array Aggregator module handles it in a few clicks. Zapier's 'Formatter' step can do basic number formatting but falls apart when you need sorted lists or conditional logic. Power Automate has native Zoho CRM connectors only through third-party premium connectors ($1.50-$2/run), making it expensive for a daily workflow. Pipedream's Node.js environment is equally capable as n8n's Code node but requires managing deployments separately. n8n wins here because the Code node and the Zoho/Slack nodes live in the same canvas, no context switching, and the self-hosted option removes cost entirely for teams with a server already running.
Three things you'll hit after setup. First: Zoho's Owner field comes back as a nested object ({id: '...', name: 'Sarah Chen'}) in some API responses and as a plain string in others, depending on which operation you use. Always use deal.Owner?.name in your Code node and add a fallback. Second: the Slack bot silently fails to post if it's removed from the channel (which happens when channel membership is cleaned up). Add a monitoring step that checks once a week that the bot is still a member. Third: Zoho CRM's API throttles at 5,000 calls/day on Standard plans — if you add a second workflow that also hits the Deals module, you can hit this limit faster than expected. Check Zoho's API usage dashboard weekly during the first month.
Ideas for what to build next
- →Add a weekly pipeline trend comparison — Extend the workflow to run on Mondays and compare this week's pipeline value to last week's. Store last week's total in a Google Sheet or n8n's static data, then calculate the delta and include a trend arrow (↑ or ↓) in the Slack message.
- →Route digests to individual rep channels — After aggregating deal data, add a SplitInBatches node to group deals by Owner and post a personalized digest to each rep's Slack DM — so every salesperson sees only their own pipeline without exposing the full team's numbers.
- →Add stale deal alerts to the digest — In the Code aggregation node, flag any open deals where Modified_Time is more than 7 days ago and surface them in a 'Needs Attention' section at the bottom of the digest. This turns the digest from a passive report into an action list.
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