Founding 10

Paid waitlist for Hina — build plan

5 min

The loop Stack Routes Checkout flow Supabase schema The notify automation Whitelist bridge The cold-open constraint Build steps Open decisions

The loop

A stranger watches Hina text → signs in → drops their phone → pays $99. Supabase holds the row, your assistant texts you the second it lands, you add them to the whitelist and send Hina's opener. Hina takes the thread from there.

VERCEL + SUPABASE · AUTOMATED Landing page full-bleed Hina video → "Claim your founding spot" Sign in (Google) → authed one click, no SMS Enter phone stored on their row — the number Hina will text Stripe Checkout · $99 webhook flips paid = true Supabase · users table you watch it fill in the built-in table editor YOUR MAC · iMessage agents Assistant polls Supabase → texts YOU "new founding member: +1555…" · fires once via notified flag You: npm run allow +1555… + send opener ~30s — adds to whitelist, opens the thread Hina takes over the thread onboarding · proactive · SRS — zero new agent code

Stack

PieceChoiceWhy
Landing appSvelteKit (Svelte 5) on VercelServer routes for Stripe + Supabase. Lives in language/web (sibling of agent/).
Auth + DBSupabaseAuthed state, a user record, and a dashboard — all three at once. Table editor = your view.
PaymentsStripe Checkout + webhookHosted page, no PCI surface. Webhook sets paid = true.
Notifymorpheus/assistant (Mac) polls SupabaseTexts you the instant a member pays. iMessage ⇒ must run on the Mac (see automation).
DeliveryHina agent (Mac, existing)You send the opener; Hina runs the thread. No changes beyond the whitelist.

Note: this reverses the earlier "no Supabase" call — justified the moment you wanted authed state + a visible user record. Still no Twilio.

Routes

PathWhat's there
/Landing. Full-bleed Hina demo video + the "text message" CTA button → takes you to /pricing.
/pricingFounder pricing only (for now) — $99/yr, Founding 10. Sign in (Google) → phone → Stripe Checkout.

Checkout flow — auth → phone → pay

Order matters. Auth and phone come before the charge so Stripe is the last step (best completion) and you never take money from someone you can't reach.

Why not Stripe-first / Google Form: losing a maybe-buyer at sign-in is a normal funnel; losing a paid user's phone is fatal — the product is texting that number. A form can't give authed state or tie a row to an identity.

Supabase schema

One table. It is the CRM, the funnel view, and the automation's queue — all at once.

create table users (
  id          uuid primary key references auth.users,
  email       text,
  phone       text,           -- normalized at write, matches iMessage handle
  paid        boolean default false,
  paid_at     timestamptz,
  notified    boolean default false,  -- assistant flips this after it texts you
  created_at  timestamptz default now()
);

The notify automation

You want a text the second a paid member lands. The seam is deliberately decoupled: the landing page only writes the row — the consumer can change without touching the web app.

Later, if you ever want notify off your Mac, swap the poller for a Supabase webhook → push/Telegram. The row contract doesn't change.

Whitelist bridge

Gotcha in today's code: the whitelist is frozen at boot — config.mjs reads ALLOW_FROM from .env and the poller builds the allow-set once at startup (chat-poller.mjs:300). Adding a number today means editing .env and restarting.

The cold-open constraint

Hina can't cold-text a stranger. The agent only reacts to inbound iMessages and its proactive engine needs an existing chat to poke — it can't open a brand-new thread to a number that's never messaged it. So you send the first text by hand; Hina takes over from their reply. At 10 users this is ~30s of work and it preserves the "Hina texts you first" magic.

Build steps

Open decisions