What it does
Email results in HubSpot live on each email's own page, so comparing this week's sends means opening them one at a time. Open rates have also been unreliable since Apple Mail Privacy Protection began preloading images, which makes the number everyone quotes the least useful one.
The agent collects every marketing email published this week, reads its statistics through the HubSpot API and leads with click rate, the signal that still means a person acted. Opens and unsubscribes are there for context, and an email whose unsubscribe rate spikes gets called out.
How it works
1Find this week's sends
Call
GET /marketing/v3/emails(or the date-versioned path,/marketing/emails/2026-09) withincludeStats=trueandpublishedAfterset to Monday 00:00 in the portal's time zone. Leave out drafts and automated emails unless you ask for them.2Read the counters
From each email's
stats.counters, take sent, delivered, open, click, unsubscribed, bounce and spam report counts. For one total across the week,GET /marketing/v3/emails/statistics/listwithstartTimestampandendTimestampreturns the aggregate and a per-campaign breakdown.- 3
3Work out the rates
Divide by delivered, not sent: open rate, click rate, click-to-open rate and unsubscribe rate. Flag an email whose unsubscribe rate is above 0.5% or twice your 8-week average.
- 4
4Mark the early numbers
An email sent this morning has hours of data and one sent on Monday has days. Mark anything under 48 hours old as early so it is not judged against the rest.
- 5
5Post the tile
The week's click rate is the number, with 8 weeks of history. The glance holds delivered, open rate and unsubscribes; the card opens to one row per email.
Post it to the board
With
BRIEFKIN_TOKENset, the agent sends the result as one card that updates in place every run. Without it, you get the report in the chat.
Install and run it
Pick your agent, where the data comes from and how to schedule it; the commands below follow.
Install the skill
One command. It puts the SKILL.md in ~/.claude/skills and in ~/.agents/skills, which Codex, Cursor, Gemini CLI, Copilot CLI and OpenCode read too.
shell
curl -fsSL https://briefkin.dev/skills/hubspot-marketing-email-report/install | shRun it
Ask Claude Code:
prompt
Run the hubspot-marketing-email-report skillOr paste the prompt instead of installing:
prompt
Using the HubSpot Marketing Emails API (GET /marketing/v3/emails with includeStats=true), find the marketing emails published this week and give me, for each, delivered, open rate, click rate, click-to-open rate and unsubscribes, all as a share of delivered. Mark emails sent less than 48 hours ago as early. Total the week (or use /marketing/v3/emails/statistics/list for the date range) and compare the click rate with the last 8 weeks. Flag unsubscribe rates above 0.5% and bounce rates above 2%. Lead with click rate, since opens are inflated by Apple Mail Privacy Protection, and end with which email did best and why.Put it on a schedule
Fridays at 16:00, from cron on any machine where Claude Code is signed in:
crontab
0 16 * * 5 claude -p "Run the hubspot-marketing-email-report skill"Add it with
crontab -e. A laptop asleep at that time runs it when it wakes if you use launchd or Task Scheduler instead; GitHub Actions works for the API route.Send it to your board
Connect Briefkin once with
curl -fsSL https://briefkin.dev/install | shand every run lands on your board as a stat tile. How setup works
View the SKILL.mdRaw file
SKILL.md---
name: hubspot-marketing-email-report
description: "Every Friday afternoon: opens, clicks and unsubscribes for each HubSpot marketing email sent this week, with click rate as the number to watch. Use when the user asks for this, or when it runs on a schedule."
---
# HubSpot marketing email report
Every Friday afternoon: opens, clicks and unsubscribes for each HubSpot marketing email sent this week, with click rate as the number to watch.
Email results in HubSpot live on each email's own page, so comparing this week's sends means opening them one at a time. Open rates have also been unreliable since Apple Mail Privacy Protection began preloading images, which makes the number everyone quotes the least useful one.
The agent collects every marketing email published this week, reads its statistics through the HubSpot API and leads with click rate, the signal that still means a person acted. Opens and unsubscribes are there for context, and an email whose unsubscribe rate spikes gets called out.
## Two ways to get the data
Use the API or MCP route when you have an MCP server, a CLI or an API token for the app. Otherwise, if you can drive a browser the user is signed in to (Claude in Chrome, a Playwright or Chrome DevTools MCP server), use the browser route. Say which one you used.
## Route A: API or MCP server
### What you need
- Marketing Hub email in your HubSpot account, and a private app token with the `marketing.email.read` scope (`content` also works), or an OAuth app that has it.
- The token in `HUBSPOT_ACCESS_TOKEN`, or a HubSpot MCP server or connector the agent can call.
If something is missing, say what and stop; do not invent numbers.
### Steps
1. **Find this week's sends** (HubSpot): Call `GET /marketing/v3/emails` (or the date-versioned path, `/marketing/emails/2026-09`) with `includeStats=true` and `publishedAfter` set to Monday 00:00 in the portal's time zone. Leave out drafts and automated emails unless you ask for them.
2. **Read the counters** (HubSpot): From each email's `stats.counters`, take sent, delivered, open, click, unsubscribed, bounce and spam report counts. For one total across the week, `GET /marketing/v3/emails/statistics/list` with `startTimestamp` and `endTimestamp` returns the aggregate and a per-campaign breakdown.
3. **Work out the rates**: Divide by delivered, not sent: open rate, click rate, click-to-open rate and unsubscribe rate. Flag an email whose unsubscribe rate is above 0.5% or twice your 8-week average.
4. **Mark the early numbers**: An email sent this morning has hours of data and one sent on Monday has days. Mark anything under 48 hours old as early so it is not judged against the rest.
5. **Post the tile**: The week's click rate is the number, with 8 weeks of history. The glance holds delivered, open rate and unsubscribes; the card opens to one row per email.
## Route B: the browser
### What you need
- Signed in to HubSpot as a user with Marketing email access (Marketing tools, Email, at least View) in a Marketing Hub account.
If something is missing, say what and stop; do not invent numbers.
### Steps
Start at https://app.hubspot.com/. The user is signed in already; never type passwords or codes, and ask if a sign-in page appears.
1. **Open the email Analyze tab**: Open `https://app.hubspot.com/`, pick the account if more than one shows, and go to Marketing, `Email`, then the `Analyze` tab.
2. **Set the week and the type**: Set `Date range` to a custom range from this Monday to today and `Email type` to regular emails only, leaving automated emails out. Read `Recipient engagement` (open rate, click rate) and `Delivery` (delivered, unsubscribes, bounces, spam reports).
3. **Read each email**: Scroll to the `Performance dashboard` table, which lists every email sent in the range with delivered, open rate, click rate and unsubscribe figures. Mark emails sent less than 48 hours ago as early. Rates in HubSpot already divide by delivered.
4. **Add the 8-week trend**: Change the date range to the last 8 weeks and set `Performance over time` to `Click rate` at `Weekly` frequency; read the weekly values for the trend and the average.
5. **Flag and conclude**: Flag any email with an unsubscribe rate above 0.5% or twice the 8-week average, and any bounce rate above 2%. Name the email with the best click rate and what it did differently.
Note: The Analyze tab needs a date range that is not too wide; 8 weeks is fine. For many sends in one week, use Actions, Compare, then Export as a spreadsheet instead of reading the table. Automated emails are filtered out here because they send continuously and would mix old and new recipients.
## The report
- **Click rate** across this week's marketing emails, with an 8-week trend.
- **Delivered, open rate and unsubscribes** for the week.
- **Per email**: delivered, open rate, click rate and unsubscribes, with early sends marked.
- **Flags**: unsubscribe spikes and bounce rates above 2%.
- The best performer and what it did differently, in one line.
## Post it to Briefkin
When `BRIEFKIN_TOKEN` is set, or `~/.config/briefkin/env` exists (the installer from https://briefkin.dev/docs writes it), also send the result to the user's Briefkin board as one card. Keep the `item_key` the same on every run so the card updates in place, use a new `event_id` per run, and put today's real numbers in place of the example ones. If the request fails, say so once and carry on.
```bash
[ -n "${BRIEFKIN_TOKEN:-}" ] || . ~/.config/briefkin/env
curl -sS -X POST "${BRIEFKIN_URL:-https://briefkin.dev}/v1/events" \
-H "authorization: Bearer $BRIEFKIN_TOKEN" \
-H "content-type: application/json" \
-d @- <<'JSON'
{
"v": 1,
"event_id": "hubspot-marketing-email-report/<date of this run, e.g. 2026-10-16>",
"item_key": "hubspot-marketing-email-report",
"project": "Acme",
"source": "HubSpot marketing email report skill",
"kind": "metric",
"title": "Email click rate 3.4% this week",
"metric": {
"label": "Click rate",
"value": 3.4,
"unit": "%",
"better": "higher",
"period_start": "<start of the period, ISO 8601>",
"period_end": "<end of the period, ISO 8601>"
},
"glance": {
"type": "Grid",
"props": {
"columns": 3
},
"children": [
{
"type": "Stat",
"props": {
"label": "Delivered",
"value": 34220
}
},
{
"type": "Stat",
"props": {
"label": "Open rate",
"value": 39.3,
"unit": "%"
}
},
{
"type": "Stat",
"props": {
"label": "Unsubscribed",
"value": 85,
"better": "lower"
}
}
]
},
"widget": [
{
"type": "Table",
"props": {
"columns": [
"Email",
"Delivered",
"Open rate",
"Click rate",
"Unsubs"
],
"align": [
"left",
"right",
"right",
"right",
"right"
],
"rows": [
[
"October product update",
18240,
"41.2%",
"3.1%",
46
],
[
"Webinar invite: closing the books faster",
9860,
"38.5%",
"4.6%",
21
],
[
"Customer story: Acme Bakery (early)",
6120,
"35.0%",
"2.2%",
18
],
[
"Total",
34220,
"39.3%",
"3.4%",
85
]
]
}
},
{
"type": "Callout",
"props": {
"text": "The webinar invite clicked best at 4.6% with a single call to action. The customer story went out yesterday, so its numbers are still early.",
"tone": "info"
}
}
],
"size": "md"
}
JSON
```
Widgets come from the Briefkin catalog (https://briefkin.dev/v1/catalog). Without a token, just give the user the report.
Set `period_start` and `period_end` to the window the number covers: the tile names it from them ("Last 7 days", "Today", "This month"). For a count right now (open items, a balance), send `"period_label": ""` so no period shows; for another name, send it as `period_label` (at most 24 characters).
## Run it on a schedule
Suggested: Fridays at 16:00 (`0 16 * * 5`). From cron, with the agent signed in on that machine:
```bash
0 16 * * 5 claude -p "Run the hubspot-marketing-email-report skill"
# Codex: codex exec, Gemini CLI: gemini -p, Copilot CLI: copilot -p, OpenCode: opencode run
```
Or use the agent's own scheduler (Claude Code routines, Codex automations, Claude or ChatGPT scheduled tasks): https://briefkin.dev/skills/hubspot-marketing-email-report#schedule
Source: https://briefkin.dev/skills/hubspot-marketing-email-report
What your agent needs
Marketing Hub email in your HubSpot account, and a private app token with the
marketing.email.readscope (contentalso works), or an OAuth app that has it.The token in
HUBSPOT_ACCESS_TOKEN, or a HubSpot MCP server or connector the agent can call.
Connecting HubSpot
- HubSpot MCP server, HubSpot's remote server at
https://mcp.hubspot.com. HubSpot also offers a Claude connector built on the same server; you sign in with your HubSpot user and it sees what that user can see (docs). - API with a private app token. Create a private app in your HubSpot account settings, give it only the scopes the report reads (
crm.objects.deals.read,crm.objects.contacts.read,crm.objects.companies.read,crm.objects.owners.read), and store its token inHUBSPOT_ACCESS_TOKEN.
The MCP server and connector can create and update records, so for unattended reports a private app with only .read scopes is the safer credential.
Questions
Why lead with clicks instead of opens?
Since 2021, Apple Mail Privacy Protection loads images for many recipients whether they read the email or not, so opens count machines as well as people. A click still means a person acted, which makes click rate the steadier number from week to week.
Are workflow emails included?
Not by default. Automated emails send continuously, so a weekly snapshot mixes old and new recipients. Ask for them and the agent reports them in a separate section, covering the same 7 days, by email name.
Do I need Briefkin to use the HubSpot marketing email report skill?
No. The skill runs with your own agent and gives you the report in the chat. With a Briefkin token set, each run also posts it to your board as a stat tile, so you can glance at it instead of asking again.
Which agents can run it?
Claude Code, Codex, Cursor, Gemini CLI, GitHub Copilot CLI, OpenCode, Windsurf and Amp all read SKILL.md files. The install command puts it in ~/.claude/skills and ~/.agents/skills, where they look. ChatGPT, the Claude app and Grok take the prompt instead. Pick your agent under "Install and run it" and the commands change to match.
Can it work without an API or MCP server, from my browser?
Yes. Switch the data source to "Your browser" and the steps change to the HubSpot pages themselves: the agent opens app.hubspot.com in a browser you are signed in to and reads the numbers off the page. Claude Code, Codex, Cursor, Claude app and ChatGPT can drive a browser out of the box; Gemini CLI, Copilot CLI and OpenCode need a Playwright or Chrome DevTools MCP server first. It is slower than the API and needs you signed in, so it suits a report you run by hand or from a scheduler on your own computer.