Log in to get this Skill or upvote it.

Onboarding Friction Map

Note from the creator

Why I built it

“I built this to provide me insights into what is causing our new customers <30 days (can customize timeframe) friction, and how we can solve for that.”
Justin Buckley's avatarJustin Buckley· Manager, CX Process & Readiness

What it does

This skill isolates support tickets filed by customers within their first N days after signup and places each one on an ordered onboarding funnel ladder you define. It derives each customer's tenure-at-contact from a registration timestamp, tracks whether they later progressed to a higher stage or stayed stuck, and attributes each block to product, a third-party provider, policy/eligibility, or customer confusion. It ranks stages by stuck users rather than raw volume, cross-tabs funding friction by provider, and is careful to state that support tickets are a friction signal, not funnel drop-off or conversion rates. The result lands as a spreadsheet Product works from plus a self-contained HTML funnel map, using whichever Google Workspace connector is reachable.

How it works

  1. 1

    Reads early-tenure tickets

    Pulls conversations from your workspace within the tenure window you set and resolves the identifier, timestamp and category columns fresh each run.

  2. 2

    Places and scores each ticket

    Derives tenure, assigns each ticket a funnel stage, blocking step and owner, and tracks whether the customer progressed or stayed stuck.

  3. 3

    Delivers map and backlog

    Writes a stage-by-stage sheet and an HTML funnel visual ranking stages by stuck users, with the drop-off caveat on every surface.

How It Looks

See the 2 things this Skill makes, before you download it.

onboarding-friction-map-lilypad-new-pond.html

Onboarding Friction Map — Lilypad new-pond cohort (last 90 days)

Onboarding friction map Built just now
Cohort tickets
312
first 30 days after signup
Unique new ponds
188
across trailing 90 days
Highest-friction stage
Fly-payment setup
by stuck customers
Dropped rows
14
unparseable signup dates
41
58
74
46
33
1. Signup / email verify
2. Pond identity check
3. **Fly-payment setup**
4. First lily-pad claim
5. Membership tier pick
Read this first: Bars show contacts, not drop-off. Support tickets only see ponds that asked for help — a quiet stage may be exactly where ponds silently quit. Stages are ranked by stuck customers, not contact volume.
Stage ladder (rank order)
StageContactsStuckGive-upsTop blocking step
1. Signup / email verify4160Verify link expired
2. Pond identity check58193ID photo rejected
3. Fly-payment setup74315Provider declined card
4. First lily-pad claim46111Pad not showing
5. Membership tier pick3350Tier unclear
Insight cards
  • Fly-payment setup owns the block: 31 stuck ponds, 62% traced to Croakpay provider declines. Vendor conversation. — Medium lift
  • Identity check messaging fails early: 3 ponds gave up before verifying; disclosure timing is the fixable failure. — Structural
  • Verify-link expiry is a quick win: 6 stuck at signup, all in-product-copy-or-error-state. — Quick win

Set aside (not ranked): post-onboarding 27 tickets · unclear 19 tickets. unknown-single-contact = 121 ponds — the largest and genuinely ambiguous bucket, reported separately, never folded in.

Illustrative preview, generated from the generic version of this Skill. The layout is real; the pond-side data is made up.

OnboardingAnalytics & InsightsProduct Insights#onboarding#activation#funnel#kyc#friction#product-insights

The Skill

Skill contents

---
name: onboarding-friction-map
description: "Maps where brand-new customers get stuck during onboarding by isolating support tickets filed within N days of signup and placing each on a fixed, user-defined funnel ladder. Derives tenure-at-contact from a registration timestamp vs. the ticket date, tracks whether each customer progressed or stayed stuck, and attributes blocks to product vs. a third-party provider vs. policy/eligibility vs. customer confusion. Deliberately does NOT claim funnel drop-off or conversion rates -- support tickets only see users who asked for help. Use when the user asks where new users get stuck, about onboarding, signup, or activation friction, verification friction, or first-key-action problems. Trigger on phrasing like \"where do new users fall over\", \"what's blocking activation\", or \"onboarding pain points\". Runs against the Rippit MCP toolchain plus whichever Google Workspace connector is available."
---
# Onboarding Friction Map

Produce a **stage-by-stage map of where new customers get stuck**, addressed
to Product: which onboarding step generates the most friction, what exactly
blocks people there, who owns the block, and what an in-product fix would be
worth.

The cohort is customers in their **first N days after signup** ({{tenure_window_days}}, default 30).
The unit of analysis is a ticket placed on a **fixed funnel ladder** you
define ({{funnel_stages}}), and the outcome signal is whether that customer
later shows up at a *later* rung.

## First run -- make it yours

**Look first, before asking anything.** Call `list_data_sources` and
`describe_table` on the most relevant table. Report what you find: the source
name, the one or two tables that hold customer conversations, and which
columns could satisfy each parameter below -- a customer identifier, a ticket
timestamp, a registration/signup timestamp, an issue category or sub-issue
field, and any provider/payment-channel field. If there is no registration
timestamp column at all, say so plainly now: this skill fundamentally needs a
signup time to derive tenure, so ask the user how to proceed before running
the interview into a dead end.

**Then ask, one question at a time, leading with what you found:**

1. {{tenure_window_days}} -- "How many days after signup counts as a new
   customer? Shorter windows catch pure signup friction, longer ones capture
   the full path to the key action." (default 30)
2. {{analysis_window_days}} -- "How far back should I pull ticket dates?"
   (default trailing 90 days)
3. {{funnel_stages}} -- "The author's version used an ordered eight-stage
   ladder from signup through the first key action to post-onboarding. What
   ordered stages describe your onboarding path?" Capture them as a ranked
   list; the skill's progression logic runs against this rank.
4. {{blocking_parties}} -- "Which owner categories should I attribute each
   block to? The author used product, third-party provider, policy/eligibility,
   and customer confusion."
5. {{scope_filter}} -- "Any scope filter to apply, such as a channel or
   region? Leave blank for all conversations."
6. {{output_destination}} -- name the Google Workspace connector you actually
   found reachable this session and propose it: "Your workspace has a Google
   Sheets connector reachable this session -- write the backlog there and also
   produce the HTML map, or somewhere else?"

Restate the filled-in bindings for confirmation, then run. On later runs,
reuse them unless the user asks to change them.

## What this skill must never claim (read before starting)

**Support tickets are not funnel telemetry.** They see only the customers who
asked for help. Two consequences that have to be stated in every delivery:

1. **Contacts per stage is a friction signal, not a drop-off rate.** A stage
   with heavy contact volume may be one where people ask and then succeed. A
   stage with almost no tickets may be exactly where people quietly quit --
   silence is the most dangerous reading in this whole analysis, and it is
   invisible here.
2. **You cannot size abandonment from this data.** Absence of a later ticket
   is **not** evidence of success or failure. The only defensible abandonment
   signal is what the customer *says* (explicit give-up language) or an
   account-closure/deletion request.

If the user wants true drop-off, say plainly that it needs product analytics
funnel events, which are not in this dataset. **Never let a stage-contact
percentage be presented as a conversion or drop-off number.**

## The funnel ladder ({{funnel_stages}} -- confirm before changing)

Stages are **ordered**, because "did this customer progress?" is only
meaningful against a fixed rank. Use the ranked stages from {{funnel_stages}},
ranks 1..N in the order the user gave. Reserve two non-friction buckets:
`post-onboarding` (counted, then set aside) and `unclear` (tickets the
enrichment can't place). **`unclear` and `post-onboarding` never enter the
stage rankings** -- report their volume so the reader sees what was set aside,
then exclude them.

## Progression, stuck, and unknown (fixed)

For each cohort customer, order their tickets by date and track the maximum
stage rank reached against {{funnel_stages}}:

- **`progressed`** -- a later ticket sits at a strictly higher rank. Strong
  evidence the earlier block was cleared.
- **`stuck`** -- two or more tickets, never exceeding that rank. Strong
  evidence of an unresolved block.
- **`unknown-single-contact`** -- one ticket only. **Usually the largest
  bucket, and genuinely ambiguous** -- report it separately and never fold it
  into either of the others.

Rank stage severity by **`stuck` users**, not raw contact volume, with
explicit give-up language as the strongest single signal.

## Who owns the block ({{blocking_parties}})

Attribution matters because a large share of new-user funding friction can
originate outside the platform. Map each block to one of {{blocking_parties}}
plus `unclear`, and note the fix owner: product blocks -> Product/Engineering;
third-party-provider -> vendor management + Product (surface the error
better); policy-or-eligibility -> Policy/Comms (often unfixable, but the
*messaging* is fixable); customer-error-or-confusion -> in-product guidance/KB.

Where the table carries a provider/payment-channel column, cross-tab funding
friction **by provider** -- "N% of new-user payment friction traces to one
provider" is a vendor conversation and one of the most actionable findings.

`policy-or-eligibility` deserves care: an ineligible customer cannot be fixed
by Product, and filing that as a product defect wastes time. The finding there
is whether we told them **before** they signed up and verified -- a real and
fixable failure.

## Analysis engine

Rippit MCP tools: `list_data_sources`, `describe_table`, `describe_column`,
`aggregate_table`, `read_table`, `create_worksheet`, `enrich_worksheet` +
`get_enrich_status`, `read_conversations`, `get_report_guide`.

Delivery uses whichever Google Workspace connector is reachable **this
session** ({{output_destination}}) -- check the live tool list rather than
reusing names from a prior run. If none is reachable, say so and fall back to
in-chat plus CSVs.

---

## Step 1 -- Confirm scope (light checkpoint)

- **New-customer definition**: {{tenure_window_days}} days since signup.
- **Analysis window** for ticket dates: {{analysis_window_days}}.
- **Scope filter**, if {{scope_filter}} is set.
- Whether to **exclude bot-only conversations** (default: exclude).

## Step 2 -- Find the table and resolve columns fresh

1. `list_data_sources`, then `describe_table`. Read the **Data coverage
   block**: if the window predates the sync floor, say so and analyze only
   covered data.
2. Resolve, never reuse from memory: **customer id**, **ticket created-at**,
   the **registration timestamp** column, **issue category** + sub-issue
   columns, and any **provider/payment-channel** column.
3. **The registration column is the fragile one.** Its type and units vary --
   it may be a DateTime, or an epoch-seconds value, sometimes wrapped in an
   array. Confirm its type and units with `describe_column` before computing
   anything, and take the first non-empty value per row. If it is missing or
   unparseable on a row, that row **cannot be assigned a tenure** and must be
   dropped from the cohort and counted as excluded -- never defaulted to zero,
   which would silently label every such customer brand-new.
4. `aggregate_table` a row count for the window, and compare against the
   **10,000-row worksheet cap** before Step 3.

## Step 3 -- Build the cohort (tenure is derived, not stored)

1. `read_table` the window with a narrow projection -- ticket id, customer id,
   registration timestamp, ticket created-at, issue category, dominant
   sub-issue, provider/channel -- paging via `pagination.nextOffset`. Write to
   a local CSV.
2. Run `scripts/onboarding_friction.py --mode cohort`. It parses both
   timestamps, computes **tenure-days at contact**, keeps rows within
   {{tenure_window_days}}, assigns a **provisional stage** from the coarse
   issue taxonomy against {{funnel_stages}}, and reports how many rows it had
   to drop for unparseable dates.
3. Sanity-check the tenure distribution. A large spike at exactly 0 days, or
   negative tenures, means the units are wrong or the columns are transposed
   -- **fix that before enriching**.
4. If the cohort exceeds 10,000 rows, shorten the window or the tenure
   threshold. **Never sample.**

## Step 4 -- Enrich the cohort (one batched call)

`create_worksheet` filtered to the cohort ticket ids, then `enrich_worksheet`
in a single batched call:

- `funnel_stage` -- `enum` over {{funnel_stages}} plus `unclear`. The
  provisional stage from Step 3 goes in via `contextColumnIds` as a hint the
  model may override; the transcript wins.
- `blocking_step` -- `unspecified_enum`: the specific step the customer could
  not complete, normalized across customers.
- `blocking_party` -- `enum` over {{blocking_parties}} plus `unclear`.
- `third_party_named` -- `string`: the provider named in the conversation, or
  empty. Free text on purpose; providers appear under many spellings.
- `user_outcome_in_conversation` -- `enum`: `resolved-in-thread`,
  `told-to-wait`, `handed-to-another-team`, `no-resolution-given`,
  `user-gave-up-explicitly`, `unclear`. Set a high bar for
  `user-gave-up-explicitly`: the customer says they are done, closing, or
  going elsewhere -- not merely frustrated.
- `self_service_fix` -- `enum`: `in-product-copy-or-error-state`,
  `in-product-guidance-at-step`, `kb-article`, `not-self-serviceable`,
  `unclear`. Bias toward in-product.
- `evidence_quote` -- `string`: the customer's own words describing the block.
  Findings without a quote can't be defended to Product.

Pilot on ~50-75 rows, spot-check the stage assignment against the provisional
one, then run the rest. Poll `get_enrich_status` until `done: true`.

## Step 5 -- Roll up stages, progression, and attribution

1. `read_table` the enriched worksheet in pages to a local CSV
   (`ticket_id,customer_id,ticket_date,tenure_days,funnel_stage,blocking_step,blocking_party,third_party_named,user_outcome_in_conversation,self_service_fix,provider,evidence_quote`).
2. Run `scripts/onboarding_friction.py --mode rollup`. It computes per-stage
   contacts, unique customers, median tenure-days, the
   progressed/stuck/unknown split, give-up counts, attribution mix, provider
   breakdown, and clusters blocking steps within each stage.
3. **Cross-check:** the script's stage counts must match an `aggregate_table`
   groupBy on `funnel_stage` over the same worksheet. A mismatch means the row
   pull was scoped differently -- fix the pull, not the rule.

## Step 6 -- Deliver

Call `get_report_guide` first, whatever the destination. Build **both**
surfaces by default -- the visual map *is* the deliverable, and the Sheet is
what Product works from.

### Sheet

- **Summary** -- cohort definition, window, cohort tickets and unique
  customers, rows dropped for unparseable registration dates, the
  highest-friction stage by stuck users, the top blocking step, attribution
  mix, and **the caveat from "What this skill must never claim" stated in the
  tab itself**, not just in chat.
- **Stage Map** -- one row per stage in ladder order: contacts, unique
  customers, share of cohort contacts, median tenure-days at contact,
  progressed / stuck / unknown-single-contact, give-up count, attribution mix,
  top blocking step.
- **Blocking Steps** -- one row per (stage x blocking step): customers, stuck
  customers, give-ups, `blocking_party`, `self_service_fix`, provider if any,
  and 2-3 example quotes with ticket links. Sorted by stuck customers
  descending. This is the Product backlog.
- **Provider Breakdown** -- only if a provider/channel column exists:
  provider x stage x contacts x stuck customers.
- **Set Aside** -- `unclear` and `post-onboarding` volumes.

### HTML funnel map

A single self-contained `.html` file, embedded JSON + vanilla JS, no framework
or build step. **Load the `dataviz` skill before writing any chart code.**

- A **funnel/ladder visual** as the centrepiece: stages in rank order, sized
  by contacts, with stuck customers shown as a distinct encoded band. Label it
  "contacts, not drop-off" directly on the chart.
- **Insight cards** above it: 3-6 findings, each with real numbers from this
  run and one labeled action (**Quick win** / **Medium lift** /
  **Structural**).
- **Blocking steps collapsed per stage** behind `<details>`, with example
  quotes and ticket links inside.
- Light + dark via `prefers-color-scheme`.
- Save to `~/Downloads/onboarding-friction-map-<window-end>.html` unless told
  otherwise, and state the path back.
- **Privacy:** contains real customer ids and quotes. Internal audiences only
  unless the user says otherwise.

Build ticket links from the source helpdesk's URL shape, confirming the
workspace id for the current environment.

In chat, lead with the highest-friction stage **by stuck customers**, its top
blocking step, and the attribution split -- plus the drop-off caveat in one
sentence. Link the worksheet.

---

## Principles

- **Contacts per stage is friction, never drop-off.** State this on every
  surface, including the chart.
- **Never present a stage share as a conversion number.**
- **Tenure is derived and fragile.** Confirm the registration column's type
  and units every run; drop unparseable rows and report the count.
- **Check the tenure distribution before enriching.**
- **Rank stages by stuck customers, not contact volume.**
- **`unknown-single-contact` is genuinely ambiguous and usually the biggest
  bucket.** Report it separately.
- **A missing follow-up ticket is not abandonment.**
- **Attribute the block before proposing a fix.**
- **A policy block still has a fixable failure inside it** -- often the timing
  of the disclosure.
- **Bias self-service findings toward in-product fixes.**
- **Never sample the cohort.**
- **Never hand-compute tenure, the stage rollup, or the progression split.**
  Use the script.

Built something clever?
Share it.

Publish a Skill, climb the leaderboard, and get Rippit rewards

+ Submit a Skill