| name | nps-feedback-report |
| description | Build NPS feedback rollout/performance reports by combining email-platform send metrics (e.g. Customer.io) with NPS submissions from your data warehouse. Use when the user asks for an NPS report, NPS send delivery/open/click rates, response rate, trailing 30-day NPS score, or comment highlights/patterns from NPS responses.
|
NPS Feedback Report
Create a concise NPS report that joins email-platform delivery/engagement metrics with response data from your data warehouse's NPS flow.
Default reporting window: trailing 30 complete days ending now, unless the user asks for a different range. Keep all date ranges explicit in the report.
Data sources
Customer.io
Use Customer.io for send-side metrics:
- sent
- delivered
- opened / open rate
- clicked / click rate
Configure the workspace/message/campaign identifiers for your own Customer.io
account (keep them in your private config, not in this repo):
- workspace/environment:
<CIO_ENVIRONMENT_ID>
- NPS transactional message:
<CIO_TRANSACTIONAL_MESSAGE_ID>
- NPS campaign id:
<CIO_CAMPAIGN_ID>
Use the Customer.io MCP when available:
- Call
cio_prime first.
- Use
cio_schema before reading endpoints.
- Use
cio_read_api with --jq/jq filters to keep output small.
Useful endpoint patterns:
- Transactional message metrics:
/v1/environments/{environment_id}/transactional_messages/{transactional_message_id}/metrics
- Campaign metrics:
/v1/environments/{environment_id}/campaigns/{campaign_id}/metrics
Important Customer.io metric handling:
- Prefer human-only opens/clicks if Customer.io exposes both human and total metrics. Bot activity can inflate total opens/clicks.
- If only all-time transactional metrics are available from the endpoint, state that limitation and use campaign/action time series, exports, or UI-provided period metrics for the 30-day window.
- Compute:
- delivery rate = delivered / sent
- open rate = opened / delivered
- click rate = clicked / delivered
- click-to-open rate = clicked / opened
- Use
delivered as the denominator for open/click rates unless the user explicitly asks for sent-based rates.
Data warehouse
Always use a cloud subagent in the environment named by $DATA_WAREHOUSE_ENV_ID for warehouse work. Do not try to query the warehouse locally, even if a CLI is installed, because local credentials are often missing or too broad/narrow. The lead agent should give the cloud subagent the reporting window, desired fields, and output contract; the subagent should choose and run the appropriate warehouse queries.
Ask the cloud subagent to retrieve response-side metrics:
- NPS submissions
- scores
- comments
- modeled table refresh status
Tell the cloud subagent to check both the freshest raw event stream and the modeled NPS responses table. It should use raw events when the modeled table lags, and it should explicitly report that lag rather than hiding it.
The cloud subagent should return a compact JSON or markdown summary with:
- reporting window used
- raw source queried and whether it was used for final metrics
- modeled source queried and freshness status
- total submissions
- score counts by score 0-10
- promoter/passive/detractor counts
- number of non-empty comments
- representative comments with scores
- any caveats about model lag, missing fields, or suspicious duplicates
When asking the cloud subagent, phrase the task in terms of what to retrieve, not the exact query to run. Example:
Query the data warehouse in the $DATA_WAREHOUSE_ENV_ID environment for NPS responses from <start> to <end>. Use the freshest reliable source, checking the raw event stream and the modeled NPS response table. Return total submissions, score distribution, promoter/passive/detractor counts, non-empty comment count, representative comments with scores, source freshness, and caveats. Do not print secrets or unrelated PII.
Metrics to compute
Use the same reporting window for all metrics whenever possible.
Send-side
From Customer.io:
sent
delivered
opened
clicked
delivery_rate = delivered / sent
open_rate = opened / delivered
click_rate = clicked / delivered
click_to_open_rate = clicked / opened
Round rates to one decimal percentage unless the user asks for more precision.
Response-side
From the warehouse:
submitted = count(responses)
responded = submitted
added_comments = count where comment is non-empty after trimming whitespace
- Do not include response rate in the default report; it is often misleading while Customer.io send metrics and all NPS submissions cover different populations during rollout. Only calculate it if the user explicitly asks and the denominator is comparable.
NPS score
Calculate NPS correctly:
- promoters = count of scores 9 or 10
- passives = count of scores 7 or 8
- detractors = count of scores 0 through 6
- total_responses = promoters + passives + detractors
- NPS =
((promoters / total_responses) - (detractors / total_responses)) * 100
Equivalent:
NPS = ((promoters - detractors) / total_responses) * 100
Round NPS to the nearest integer. If there are zero responses, report NPS as unavailable, not 0.
Always label it as “trailing 30-day NPS” or whatever the actual period is.
Comment analysis
Analyze comments from the same reporting window as the NPS score.
Split highlights by score bucket:
- Promoter comments: scores 9-10
- Passive comments: scores 7-8
- Detractor comments: scores 0-6
Look for:
- repeated product themes
- repeated praise
- repeated pain points
- bugs/regressions
- support or pricing concerns
- enterprise/team collaboration themes
- comments that are specific enough to quote
Be careful:
- Do not overgeneralize from a tiny number of comments.
- Do not include sensitive personal details unless needed.
- Quote short comments only when they illustrate a pattern.
- Separate “high-signal individual quote” from “recurring pattern.”
Notable testimonial quotes
In addition to patterns, select a few notable testimonial quotes to include verbatim in the report. These are individual comments that are vivid, specific, and quotable on their own, independent of whether they represent a recurring pattern.
Selection guidance:
- Prefer 2-4 quotes total. Lead with promoter testimonials, but include a detractor or passive quote when it surfaces an important watchout.
- Quote the comment verbatim; do not paraphrase. Trim only to remove rambling or sensitive details, and use an ellipsis (
…) to mark any trimming.
- Keep each quote short (roughly one to two sentences). Drop quotes that are too long to read at a glance.
- Attribute each quote with its score bucket and score, e.g.
promoter (10) or detractor (3).
- Skip comments that contain names, emails, account identifiers, or other PII you would not want reposted.
- If there are no comments worth quoting, omit the quotes section rather than forcing weak quotes.
Report format
Keep the report short and make the final NPS score the first thing the reader sees. The default output should look like a Slack update, even when shown in the terminal. Do not include separate Summary, Funnel, Supporting metrics, Data freshness/caveats, or Recommended next steps sections unless the user asks. Include a short Quotes section with a few notable testimonials when quotable comments exist; omit it when none do. End the report with your dashboard footer link ($NPS_DASHBOARD_URL) when configured.
Use this structure exactly for normal markdown:
NPS Feedback Report — <date range>
NPS: <score> · <responses> responses · <comments> comments
Delivered <n> · Opened <n> (<open_rate>%) · Clicked <n> (<click_rate>%)
Breakdown: <promoters> promoters · <passives> passives · <detractors> detractors
Good:
- <pattern>
- <pattern>
Bad / watchouts:
- <pattern>
- <pattern>
Quotes:
- "<verbatim comment>" — promoter (<score>)
- "<verbatim comment>" — detractor (<score>)
Dashboard: $NPS_DASHBOARD_URL
For Slack or DM output, use mrkdwn emphasis while preserving the same compact structure:
*NPS Feedback Report — <date range>*
*NPS: <score>* · <responses> responses · <comments> comments
Delivered <n> · Opened <n> (<open_rate>%) · Clicked <n> (<click_rate>%)
*Breakdown:* <promoters> promoters · <passives> passives · <detractors> detractors
*Good:*
- <pattern>
- <pattern>
*Bad / watchouts:*
- <pattern>
- <pattern>
*Quotes:*
- "<verbatim comment>" — promoter (<score>)
- "<verbatim comment>" — detractor (<score>)
<$NPS_DASHBOARD_URL|NPS Feedback dashboard>
If any metric is unavailable, omit it or write unavailable; do not replace missing values with 0.
Optional Slack delivery
Only post to Slack if the user explicitly asks to send/post it, or if the skill is running in a scheduled reporting context. Otherwise, display the report in the conversation.
Default destination:
- the channel ID in
$BUZZ_NPS_SLACK_CHANNEL
Environment variables:
BUZZ_SLACK_TOKEN: Slack bot token with permission to post to the target channel.
BUZZ_NPS_SLACK_CHANNEL: channel ID to post the report to.
NPS_DASHBOARD_URL: optional dashboard link shown in the report footer.
Use chat.postMessage with the exact compact report shape above, formatted as mrkdwn. Keep it under Slack's comfortable reading length; if the report is long, trim patterns rather than adding new sections.
Example posting pattern:
import json
import os
import urllib.request
token = os.environ["BUZZ_SLACK_TOKEN"]
channel = os.environ["BUZZ_NPS_SLACK_CHANNEL"]
text = "<formatted Slack-ready NPS report>"
resp = json.loads(urllib.request.urlopen(urllib.request.Request(
"https://slack.com/api/chat.postMessage",
data=json.dumps({"channel": channel, "text": text, "mrkdwn": True}).encode(),
headers={
"Authorization": f"Bearer {token}",
"Content-Type": "application/json",
},
)).read())
assert resp["ok"], f"chat.postMessage failed: {resp}"
If Slack rejects a channel name, ask for or configure the channel ID in BUZZ_NPS_SLACK_CHANNEL.
Workflow
- Clarify the reporting window if the user did not specify one. Default to trailing 30 days.
- Fetch Customer.io send-side metrics for the same window.
- Spawn a cloud subagent in the
$DATA_WAREHOUSE_ENV_ID environment to retrieve warehouse response-side metrics for the same window.
- Use the cloud subagent's raw/modeled freshness notes to decide whether to report raw, modeled, or both.
- Compute funnel metrics and NPS with the exact formula above.
- Analyze comments by score bucket, identify patterns, and select a few notable testimonial quotes.
- Produce the compact NPS-first report in the exact format above.
- If explicitly requested or scheduled, post the Slack-ready report to
$BUZZ_NPS_SLACK_CHANNEL.
Validation checklist
Before finalizing:
- Date range is explicit.
- NPS uses promoters minus detractors divided by total responses, not an average score.
- Open/click rates use delivered denominator unless otherwise stated.
- Comments and NPS are from the same time period.
- Notable quotes are verbatim, attributed with score bucket and score, and free of PII.
- Raw/model lag is considered internally; include it only if it changes the interpretation or the user asks.
- No secrets, API keys, or unnecessary PII are printed.
- Slack is only posted when requested or scheduled.
- The report ends with the dashboard footer link when
$NPS_DASHBOARD_URL is set.