Skip to content

Record where new accounts first came from - #337

Merged
pumfleet merged 3 commits into
mainfrom
seo/signup-first-touch
Nov 5, 2026
Merged

pumfleet merged 3 commits into
mainfrom
seo/signup-first-touch

Conversation

@pumfleet

@pumfleet pumfleet commented Nov 5, 2026 •

Copy link
Copy Markdown
Collaborator

We can't tell whether search brings anyone who signs up, so we can't tell whether any SEO work pays off. This records where each new account first came from, with no third-party script and no cookie. Legal confirmed no policy changes are needed.

How it works

  1. The site and /sign-up remember the first visit in localStorage (@coss/auth/first-touch). The two apps share coss.com's origin, so the dashboard can read what the site wrote. A later visit doesn't overwrite it until it's 90 days old.
  2. /onboarding reports it once through a server action, then forgets it. This covers email and social sign-ups alike. The server validates it again and only accepts it for the signed-in user, within 7 days of the account being created, and never while impersonating.
  3. /admin/signups shows the counts for the last 30 days: first page, referrer and campaign source.

What's stored

The first page's path, the referrer's host, utm_source (or ref), utm_medium, utm_campaign and a timestamp. Never full URLs or query strings. With Global Privacy Control on, nothing is stored. signup_attribution is keyed by user and cascades when the account is deleted.

What it misses

  • Invited users, who never reach onboarding. That's fine, since they aren't acquisition.
  • Sign-ups finished on another device or browser.
  • Visitors arriving through /ui, /origin or /qnot, which are separate apps. If they then reach a site page, it's recorded as that page with no referrer.

The admin card shows how many accounts have a record, so these gaps stay visible.

Testing

Run against a throwaway database (coss_seo_test), with both apps on spare ports:

  • New unit tests in packages/auth and DB tests in packages/tenancy pass (including the cascade), as do the full packages/tenancy and apps/web suites.
  • A new smoke e2e test signs up with a utm_source, reaches onboarding, and checks the row is written and the browser value is gone.
  • A one-off Playwright run against the real site: /compare/resend?utm_source=hn&token=secret with a Google referrer stored only the path, host and source; a second page didn't overwrite it; no cookies were set; Global Privacy Control stored nothing.
  • biome ci . and typecheck for web, site, tenancy, auth and db pass.

Not checked: the admin card in a browser, and a real social sign-up.

After merge

The migration (signup_attribution) needs to run. I'd check the admin card after the first few sign-ups.


Autofix with [code]smith
Open in [code]smith · Need help on this PR? Tag @codesmith-bot with what you need.

The site and /sign-up keep a visitor's first touch in localStorage (path, referrer host, campaign parameters), onboarding reports it once and /admin/signups shows the counts. No third-party script and no cookie; nothing is stored with Global Privacy Control on.
@pumfleet
pumfleet added this pull request to the merge queue Nov 5, 2026
@github-merge-queue
github-merge-queue Bot removed this pull request from the merge queue due to a conflict with the base branch Nov 5, 2026
@pumfleet
pumfleet enabled auto-merge November 5, 2026 18:25
# Conflicts:
#	apps/web/AGENTS.md
#	packages/db/migrations/meta/0140_snapshot.json
#	packages/db/migrations/meta/_journal.json
@pumfleet
pumfleet added this pull request to the merge queue Nov 5, 2026
Merged via the queue into main with commit bcee30a Nov 5, 2026
14 checks passed
@pumfleet
pumfleet deleted the seo/signup-first-touch branch November 5, 2026 18:33
@linear-code

linear-code Bot commented Nov 5, 2026

Copy link
Copy Markdown

ENG-8166

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant