Isaac Hess
Back to Isaac Hess

Building a two-sided marketplace

This is the written version of a marketplace starter I extracted from CageList, which runs in production with real users and real money. The code itself is private; what follows is everything I learned building it — the costs, the setup order, the decisions, and the mistakes.

A two-sided marketplace chassis, extracted from a marketplace that runs in production with real users and real money.

This is deliberately not a finished product. It is the part that is the same every time — the schema, the safety rails, the secrets workflow, the conventions — so the weeks you spend are spent on what makes your marketplace different.


What you actually need to know before starting

Two numbers, honestly:

Upfront — roughly $60, once

ItemCost
Domain~$10/yr
SMS campaign registration + vetting~$45 one-time
Everything else$0 — free tiers are real

You can build and demo the entire thing for the price of a domain. Payment, database, hosting, email, and error tracking all have free tiers that carry an early build comfortably.

Monthly — about $65 once you are live

ServiceCost
Database (Supabase Pro)$25
Hosting (Vercel Pro)$20
Email (Resend)$0 until ~3k/mo, then $20
SMS$18–45 depending on volume
Everything elsefree tier
Infrastructure floor~$65/mo

Payments cost ~2.9% + 30¢ per transaction and only when you earn.

The number nobody warns you about

Your AI development subscription will be your largest line — $100–200/month — and it will be worth more than all the infrastructure combined.

And a warning that came from a real invoice: on the reference build, two months of hosting came to about $693, of which serving the application to real users was $12.39. The rest was development cost — a per-token AI add-on left switched on, and build minutes from shipping 1,439 changes. Infrastructure is cheap. Velocity is what bills, silently, monthly.

Full breakdown in the cost model.


Setup, in order

Each service needs identifiers from the one above it, so do not shuffle these.

  1. Identity first. Buy a domain, put Google Workspace on it, create one admin account, enable two-factor. Sign up for every service below with a you+servicename@yourdomain.com alias. Never use a personal address for a business service — you cannot hand it to a client later and you cannot recover it if you lose the account.
  2. Supabase — database, auth, storage.
  3. Vercel — hosting, connected to your Git repository. Never deploy from the CLI.
  4. Cloudflare — domain registrar and DNS.
  5. Stripe — payments. Test mode is enough for a long time.
  6. Resend — email. Verify a subdomain so marketing volume cannot damage your root domain's reputation.
  7. Twilio — SMS. Start campaign registration on day one; approval takes weeks.

Then:

pnpm install
pnpm env:push --write     # push your keys into 1Password, once
pnpm env:sync             # generate .env.local from the vault
pnpm check:env            # confirm nothing is missing or empty
pnpm dev

Detailed per-service instructions: provider setup.


What is in here

PathWhat it is
supabase/migrations/The marketplace schema. Every table has row level security and policies. Apply this to a fresh, empty project first — it is the foundation everything else assumes.
scripts/env-*Secrets live in 1Password; .env.local is generated from it and never hand-edited.
scripts/secret-scan.mjsPre-commit hook that blocks a live credential from being committed.
scripts/verify-structure.mjsKeeps the route convention from decaying.
CLAUDE.mdRules your AI assistant reads every session. The most valuable file here after the schema.
docs/Costs, setup, decisions, and how to prompt.

What is deliberately not in here

No listing UI, no search, no checkout screens, no design system. Those are your product, and a generic version of them is worth less than nothing — you would spend longer deleting it than writing it.

What is here is the part that is identical in every marketplace and unpleasant to get right: the money columns, the double-booking constraint, the webhook dedupe table, the access rules, and the secrets workflow.


The schema is the point

Read supabase/migrations/00000000000001_foundation.sql before anything else. Things it settles that are expensive to retrofit:

  • Money is integer cents. Never floating point.
  • The fee split is frozen onto each booking. Rates change; a historical booking must still reconcile against what was actually charged.
  • Double-booking is a database constraint, not an application check. Two concurrent requests both pass an application check.
  • Payments have no user-write policy. They are written only by a service-role client from a verified webhook.
  • A webhook dedupe table exists from day one. Without it, a provider retry is a double charge.
  • Users cannot promote themselves. Role is set by trigger, changed only by a service-role client.

Verified, not assumed

Applied against PostgreSQL 16 on an empty database, with auth.users and auth.uid() stubbed to stand in for Supabase:

  • Applies cleanly, no errors.
  • All 8 tables report row level security enabled, 15 policies across them.
  • An overlapping booking on the same listing is rejected by the database: conflicting key value violates exclusion constraint "bookings_no_overlap".
  • A non-overlapping booking is accepted.
  • Cancelling a booking frees the slot for rebooking.