Build in Public· 7 min read

Onboarding is never done

A contextual tour, a 14-day email sequence, a data quality bug that was silently corrupting first sessions — and the infrastructure that lets onboarding iterate every week without breaking.

Onboarding is never done cover

I shipped a new contextual tour for WatchDeck today. Five steps, anchored to real UI, with a celebration overlay when a user completes it. It's live. And I'm already planning the next revision.

This is the third entry in the WatchDeck build log. Day 1 was the zero-to-deployed push. Day 2 was deliverability. Day 3 — today — was the one I thought would be quick. It wasn't. The theme that kept surfacing: onboarding is never done.

The Tour That Looked Finished

The first pass of the contextual tour was already live. A handful of tooltips anchored to nav items, a progress indicator, local-storage tracking of completed steps. Looked fine. Worked fine. But activation wasn't budging.

The problem wasn't the tooltips. The problem was what came after them. Users hit the end of the tour, got told "you're all set!" and landed on an empty dashboard with no obvious next move. The completion was a dead end, not a launchpad.

Today's rebuild added a fifth step, a celebration overlay on completion, and a "What's next" panel that turns the dead end into a ramp. The tour now hands the user into the first meaningful action, rather than just clapping for them.

LocalStorage Is a Trap

The subtle bug that ate an hour: the "Reset Tour" control in admin wasn't actually resetting the tour. Users who had completed the tour once — internal users, specifically — were stuck in the completed state even after the reset.

Turned out tour state lived in two places: a localStorage key keyed by user ID, and a showOnboarding flag on the session. Reset cleared the localStorage key but not the session flag. Next page load, the session re-hydrated the completed state and wrote back to localStorage. The reset was instant and the rollback was instant.

Fixed by making reset update both surfaces and also bumping a tour version number in the localStorage key. Anyone on an old version gets the new tour automatically. A small pattern but it's the kind of thing you need in place before you want to ship tour revision number two.

The Real Fix Is the 14-Day Sequence

The tour is one surface. The better retention lever is the 14-day onboarding email sequence that shipped alongside it — automated, behavior-driven nudges that walk users through the product over two weeks.

The infrastructure for this is non-trivial. You need:

  • A job runner that fires per-user on a schedule, not a cron that blasts everyone.
  • Per-user state tracking — what step they're on, whether they've completed it, whether they've opted out.
  • Idempotency, because retries shouldn't double-send.
  • A way to skip nudges for users who've already done the thing the nudge is pushing toward.

Built it on EmailLog with a @@unique([userId, emailKey]) constraint so the same email key can't fire twice for the same user. Emails are keyed like onboarding_day_3 or weekly_digest_2026-04-14 — stable enough to reason about, flexible enough to let us change the copy or schedule without breaking the dedupe.

What Broke in the Data

While I was there, I found two data quality issues that were quietly corrupting the onboarding experience.

Streaming service attribution was inconsistent. The same show was being attributed to "Paramount" in one table and "Paramount+" in another, which broke service filtering. Consolidated to the canonical name. An hour of work but it's the kind of thing that looks like a bug in the product to users — "why isn't my Paramount+ content showing?" — when it's really a bug in the data layer.

Live TV bundles were competing with direct streaming services. A show available on both Hulu Live and direct Hulu was getting attributed to Hulu Live, which is wrong for most users. Updated the preference order to surface the direct service first.

Neither of these were "onboarding" work in the strict sense. But the user impact is the same — the product feels broken in the first session, the tour can't compensate, and the 14-day sequence can't save a user whose first experience was wrong.

The Lesson That Keeps Repeating

Every time I've thought onboarding was done, a new confusion point has surfaced within a week. The right posture isn't "ship the best onboarding possible" — it's "build the infrastructure to iterate on onboarding fast."

That means:

  • Versioned localStorage keys. So a new tour doesn't leave users stuck on the old one.
  • Easy reset mechanisms. Admin panel, force reset from user settings, clear from the URL query string. All three.
  • Good drop-off analytics. PostHog events at every meaningful step — I want to see exactly where users stall, not guess.
  • Dedupe guarantees on the email side. The unique constraint on EmailLog is the backstop that lets me iterate on the sequence without fearing a double-send incident.

Everything else is just copy and design. The infrastructure is what lets the copy and design change every week without breaking.

Big Win

Tour v2 is live with a real celebration overlay and a handoff to the next action. The 14-day sequence is in the scheduler, sending. Data quality issues that were invisible from the outside but corrupting first sessions are gone.


Previous: Day 2 — Email Deliverability Is Its Own Discipline. The operating model that lets me run this cadence solo is covered in the pillar on Claude Code + Obsidian as an unfair advantage.

Want to talk through something you’re working on?

I take on a small number of consulting and build engagements each quarter. If something in this piece maps to a problem you’re trying to solve, reach out.