Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 10 additions & 5 deletions claude-plugins/sent/skills/messaging-performance-analyzer/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,21 +48,23 @@ Capture these dimensions before calculating anything: profile or sender identity

### 2. Build cohorts from Sent message IDs

Use Sent `message_id` as the primary unit. A v3 send can create separate messages for each recipient and channel pair when multiple channels are specified. Count each Sent message once at its latest status, then add recipient-level or campaign-level rollups only after deduplication.
Use Sent `message_id` as the primary unit. A v3 send can create separate messages for each recipient and channel pair when multiple channels are specified. Count each Sent message once in a terminal-outcome rollup, then add recipient-level or campaign-level rollups only after deduplication.

Distinguish an activity history from a latest-status snapshot. A history can prove the transitions it contains. A snapshot such as `status=DELIVERED` proves only the observed current outcome; it does not prove that the export also observed `QUEUED`, `ROUTED`, or `SENT`. Report unavailable transition denominators as `N/A`, not zero, and never synthesize missing transitions.

Do not use provider IDs such as WhatsApp `wamid`, SMS carrier IDs, or RCS message IDs as the primary join key unless the exported evidence lacks Sent IDs. Provider IDs are useful for escalation, but the Sent API and dashboard track status by Sent message ID.

### 3. Normalize lifecycle stages to Sent’s documented statuses

Use Sent’s documented lifecycle as the first-pass funnel: `QUEUED`, `ROUTED`, `SENT`, `DELIVERED`, and `READ` for WhatsApp and RCS. Keep failed and error states in a separate terminal bucket using the exact status/error fields present in the evidence.
Use Sent’s documented delivery lifecycle as the first-pass funnel: `QUEUED`, `ROUTED`, `SENT`, and `DELIVERED`. Treat `READ` as a separate engagement measure for WhatsApp and RCS, never as an SMS delivery requirement. Keep terminal failures, deferred/in-flight messages, inbound `RECEIVED` messages, and malformed/unknown records in separate buckets using only fields present in the evidence.

| Stage | Interpretation | Common diagnostic question |
|---|---|---|
| `QUEUED` | Sent accepted the request for processing. | Is the backlog growing or did the request never route? |
| `ROUTED` | Sent selected a channel/provider path. | Did routing choose the expected channel or fallback path? |
| `SENT` | The message left Sent/provider processing toward the destination network. | Are provider accepts high but downstream delivery low? |
| `DELIVERED` | Delivery was confirmed where supported. | Did the destination network confirm receipt? |
| `READ` | WhatsApp/RCS read receipt was observed where available. | Did users open the message after delivery? |
| `READ` | WhatsApp/RCS engagement receipt was observed where available. | Did users open the message after delivery? |
| Error/failure | A terminal or recoverable error occurred. | Is the root cause compliance, payload, throughput, opt-out, or provider outage? |

### 4. Check webhook health before diagnosing delivery
Expand All @@ -83,7 +85,7 @@ SMS, WhatsApp, and RCS fail differently. Do not average them together unless the

### 6. Quantify impact before recommending fixes

Report raw counts and rates together. A 40% failure rate over 15 messages is a different decision than a 4% failure rate over 150,000 messages. Include exclusions such as pending messages, test traffic, sandbox sends, retries, and duplicate channel fan-out.
Report raw counts and rates together. A 40% failure rate over 15 messages is a different decision than a 4% failure rate over 150,000 messages. Reconcile the global totals with every channel × direction group, retaining explicit `unknown` groups instead of silently dropping incomplete dimensions. Include exclusions such as pending messages, test traffic, sandbox sends, retries, and duplicate channel fan-out.

A practical analysis table should include: sent count, latest status distribution, failure count, failure-rate delta versus baseline, top exact error strings/codes, first observed timestamp, affected templates, affected countries, and affected profiles.

Expand All @@ -108,7 +110,10 @@ Do not mistake broadcast for fallback. Omitted `channel` or `["sent"]` enables a
- [ ] The analysis uses Sent `message_id` values as the primary unit.
- [ ] The cohort is pinned by time window, profile/sender identity, template, channel, and recipient segment.
- [ ] Status math uses the latest known status per Sent message ID.
- [ ] Transition math uses observed activity histories and never backfills stages from a latest-only status.
- [ ] Pending or in-flight messages are either excluded or reported separately.
- [ ] SMS delivery analysis stops at `DELIVERED`; WhatsApp/RCS `READ` is labeled engagement.
- [ ] Global and channel × direction totals reconcile, including malformed and explicit `unknown` buckets.
- [ ] Webhook configuration, event history, and endpoint test results are checked when the symptom is missing callbacks.
- [ ] Channel-specific failures are split before aggregate rates are reported.
- [ ] Provider or carrier codes are quoted exactly as observed and not invented from a lookup table.
Expand All @@ -132,7 +137,7 @@ Use the `sent` skill for shared Sent terminology and routing.
|---|---|---|
| `references/mdr-status-codes.md` | Lookup table | Normalize observed SMS, WhatsApp, and RCS provider errors without putting long code dictionaries in the skill body. |
| `references/performance-diagnosis-playbook.md` | Worked examples | Decision tree for which signal to investigate first, channel-specific diagnostic patterns, cross-skill handoff matrix, and escalation criteria. |
| `scripts/analyze_mdr_funnel.py` | Validation script | Reads an MDR export (CSV or JSON), prints per-stage counts and drop-off percentages, exits non-zero on anomalies. Run from the skill root: `python scripts/analyze_mdr_funnel.py path/to/mdr.csv` (use `--threshold N` to tune, default 20; pass `--show-errors` to also tally `ERR_*` codes parsed from FAILED message `description` fields). |
| `scripts/analyze_mdr_funnel.py` | Validation script | Reads an MDR export (CSV or JSON), groups channel × direction outcomes, separates delivery transitions from engagement, and retains malformed/unknown rows. Run from the skill root: `python scripts/analyze_mdr_funnel.py path/to/mdr.csv` (use `--threshold N`, `--show-errors`, or `--format json`). Exit `0` means no observed transition breach, `2` means bad input/no usable cohort, and `3` means an observed breach. JSON uses `null` where a denominator is unavailable; text uses `N/A`. |
| `scripts/fixtures/good.json` | Fixture | Synthetic healthy-funnel MDR export. |
| `scripts/fixtures/bad.json` | Fixture | Synthetic MDR export with deliberate >50% SENT→DELIVERED drop. |

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ Authoritative upstream sources (for the downstream provider codes that may appea

## Message status lifecycle

Sent normalizes all channels into a single state machine. Only one terminal at a time; only the **latest** status per `message_id` is meaningful when computing funnel counts.
Sent normalizes channels into a shared delivery state machine. The latest status per `message_id` determines its current outcome, while an activity history supplies the evidence for transition counts. Do not backfill earlier stages from a latest-only record.

```
QUEUED -> ROUTED -> SENT -> DELIVERED -> READ (WhatsApp & RCS only)
Expand All @@ -27,7 +27,7 @@ FAILED FAILED FAILED FAILED
| `FAILED` | Terminal failure; the per-message reason is in the `description` field. |
| `RECEIVED` | Inbound message from end user. |

A message can transition `SENT -> DELIVERED -> FAILED` (e.g. expired WhatsApp window, capability lost on RCS); count the latest status, not the journey.
A message can transition `SENT -> DELIVERED -> FAILED`; classify its terminal outcome from the final observed event while retaining the actual journey for transition analysis. `READ` is WhatsApp/RCS engagement, not an SMS delivery stage.

## Synchronous errors (HTTP response body)

Expand Down Expand Up @@ -207,7 +207,10 @@ Carriers don't share an enum; the categories you actually need to triage on:
## Counting rules

- **Use Sent `message_id`** as the primary unit. Provider IDs (carrier message IDs, `wamid`, RBM `messageId`) are useful for escalation but are **not** in the v3 docs as join keys.
- **Use the latest status** (`max(timestamp)`) — a `FAILED` after `DELIVERED` means `FAILED`; a `READ` after `DELIVERED` means `READ`.
- **Exclude pending** (`QUEUED`/`ROUTED`/`SENT` with no terminal status after the analysis window closes) from rate denominators — they're indeterminate.
- **Use the latest observed event for the outcome** — a `FAILED` after `DELIVERED` is a terminal failure; a `READ` after `DELIVERED` is a delivered message with observed engagement.
- **Use only explicit history for transitions.** A latest-only `DELIVERED` record does not prove the export observed `QUEUED`, `ROUTED`, or `SENT`; render those transition denominators as unavailable.
- **Separate pending/deferred records** (`QUEUED`/`ROUTED`/`SENT` with no terminal status after the analysis window closes) from terminal rate denominators.
- **Group by channel and direction.** Keep missing or unsupported dimensions in `unknown` buckets so totals reconcile instead of silently excluding them.
- **Stop SMS at `DELIVERED`.** Calculate `READ` engagement only for WhatsApp and RCS.
- **Separate channel fan-out.** `POST /v3/messages` with `"channel": ["sms","whatsapp","rcs"]` creates one message per channel; each has its own `message_id` and its own lifecycle. Don't double-count at the recipient level unless the user explicitly asks for recipient-level rollup.
- **Honor a minimum cohort size** before drawing conclusions about small rate shifts. A working heuristic is ≥1,000 messages per cohort; below that, noise dominates. This is an analyst rule of thumb, not a Sent API rule.
Original file line number Diff line number Diff line change
Expand Up @@ -129,9 +129,11 @@ When escalating, include: account / profile ID, channel, cohort definition (temp
Repeat until the symptom is explained or scoped:

1. Pin the cohort (channel × template × country × profile × window).
2. Compute the funnel; identify the broken lifecycle stage (`QUEUED`/`ROUTED`/`SENT`/`DELIVERED`/`READ`).
3. If the gate is between `QUEUED` and `SENT`: check synchronous codes on recent request envelopes.
4. If the gate is at `FAILED` after `SENT`: fetch a sample of failed message IDs, read `description` for `ERR_*` codes.
5. If the symptom is missing customer-side data: prove webhook health via `is_active`, `consecutive_failures`, and `/v3/webhooks/{id}/events` before blaming delivery.
6. Hand off via the matrix above, or escalate to Sent support with the required evidence.
7. Quantify the diagnosis — never "looks better now" without a recomputed funnel.
2. Split channel × direction groups, reconcile them to the input total, and separate progression, terminal failure, deferred, inbound, and malformed/unknown outcomes.
3. Compute delivery transitions only from explicit activity histories (`QUEUED`/`ROUTED`/`SENT`/`DELIVERED`). If the export contains latest-only rows, report their outcomes without inventing prior transitions.
4. For WhatsApp/RCS, report `READ` separately as engagement. Stop SMS delivery analysis at `DELIVERED`.
5. If the gate is between `QUEUED` and `SENT`: check synchronous codes on recent request envelopes.
6. If the gate is at `FAILED` after `SENT`: fetch a sample of failed message IDs, read `description` for `ERR_*` codes.
7. If the symptom is missing customer-side data: prove webhook health via `is_active`, `consecutive_failures`, and `/v3/webhooks/{id}/events` before blaming delivery.
8. Hand off via the matrix above, or escalate to Sent support with the required evidence.
9. Quantify the diagnosis — never "looks better now" without a recomputed funnel.
Loading