Issue 41 brought 38 sign-ups and $412
Issue 41 brought 38 sign-ups and $412 in its 7 days, more than the other four sends together (31 and $261). It had the lowest open rate of the five, so open rate predicted neither.
Every Monday: each recent email's sessions, sign-ups and sales in the 7 days after it went out, joined on its utm_campaign, next to the open rate.
Reads
You get
The card on the right is a sample. Yours shows your own numbers and updates in place every run.
What lands on your board
Issue 41 brought 38 sign-ups and $412 in its 7 days, more than the other four sends together (31 and $261). It had the lowest open rate of the five, so open rate predicted neither.
Email tools rank sends by open rate, and open rate is mostly Apple Mail opening everything on arrival. What a creator or a marketer actually wants to know is which issue brought people to the site who then signed up or bought, and the email tool cannot see that.
Every Monday the agent takes each send whose 7-day window has closed, follows its utm_campaign into GA4 for sessions and sign-ups, and into Stripe or Gumroad for sales, with a buyer-email match against the people who clicked as a fallback. The card ranks sends by what they earned and shows how little the open rate had to do with it.
From whichever email tool you use, take sends from the last 5 weeks whose 7-day window has closed. Mailchimp: GET /campaigns?status=sent&since_send_time=..., with tracking.google_analytics as the utm_campaign and GET /reports/{campaign_id} for open and click rate. Klaviyo: GET /api/campaigns (filter on messages.channel = email) and read the UTM values from the campaign's tracking settings. beehiiv: GET /publications/{publicationId}/posts?expand[]=stats; its automatic tagging uses a URL-friendly post title as utm_campaign.
Call runReport with dimensions: ["sessionCampaignName", "sessionMedium"], metrics sessions and engagedSessions, a filter sessionMedium matching email (case-insensitive), and the 5-week range; run it again with eventCount and a filter eventName == "sign_up" (or your sign-up key event). Read each send's 7 days only by adding the date dimension.
Stripe: Checkout Sessions with status=complete in the range, with metadata.utm_campaign, amount_total and customer_details.email. Gumroad: GET /v2/sales with after and before, keeping each sale's price, buyer email and referrer.
Normalize campaign values (lowercase, URL-decode, trim) and match sessions and sales to the send whose value equals them, only within 7 days of the send. Sales with no UTM fall back to buyer email: match it, lowercased, against who clicked that send (Mailchimp GET /reports/{campaign_id}/email-activity, Klaviyo Clicked Email events), and give the sale to the latest send they clicked. Every sale counts once; the rest stay "not from email".
Rank sends by revenue, then sign-ups. Put open rate beside each so the card shows whether it predicted anything, and name the send that earned most and what was different about it (subject, offer, link position).
With BRIEFKIN_TOKEN set, the agent sends the result as one card that updates in place every run. Without it, you get the report in the chat.
Pick your agent, where the data comes from and how to schedule it; the commands below follow.
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.
shellcurl -fsSL https://briefkin.dev/skills/mailchimp-email-to-revenue/install | shAsk Claude Code:
promptRun the mailchimp-email-to-revenue skillOr paste the prompt instead of installing:
promptTake my email sends from the last 5 weeks whose 7 days have passed (Mailchimp, Klaviyo or beehiiv) with their open rate and utm_campaign value. From GA4 runReport, get sessions and sign_up events by sessionCampaignName with sessionMedium email, in each send's 7 days. From Stripe Checkout Sessions (metadata.utm_campaign) or Gumroad sales, total the revenue per campaign in the same 7 days; for sales with no UTM, match the buyer email to people who clicked the send and credit the latest one. Normalize campaign values (lowercase, URL-decode). Give me a table of sends with open rate, sessions, sign-ups and revenue ranked by revenue, say how many sales matched by UTM and by email, and tell me which send to learn from and why.Mondays at 9:30, from cron on any machine where Claude Code is signed in:
crontab30 9 * * 1 claude -p "Run the mailchimp-email-to-revenue 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.
Connect Briefkin once with curl -fsSL https://briefkin.dev/install | sh and every run lands on your board as a report card. How setup works
SKILL.md---
name: mailchimp-email-to-revenue
description: "Every Monday: each recent email's sessions, sign-ups and sales in the 7 days after it went out, joined on its utm_campaign, next to the open rate. Use when the user asks for this, or when it runs on a schedule."
---
# Which emails made money: Mailchimp, Klaviyo or Beehiiv, analytics and sales
Every Monday: each recent email's sessions, sign-ups and sales in the 7 days after it went out, joined on its utm_campaign, next to the open rate.
Email tools rank sends by open rate, and open rate is mostly Apple Mail opening everything on arrival. What a creator or a marketer actually wants to know is which issue brought people to the site who then signed up or bought, and the email tool cannot see that.
Every Monday the agent takes each send whose 7-day window has closed, follows its `utm_campaign` into GA4 for sessions and sign-ups, and into Stripe or Gumroad for sales, with a buyer-email match against the people who clicked as a fallback. The card ranks sends by what they earned and shows how little the open rate had to do with it.
## 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
- UTM tagging on in the email tool (Mailchimp Google Analytics link tracking, Klaviyo UTM tracking, beehiiv Automatic UTM Tagging), and a checkout that stores the UTM in Stripe Checkout Session metadata if you sell through Stripe.
- A GA4 sign-up event (marked as a key event helps), Viewer on the GA4 property, and read access to the email tool and to Stripe or Gumroad (a restricted key, or `view_sales` on Gumroad).
If something is missing, say what and stop; do not invent numbers.
### Steps
1. **List the sends and their UTM value** (Mailchimp): From whichever email tool you use, take sends from the last 5 weeks whose 7-day window has closed. Mailchimp: `GET /campaigns?status=sent&since_send_time=...`, with `tracking.google_analytics` as the utm_campaign and `GET /reports/{campaign_id}` for open and click rate. Klaviyo: `GET /api/campaigns` (filter on `messages.channel` = email) and read the UTM values from the campaign's tracking settings. beehiiv: `GET /publications/{publicationId}/posts?expand[]=stats`; its automatic tagging uses a URL-friendly post title as utm_campaign.
2. **Get sessions and sign-ups per campaign** (Google Analytics): Call `runReport` with `dimensions: ["sessionCampaignName", "sessionMedium"]`, metrics `sessions` and `engagedSessions`, a filter `sessionMedium` matching `email` (case-insensitive), and the 5-week range; run it again with `eventCount` and a filter `eventName == "sign_up"` (or your sign-up key event). Read each send's 7 days only by adding the `date` dimension.
3. **Collect sales in each window** (Stripe): Stripe: Checkout Sessions with `status=complete` in the range, with `metadata.utm_campaign`, `amount_total` and `customer_details.email`. Gumroad: `GET /v2/sales` with `after` and `before`, keeping each sale's price, buyer email and `referrer`.
4. **Join on utm_campaign, then on the clicker's email**: Normalize campaign values (lowercase, URL-decode, trim) and match sessions and sales to the send whose value equals them, only within 7 days of the send. Sales with no UTM fall back to buyer email: match it, lowercased, against who clicked that send (Mailchimp `GET /reports/{campaign_id}/email-activity`, Klaviyo `Clicked Email` events), and give the sale to the latest send they clicked. Every sale counts once; the rest stay "not from email".
5. **Rank and post**: Rank sends by revenue, then sign-ups. Put open rate beside each so the card shows whether it predicted anything, and name the send that earned most and what was different about it (subject, offer, link position).
## Route B: the browser
### What you need
- Signed in to the email tool, to GA4 with Viewer access on the property, and to Stripe or Gumroad with access to sales.
If something is missing, say what and stop; do not invent numbers.
### Steps
Start at https://login.mailchimp.com/. The user is signed in already; never type passwords or codes, and ask if a sign-in page appears.
1. **List the sends**: In Mailchimp open **Analytics > Reports** (in Klaviyo, **Campaigns**; in beehiiv, **Posts**) and note each send from the last 5 weeks whose 7 days are over: name, send date and open rate. Open one report and click a tracked link to read the `utm_campaign` value the tool adds.
2. **Read sessions and sign-ups in GA4**: Open `https://analytics.google.com/`, then **Explore > Free form**. Add the dimensions **Session campaign** and **Session medium**, the metric **Sessions**, a filter Session medium contains `email`, and each send's 7 days as the date range. Read sessions, then add the metric **Event count** and a filter Event name exactly `sign_up` and read sign-ups (the event filter changes what Sessions counts).
3. **Read sales in each window**: In Stripe open **Payments**, filter by date and read the `utm_campaign` metadata your checkout puts on each payment. On Gumroad open **Sales**, filter by date, and export the CSV to see each buyer's email and referrer.
4. **Join and rank**: Match each send's UTM value to the GA4 rows and sales (lowercased), only in the 7 days after the send. For sales without a UTM, check whether the buyer appears in the send's click report. Rank the sends by revenue and put the open rate next to each.
Note: GA4 explorations sample large date ranges on standard properties, so keep each range to one send's 7 days. The clicker-email fallback is slow by hand: a few sales are fine, dozens are a job for the API route. beehiiv shows clicks per link but not who clicked through the UI on every plan.
## The report
- **Best send** in the last 5 weeks: sessions, sign-ups and revenue in its 7 days.
- **Every send**: open rate, sessions, sign-ups, revenue, ranked by revenue.
- **How the sales were matched**: by UTM or by clicker email.
- **Not from email**: sales in the period that no send can claim.
- The one thing to repeat, for example "Put the offer link first, as Issue 41 did".
## 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": "mailchimp-email-to-revenue/<date of this run, e.g. 2026-10-16>",
"item_key": "mailchimp-email-to-revenue",
"project": "example.com",
"source": "Which emails made money: Mailchimp, Klaviyo or Beehiiv, analytics and sales skill",
"kind": "update",
"title": "Issue 41 brought 38 sign-ups and $412",
"summary": "Issue 41 brought **38 sign-ups** and **$412** in its 7 days, more than the other four sends together (31 and $261). It had the lowest open rate of the five, so open rate predicted neither.",
"widget": [
{
"type": "Table",
"props": {
"columns": [
"Send",
"Open rate",
"Sessions",
"Sign-ups",
"Revenue"
],
"align": [
"left",
"right",
"right",
"right",
"right"
],
"rows": [
[
"Issue 41 (Oct 14)",
"31%",
540,
38,
"$412"
],
[
"Issue 39 (Sep 30)",
"44%",
290,
11,
"$116"
],
[
"Issue 38 (Sep 23)",
"46%",
270,
9,
"$87"
],
[
"Issue 40 (Oct 7)",
"49%",
310,
7,
"$58"
],
[
"Pricing update (Oct 9)",
"52%",
260,
4,
"$0"
],
[
"All five",
"",
1670,
69,
"$673"
]
]
}
},
{
"type": "Callout",
"props": {
"text": "Issue 41 opened with the template link instead of an essay. 6 of its 11 sales matched by UTM, 5 by a buyer who had clicked it. $1,940 of sales in the period came from no send.",
"tone": "info"
}
}
]
}
JSON
```
Widgets come from the Briefkin catalog (https://briefkin.dev/v1/catalog). Without a token, just give the user the report.
## Run it on a schedule
Suggested: Mondays at 9:30 (`30 9 * * 1`). From cron, with the agent signed in on that machine:
```bash
30 9 * * 1 claude -p "Run the mailchimp-email-to-revenue 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/mailchimp-email-to-revenue#schedule
Source: https://briefkin.dev/skills/mailchimp-email-to-revenue
UTM tagging on in the email tool (Mailchimp Google Analytics link tracking, Klaviyo UTM tracking, beehiiv Automatic UTM Tagging), and a checkout that stores the UTM in Stripe Checkout Session metadata if you sell through Stripe.
A GA4 sign-up event (marked as a key event helps), Viewer on the GA4 property, and read access to the email tool and to Stripe or Gumroad (a restricted key, or view_sales on Gumroad).
https://ai-inc.mailchimp.com/claude/mcp/v2) is in Claude's connector directory. It plans, edits and saves campaigns as drafts and uses your past performance for its suggestions, but Mailchimp describes it as focused on campaign planning, not on pulling reports, and connecting it takes an Owner or Admin role (help).MAILCHIMP_API_KEY, and read the data center from its suffix (...-us6 means https://us6.api.mailchimp.com/3.0). Send it with HTTP Basic auth (any username, the key as the password) or as a Bearer token. The API allows 10 simultaneous connections, so page through reports one request at a time.API keys can't be limited to read-only: a key acts with the role of the user who created it, so keep it in a secret store, use it only for GET requests, and revoke it when the report is retired.
https://mcp.klaviyo.com/mcp, which signs in with OAuth; it also runs locally with uvx klaviyo-mcp-server@latest and a private key in PRIVATE_API_KEY. In Claude Code, run claude mcp add --transport http klaviyo "https://mcp.klaviyo.com/mcp?read-only=true"; Cursor and Codex take the same URL in their MCP config. Its reporting tools are get_campaign_report and get_flow_report, alongside tools for campaigns, flows, lists, segments, profiles and templates (docs).KLAVIYO_API_KEY, and send Authorization: Klaviyo-API-Key <key>.read-only=true on the MCP URL (or READ_ONLY=true locally) disables every tool that can write to your account, and disable-tools-with-user-generated-content=true keeps subscriber-written content out of the agent's context.
https://mcp.beehiiv.com/mcp. In beehiiv, open Settings > MCP, pick your client (beehiiv has guides for Claude, Claude Code, Cursor and Codex) and grant access when asked. It is available on every plan: free plans are read-only, and paid plans add write tools for content, segments and automations, though it can't publish, schedule or send posts. To connect a second workspace, add a parameter such as ?account=2 to the URL (docs).BEEHIIV_API_KEY with BEEHIIV_PUBLICATION_ID (it starts with pub_), and send it as a Bearer token.The endpoints this report reads need only posts:read, subscriptions:read and publications:read. On a paid plan the MCP connection can edit drafts and segments, so keep it for interactive work and use a read-only key for scheduled runs.
analytics-mcp on PyPI, source). It runs locally with pipx run analytics-mcp, offers run_report, run_realtime_report and get_account_summaries, and Google labels it experimental.GOOGLE_APPLICATION_CREDENTIALS at its JSON key. The skill also reads GA4_PROPERTY_ID.gcloud auth application-default login --scopes https://www.googleapis.com/auth/analytics.readonly,https://www.googleapis.com/auth/cloud-platform --client-id-file=client.json gives the MCP server or a script your user's access without a key file.Request only analytics.readonly and keep the service account at Viewer: none of these reports needs Editor.
https://mcp.stripe.com. Add Stripe from Claude's connector directory, or run claude mcp add --transport http stripe https://mcp.stripe.com/, and sign in with OAuth; clients without OAuth can send a restricted key as a Bearer token.stripe login once, then stripe subscriptions list --status active --limit 100 or stripe invoices list --status open. The CLI defaults to test mode, so add --live for real data.rk_live_...) in the Dashboard under Developers > API keys, store it in STRIPE_API_KEY, and give it Read on the resources the report touches: Subscriptions, Invoices, Charges, Customers, PaymentIntents, Balance and Disputes.Never hand an agent the secret key (sk_live_...); a restricted key with Read where needed and None everywhere else can't refund, cancel or charge anything.
https://gumroad.com/claude/v1/mcp as a custom connector (Gumroad isn't in Claude's directory yet) and sign in to Gumroad; other clients that support OAuth with PKCE can use the same URL. Its tools list products, sales and payouts, and can create, publish and unpublish product drafts (details).GUMROAD_ACCESS_TOKEN and pass it as the access_token parameter. An OAuth app can ask for just the view_sales scope.If your client lets you choose OAuth scopes for the MCP server, keep view_sales and view_payouts and leave out edit_products unless you want drafts made from chat. Disconnecting in the chat app doesn't revoke the token: do that under Authorized Applications in Gumroad's settings.
Most clicks on a newsletter happen within two days and a purchase follows within a week, so 7 days catches nearly all of it without overlapping the next weekly send too much. Klaviyo and Mailchimp report their own attributed revenue, but each uses its own window and Klaviyo can credit opens, which Apple Mail inflates. The skill counts only sessions and sales that carry the send's UTM or come from someone who clicked it.
That is usually a link copied from an old email into a new one, a forwarded email, or a value edited by hand. The agent lists those values under "unmatched email campaigns" with their sessions, so you can map them to a send once or fix the tagging, and never adds them to a send by guesswork.
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 report card, so you can glance at it instead of asking again.
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.
Yes. Switch the data source to "Your browser" and the steps change to the Mailchimp, Klaviyo, beehiiv, Google Analytics, Stripe and Gumroad pages themselves: the agent opens login.mailchimp.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.
Skills are free to use with any agent. Briefkin is where their results land: one board for every report, number and question your agents send, with a ping when something needs you.
Free during the open beta. No card.