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.
- 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)
- Sign-in check → Google sign-in: two allowlisted accounts only (request, sync)
- Sign-in check → Pages and forms: page loads and form posts (request, sync)
- Phone app → Capture route: a freeform note (request, sync)
- Capture route → Claude Haiku: one schema-constrained call per note (request, sync)
- Capture route → Postgres: note kept as pending, then parsed or unparsed (request, sync)
- Phone app → Commit route: the items you accepted (request, sync)
- Commit route → Mapbox: geocode new addresses before the transaction (request, sync)
- Commit route → Postgres: one transaction; ten pages revalidated (request, sync)
- Phone app → Open-Meteo: forecast straight from the browser (request, sync)
- Phone app → Mapbox: map tiles and directions from the browser (request, sync)
- Pages and forms → Contact importers: import on a button press (manual, sync)
- 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.
- Daily cron → Reminder route: bearer secret; an unset secret returns 503 (cron, async)
- Reminder route → Plan reminders: four offsets before a plan (request, sync)
- Reminder route → Key-date reminders: a week before and on the day (request, sync)
- Plan reminders → Send ledger: claim a row before sending (request, sync)
- Key-date reminders → Send ledger: claim a row before sending (request, sync)
- Plan reminders → Resend: send (request, sync)
- Key-date reminders → Resend: send (request, sync)
- 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
| Resource | Figure | Status | Source |
|---|---|---|---|
| Vercel edge requests, budget | under 5,000 per month | Estimate (the project's own) | cost spec, line 89; project notes, line 25 (a self-imposed budget) Read on 2026-10-17 |
| Vercel edge requests, plan | One million — Shorthand in prices and limits, as in $1 per 1M tokens. per month, shared by about 10 projects | Published 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 day | Estimate (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. tokens | Published 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. tokens | Published price | limits module, line 168 Read on 2026-10-17 |
| Vercel cron | ~30 runs per month | Derived | 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 month | Estimate (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 tier | 100,000 requests per month | Published price | project notes, line 44; limits module, line 60 Read on 2026-10-17 |
| Mapbox Search Box, August | 121 requests | Measured | limits module, line 71 Read on 2026-10-17 |
| Mapbox map loads billed, August | 56 loads | Measured | limits module, line 82 Read on 2026-10-17 |
| Mapbox invoice, August | $0.00 | Measured | 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. tokens | Published price | limits module, line 159 Read on 2026-10-17 |
| Jev, monthly | ~$0.0044 per month | Derived 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