Send a weekly Slack report on HubSpot sequence performance using n8n
Install this workflow
Download the n8n workflow JSON and import it into your n8n instance.
sequence-report.n8n.jsonPrerequisites
- n8n instance — either n8n cloud or self-hosted
- HubSpot Sales Hub Professional or Enterprise (required for Sequences API access)
- HubSpot private app token with
crm.objects.contacts.readandsales-email-readscopes - Slack workspace with a Slack app configured (Bot Token Scopes:
chat:write,chat:write.public) - n8n credentials set up for both HubSpot and Slack
The HubSpot Sequences API is only available with Sales Hub Professional or Enterprise. Starter and free plans don't have API access to sequence data, even if sequences are available in the UI.
Why n8n?
n8n is the best choice for sequence reporting if you want a visual workflow that handles the per-sequence loop cleanly. The Split In Batches node iterates over sequences, and the Code node aggregates enrollment data into a ranked report. Self-hosted n8n runs this for free, and the visual editor makes it easy to add threshold alerts or week-over-week comparisons later.
How it works
- Schedule Trigger fires weekly on Monday morning
- HTTP Request node fetches all sequences from HubSpot's CRM v3 objects API
- Split In Batches node iterates over each sequence
- HTTP Request node (inside loop) searches
sequence_enrollmentsfor the last 7 days per sequence - Code node aggregates enrollment data, calculates open/reply/meeting rates, and ranks sequences
- Slack node posts a Block Kit message with per-sequence breakdown and aggregate totals
Step 1: Create the workflow and schedule trigger
Open n8n and create a new workflow. Add a Schedule Trigger node:
- Trigger interval: Weeks
- Day of week: Monday
- Hour: 9
- Minute: 0
- Timezone: Set to your team's timezone
Step 2: Fetch all sequences
Add an HTTP Request node to list your sequences:
- Method: GET
- URL:
https://api.hubapi.com/crm/v3/objects/sequences - Authentication: Predefined Credential Type -> HubSpot API
- Query params:
limit=100
This returns each sequence's id, and name (via properties).
Starting in 2024, HubSpot sequences are accessible via the CRM v3 objects API using the object type sequences. Older guides may reference a legacy /sequences/v2/ endpoint that has been deprecated.
Step 3: Fetch enrollment data for each sequence
Add an HTTP Request node inside a loop to get enrollments per sequence. First, add a Split In Batches node to iterate over sequences, then:
- Method: POST
- URL:
https://api.hubapi.com/crm/v3/objects/sequence_enrollments/search - Body:
{
"filterGroups": [
{
"filters": [
{
"propertyName": "hs_sequence_id",
"operator": "EQ",
"value": "{{$json.id}}"
},
{
"propertyName": "hs_enrollment_start_date",
"operator": "GTE",
"value": "{{$node['Set Dates'].json.seven_days_ago_ms}}"
}
]
}
],
"properties": [
"hs_sequence_id", "hs_enrollment_state",
"hs_was_email_opened", "hs_was_email_replied",
"hs_was_meeting_booked", "hs_enrollment_start_date"
],
"limit": 100
}The HubSpot Search API allows 5 requests per second. If you have many sequences, add a Wait node (1 second) inside the loop to stay within limits.
Step 4: Calculate metrics with a Code node
After the loop completes, add a Code node (Run Once for All Items) to aggregate enrollment data:
const sequences = $('Fetch Sequences').first().json.results || [];
const enrollmentBatches = $('Fetch Enrollments').all();
// Build sequence name lookup
const seqMap = {};
for (const seq of sequences) {
seqMap[seq.id] = seq.properties?.hs_sequence_name || `Sequence ${seq.id}`;
}
// Aggregate per sequence
const metrics = {};
for (const batch of enrollmentBatches) {
const enrollments = batch.json.results || [];
for (const e of enrollments) {
const seqId = e.properties.hs_sequence_id;
if (!metrics[seqId]) {
metrics[seqId] = {
name: seqMap[seqId] || `Sequence ${seqId}`,
enrolled: 0,
opened: 0,
replied: 0,
meetingsBooked: 0,
};
}
metrics[seqId].enrolled++;
if (e.properties.hs_was_email_opened === 'true') metrics[seqId].opened++;
if (e.properties.hs_was_email_replied === 'true') metrics[seqId].replied++;
if (e.properties.hs_was_meeting_booked === 'true') metrics[seqId].meetingsBooked++;
}
}
// Calculate rates and format
const report = Object.values(metrics)
.filter(m => m.enrolled > 0)
.sort((a, b) => b.enrolled - a.enrolled)
.map(m => ({
...m,
openRate: (m.opened / m.enrolled * 100).toFixed(1),
replyRate: (m.replied / m.enrolled * 100).toFixed(1),
meetingRate: (m.meetingsBooked / m.enrolled * 100).toFixed(1),
}));
const totalEnrolled = report.reduce((s, r) => s + r.enrolled, 0);
const totalReplied = report.reduce((s, r) => s + r.replied, 0);
const totalMeetings = report.reduce((s, r) => s + r.meetingsBooked, 0);
return [{
json: { report, totalEnrolled, totalReplied, totalMeetings }
}];Step 5: Format and send to Slack
Add a Slack node:
- Resource: Message
- Operation: Send a Message
- Channel:
#sales-reports - Message Type: Block Kit
{
"blocks": [
{
"type": "header",
"text": {
"type": "plain_text",
"text": "📧 Weekly Sequence Performance Report"
}
},
{
"type": "section",
"fields": [
{
"type": "mrkdwn",
"text": "*Total Enrolled*\n{{ $json.totalEnrolled }}"
},
{
"type": "mrkdwn",
"text": "*Total Replies*\n{{ $json.totalReplied }}"
},
{
"type": "mrkdwn",
"text": "*Meetings Booked*\n{{ $json.totalMeetings }}"
}
]
},
{
"type": "divider"
},
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": "*Per-Sequence Breakdown*\n{{ $json.report.map(r => `*${r.name}* (${r.enrolled} enrolled)\n Open: ${r.openRate}% | Reply: ${r.replyRate}% | Meeting: ${r.meetingRate}%`).join('\\n\\n') }}"
}
},
{
"type": "context",
"elements": [
{
"type": "mrkdwn",
"text": "Last 7 days | Generated {{ new Date().toLocaleDateString('en-US', { weekday: 'long', month: 'long', day: 'numeric', year: 'numeric' }) }}"
}
]
}
]
}Section text has a 3,000-character max. If you have many sequences, truncate the list to the top 10 and add a "and N more" note, or split across multiple blocks.
Step 6: Add error handling and activate
- Enable Settings -> Retry On Fail on each HTTP Request node (2 retries, 5-second wait)
- Create an Error Workflow with an Error Trigger that sends a Slack DM on failure
- Click Execute Workflow to test
- Toggle the workflow to Active
Troubleshooting
Common questions
How many n8n executions does this use per month?
One execution per week = ~4 executions/month. The entire loop (fetching enrollments per sequence) runs within a single execution. On n8n Cloud Starter ($24/mo, 2,500 executions), this uses less than 0.2% of your quota.
Does the report include archived or paused sequences?
By default, the Fetch Sequences request returns active sequences only. Archived sequences are excluded from the CRM v3 objects API unless you explicitly filter for them. Paused sequences may still appear but will likely have zero enrollments in the lookback window.
Can I flag underperforming sequences automatically?
Yes. Add an IF node after the Code node that checks for sequences with reply rates below a threshold (e.g., 5%). Route flagged sequences to a separate Slack notification or add a warning emoji to those entries in the report.
What if a sequence has more than 100 enrollments in a week?
Add pagination inside the enrollment search loop — check for paging.next.after in the response and make additional requests. At high-volume outbound teams (1000+ enrollments/week), this is essential.
Cost
- n8n Cloud Starter: $24/mo for 2,500 executions. A weekly report with 10 sequences uses ~1 execution (the loop runs within a single execution). Self-hosted n8n is free.
- Maintenance: monitor for new sequences in HubSpot. The workflow fetches all sequences dynamically, so new ones appear automatically.
Next steps
- Step-level analytics — extend the enrollment query to include step-level data for a more granular view of which email steps perform best
- Week-over-week comparison — store last week's metrics in n8n static data and show trend arrows
- Threshold alerts — add an IF node to flag sequences with reply rates below 5% and tag them with a warning emoji
Looking to scale your AI operations?
We build and optimize automation systems for mid-market businesses. Let's discuss the right approach for your team.