← Try the mock

Kith: how it's built

A private two-person app for keeping up with friends.

Households, people, key dates, facts and hangouts. A capture box turns a note into proposed updates, each confirmed before it saves. Two adults, Google sign-in with a two-account allowlist, mostly on phones.

Architecture

From a note to the home screen

No poller or repeating timer in the browser; one daily cron is the only server timer.

From a note to the home screenPhoneApp serverDataOutsidePhone appSign-in checkPages and formsCapture routeCommit routePostgresGoogle sign-inOpen-MeteoClaude HaikuMapboxContact importersJev12345678910111213
  1. Phone app → Sign-in check: every route but sign-in, the cron and one Open Authorization — A standard that lets an app act on a user's behalf without receiving their password. callback (request, sync)
  2. Sign-in check → Google sign-in: two allowlisted accounts only (request, sync)
  3. Sign-in check → Pages and forms: page loads and form posts (request, sync)
  4. Phone app → Capture route: a freeform note (request, sync)
  5. Capture route → Claude Haiku: one schema-constrained call per note (request, sync)
  6. Capture route → Postgres: note kept as pending, then parsed or unparsed (request, sync)
  7. Phone app → Commit route: the items you accepted (request, sync)
  8. Commit route → Mapbox: geocode new addresses before the transaction (request, sync)
  9. Commit route → Postgres: one transaction; ten pages revalidated (request, sync)
  10. Phone app → Open-Meteo: forecast straight from the browser (request, sync)
  11. Phone app → Mapbox: map tiles and directions from the browser (request, sync)
  12. Pages and forms → Contact importers: import on a button press (manual, sync)
  13. Pages and forms → Jev: a Prep run on a button press (manual, sync)

The daily reminder job

Each job runs in its own try/catch, so one failing does not stop the other.

The daily reminder jobSchedulerApp serverDataOutsideDaily cronReminder routePlan remindersKey-date remindersSend ledgerResendTwo inboxes12345678
  1. Daily cron → Reminder route: bearer secret; an unset secret returns 503 (cron, async)
  2. Reminder route → Plan reminders: four offsets before a plan (request, sync)
  3. Reminder route → Key-date reminders: a week before and on the day (request, sync)
  4. Plan reminders → Send ledger: claim a row before sending (request, sync)
  5. Key-date reminders → Send ledger: claim a row before sending (request, sync)
  6. Plan reminders → Resend: send (request, sync)
  7. Key-date reminders → Resend: send (request, sync)
  8. Resend → Two inboxes: the two allowlisted addresses only (push, async)

Wiremocks

A mock-up of a screen — Here, a static picture of a screen, drawn to show layout and flow, not the working app. — a static picture, not the real app

The home screen

A drawing of the home dashboard on the made-up roster from the Kith mock.

Coming up

  • BirthdayChidi Okaforin 3 days
  • AnniversaryTomas and Ingridtomorrow
  • PlanDinner with the Moreau householdin 6 days

Catch-up board

Households ranked by days since the last hangout.

  • Halvorsen household63 days ago

    Harbor

  • Moreau household41 days ago

    Elmwick

  • Okafor household12 days ago

    Lakeside

Running cost

ResourceFigureStatusSource
Vercel edge requests, budgetunder 5,000 per monthEstimate (the project's own)
cost spec, line 89; project notes, line 25 (a self-imposed budget)
Read on 2026-10-17
Vercel edge requests, planOne million — Shorthand in prices and limits, as in $1 per 1M tokens. per month, shared by about 10 projectsPublished price
owner's metered-resource table (Vercel Requests Vercel counts at its edge — Every request Vercel serves (a page, a script, an image) counts as one.)
Read on 2026-10-17
Claude Haiku captures~$0.45 per month at 10 captures a dayEstimate (the project's own)
project notes, line 42
Read on 2026-10-17
Conflicts with v1 design spec, line 139 (~$0.30 per month, read on 2026-10-17): The project notes line was written hours after the spec line, both on 2026-07-28, because the field catalog adds tokens to every capture, so the newer line governs the row.
Claude Haiku input price (the version the app pins)$1 per One million — Shorthand in prices and limits, as in $1 per 1M tokens. tokensPublished price
limits module, line 168
Read on 2026-10-17
Claude Haiku output price (the version the app pins)$5 per One million — Shorthand in prices and limits, as in $1 per 1M tokens. tokensPublished price
limits module, line 168
Read on 2026-10-17
Vercel cron~30 runs per monthDerived
derived (see inputs)
Inputs: one run a day (configured, vercel.json line 4) × 30-day month = 30
Read on 2026-10-17
Conflicts with reminder route, lines 16-18 (~60 runs per month, read on 2026-10-17): That counted a staging project retired 2026-10-02, so the newer count governs.
Reminder email~20–38 per monthEstimate (the project's own)
project notes, line 52 (a range from one source line counts as one figure)
Read on 2026-10-17
Mapbox geocoding free tier100,000 requests per monthPublished price
project notes, line 44; limits module, line 60
Read on 2026-10-17
Mapbox Search Box, August121 requestsMeasured
limits module, line 71
Read on 2026-10-17
Mapbox map loads billed, August56 loadsMeasured
limits module, line 82
Read on 2026-10-17
Mapbox invoice, August$0.00Measured
limits module, lines 71, 82 and 93
Read on 2026-10-17
Jev input price$0.042 per One million — Shorthand in prices and limits, as in $1 per 1M tokens. tokensPublished price
limits module, line 159
Read on 2026-10-17
Jev, monthly~$0.0044 per monthDerived from an estimate
derived (see inputs)
Inputs: ~20 Prep runs a month (projected, prep spec) × 5,192 tokens a run (measured mean of 10 runs, project notes line 56) × $0.042 per One million — Shorthand in prices and limits, as in $1 per 1M tokens. = $0.00436
Read on 2026-10-17
Conflicts with prep spec, line 441 (~$0.005 per month, from a 6,000-token estimate, read on 2026-10-17): The measured mean replaces the estimate, as that spec says it will (lines 448-449).
Neon compute—Unknown
project notes, lines 16 and 39, say free tier; limits module, lines 135-137, marks it verify; the owner's Neon row (the org moved to Launch 2026-09-29) does not name this project
Read on 2026-10-17

Open-Meteo is called from the browser, free up to 10,000 a day (limits module, line 126).

Design decisions

  • Nothing saves until you confirmOne schema-constrained call proposes updates and each item is accepted before it is written. A local model was dropped because it proposed duplicate people and invented field values.
  • Notes and field values are append-onlyA raw note is never lost; a failed parse is kept for a retry. A single-value field retires its old value with an end date, never overwriting.
  • Edge-request hygieneNo poller or repeating timer in the browser, and one daily cron is the only server timer. The shared monthly pool is small, so a commit was cut from two requests to one.
  • The field catalog is the only taxonomyNew keys are approved in the confirm diff, and the catalog rides in every prompt so the parser reuses keys rather than inventing near-duplicates.
  • A private two-user perimeterGoogle sign-in with an allowlist, middleware on every other route. The cron route is exempt from middleware but carries its own bearer check, and an unset secret returns 503.

Open questions

  • Neon compute: whether this project is billed on Launch, in which case the free-tier note is out of date
  • Edge requests: whether the 5,000 budget includes the add-ons (project notes, line 38, is ambiguous) and the measured share needs the per-project dashboard
  • Claude Haiku: measured spend lives in the app's usage table, not read here
  • Reminder email: the Resend plan is not verified
  • Mapbox: account plan not verified
  • Builds: about 80 minutes a month (project notes, line 106), Hobby allowance not verified