Skip to main content

Reading the SMS click-through panel

The SMS click-through panel at Insights → SMS click-throughs answers the one question per-link analytics cannot: what is the click-through rate on this campaign or queue, as a channel? Insights → Links tells you who clicked a given short URL; this panel rolls every tracked link up per campaign or messaging queue, so SMS click-through sits next to the email channel’s Clicked metric on equal terms. The SMS click-through page overview covers where the panel lives and what its columns mean. This guide is the reading guide — how to scope it, how to check it against itself, and how to push the leaky numbers toward zero.

1. What the panel shows

For each campaign or queue in the selected window, the table reports seven numbers:
  • Sends — outbound SMS in the window, whether or not they carried a tracked link.
  • Tracked — sends that carried at least one tracked short link. This is the engagement denominator: only tracked sends can produce a click.
  • Clicked — sends whose tracked links recorded at least one click.
  • Clicks — raw clicks across the bucket’s links, including bot and link-preview traffic. This is a volume, not a rate.
  • Verified — clicks that passed the verified-click predicate: the click crosses the verified bar only when its quality score reaches 0.5 — user-agent present, and not a known bot or preview-fetch signature.
  • CTR — clicked sends ÷ tracked sends. A bucket with no tracked sends shows , because the rate is undefined there, not zero.
  • Verified CTR — verified clicks ÷ tracked sends. Use this one when comparing against email’s Clicked metric, which is already bot-filtered.
Because the numerator divides by tracked rather than sends, a mostly-transactional bucket (few links minted) shows a small denominator and a high rate — that is correct, not inflated. Watch the untracked share (sends − tracked) instead: it is the volume that structurally cannot register engagement. Everything on the panel is served by GET /api/v1/analytics/sms-click-through — the same read the API reference documents, so the dashboard can never return a different story than the API.

2. Time window — 24 hours, 7 days, 30 days

The window control at the top of the panel sets the trailing aggregation window for every number on the page: 24h, 7d (the default), or 30d. There is deliberately no 90-day option here; the schema-backed window is exactly these three values. For a longer trend line, pull the API on a schedule or use the wider 1–90 day range on the Links insights surface. The compact summary embedded at the bottom of Messages → SMS messages is a different contract: its window is fixed at 7 days, showing clicks, verified clicks, tracked links, and average clicks per link with a day-by-day sparkline, plus a link through to Insights → Links. It is the quick health glance; the panel on Insights → SMS click-throughs is where you take a 24-hour pulse after a send wave or pull a 30-day read before a monthly review. Both surfaces read the same short-link rollup, so choosing different windows changes scope, never definition — a verification predicate, a denominator rule, an unattributed-bucketing rule behaves identically on every window. Switching the window restarts the table at page 1 (the old page index would slice rows that no longer exist), and the totals strip always covers every bucket in the window, not just the page on screen. Numbers refetch every 60 seconds behind the server-side cache, so a fresh send wave shows up within about a minute without a manual reload.

3. Group by — campaign or queue

Two groupings bucket every send:
  • Campaign — buckets on the campaign_id stamped at send time. This is the cut a marketer wants: one row per campaign, so pre-flight reads (“did the May promo out-click the April promo?”) answer straight from the table.
  • Queue — buckets on the messaging-service id recorded at send time, falling back to the provider tag when no queue resolves. This is the cut an operator wants when triaging routing: a queue bucket with depressed CTR against healthy campaign buckets usually means traffic moved onto a queue whose recipients behave differently — a routing problem, not a creative problem.
Use the campaign cut for pre-flight and creative comparison; use the queue cut for routing and infrastructure triage. If the two cuts disagree in a specific direction, that disagreement is itself the diagnosis — campaign healthy + queue sick means routing moved, campaign sick + queue healthy means creative moved. Sends where the scoped key never resolved — no campaign_id on the campaign grouping, no resolvable queue on the queue grouping — collect in the unattributed bucket (Section 5). Switching the grouping restarts the table at page 1, and buckets always sort by tracked sends descending so measurable rows rank first; column headers are not sortable.

4. Totals strip, bucket table, and reconciliation

The totals strip above the table carries the same five aggregates across all buckets in the window — total sends, sends with tracked links, sends clicked, overall CTR, overall verified CTR. It aggregates across pages, so a 25-per-page bucket table never changes what the strip reports. Check a bucket against itself before acting on it:
If a bucket’s printed rate does not reproduce from clicked ÷ tracked, the stale read is the 60-second cache, not the math — refetch. The deep reconciliation — reproducing the panel’s totals from the raw sends and click sides — is worked end-to-end in the SMS click-through parity guide. The drill-down path for any movement on the table is Insights → Links: scope the per-link surface by campaign_id and the same window, and the rows here decompose into the exact URLs behind the number — which link dragged the rate, and whether the raw-versus-verified gap says it is real engagement loss or preview-fetch noise. Because both surfaces read the same pipeline, the rollup can never disagree with its own drill-down; if the numbers look inconsistent between the two pages, the window or grouping scope is different, not the data.

5. The unattributed bucket — pushing it toward zero

The unattributed row is a catch-all, not an error grouping: it collects sends where the attribution key never resolved. Three causes account for almost all of it:
  1. Un-tracked sender links. A send whose URLs never went through the mint pipeline carries nothing to attribute on. The per-send metadata.shorten_urls opt-in and the tenant SMS/WhatsApp auto-shorten settings control minting; both are covered in short links and click tracking.
  2. Cascade or plain-variant sends. When a campaign runs variants and a fallback plain template (or a cascade hop) goes out without the campaign’s key on the payload, the send lands under unattributed even though a human would call it part of the campaign.
  3. A template missing the attribution token at send time. If the payload never carried campaign_id (campaign grouping) or never resolved a messaging-service id (queue grouping), the bucket forms at send time — no panel setting can re-attribute it after the fact.
The fix is always upstream: stamp campaign_id on the outbound payload, or resolve the queue on the messaging service. The SMS click-through parity guide walks the bucket-fix in detail. Pushing unattributed toward zero is an attribution-hygiene job, and its size on the rollout of a new campaign template is the early-warning signal — a rising unattributed share after a template change means the template stopped stamping its key.

6. API equivalent

Every tile on the page maps to one endpoint — GET /api/v1/analytics/sms-click-through — with group and window taking the same values the panel’s controls expose, plus page and pageSize (default 25, max 100):
cURL
The response returns the paged rows (one object per bucket with bucketKey, sends, tracked_sends, sends_with_clicks, total_clicks, unique_clickers, verified_clicks, click_rate, verified_click_rate), the window-wide totals object, and pagination — under the standard { data, meta } envelope. Rate fields come back null when the bucket carried no tracked sends, and unattributed sends group under a null bucketKey. Responses cache for 60 seconds server-side. For the full request/response schema, worked per-queue and per-campaign examples, and SDK forms, see the SMS click-through API reference.

7. Who can see it

The panel follows the standard Insights hub visibility rule: it renders for every member who can reach the Insights section — owner, admin, developer, and viewer seats all read it — and there is no SMS-click-through-specific role gate. The tile hide on the Insights hub grid is presentation-only; the destination page re-checks the same normalized role server-side, and reading it succeeds for any seat the hub shows it to. The Insights hub orientation page maps each tile to its read gate.

Troubleshooting

  • A bucket shows for CTR. The bucket carried no tracked sends; the rate is undefined there and both the panel and the API return null rather than a fabricated zero. Untracked volume (sends − tracked) is visible per bucket — if that share is high, shorten more of what you send.
  • Unattributed is the biggest row. Stamp campaign_id on campaign sends, or fix queue resolution on the messaging service — both are send-time decisions, not panel settings (Section 5).
  • Raw and verified disagree. Comparing raw CTR to email’s Clicked metric mixes a filtered number with an unfiltered one; line raw against raw elsewhere, or use Verified CTR for parity with email.
  • The Messages → SMS summary shows a flat zero. Its window is fixed at 7 days — clicks older than a week fall out of scope. Open the Insights surface for the 24h/7d/30d window control and per-bucket rows.
  • CTR moved but nothing about the campaign changed. Check delivery health first — CTR’s denominator is tracked sends, not delivered messages, so a delivery dip inside the window depresses the rate before any creative change is suspect. Start from SMS destination blocks for delivery-side symptoms, and the voice channel guide if the movement coincides with an omnichannel wave.

See also