A personal matchmaking club for friends and friends-of-friends (New York first — SF waitlist — mid-late 20s, largely Asian-American). One long, research-backed questionnaire, read by a human matchmaker, assisted by Claude. No swiping, no public profiles, no app.
Production: https://redknotclub.com (Vercel project redthread; redthreadintros.vercel.app remains as a secondary alias)
Brand history: launched as "Red Thread" (2026-06-10), rebranded to Red Knot Club with the redknotclub.com purchase (2026-06-11). Same legend — the thread is fate's job, the knot is ours.
- Next.js 16 (App Router, Turbopack) + TypeScript strict + Tailwind v4 on Vercel.
- Storage: encrypted document store on Vercel Blob — a
private-access store (redthread-private), where every blob is additionally AES-256-GCM encrypted with an app-held key (DATA_ENCRYPTION_KEY) before upload. No public URL exists for any byte, and a leaked storage credential alone still yields only ciphertext.applicants/<id>.json.enc— the application documentnotes/<id>.json.enc— admin notes/log, separate so applicant edits can't clobber themtokens/<sha256(token)>.json.enc— edit-token hash → applicant id (raw tokens never stored)photos/<id>/<photoId>.enc— sharp-re-encoded (EXIF/GPS-stripped) encrypted images- Reads use
get(pathname, { access: 'private', useCache: false })for strongly consistent read-after-write (CDN-cached reads serve stale content after overwrites).
- No accounts. Applicants get a 128-bit edit token in a private link (
/edit/<token>); only its SHA-256 is stored. The single admin signs in with Google — OAuth authorization-code flow allowlisted to exactlyADMIN_EMAIL, id_token verified against Google JWKS — and gets a 7-dayjose-signed httpOnly cookie. - Auth is checked in every admin route handler and page — no middleware/proxy as a security boundary.
Two tiers, grounded in the research in docs/research-digest.md:
- Express (~10 min, 6 sections + photos) — gets someone into the pool. Identity, intent/timeline/kids, the diaspora core (cultural background + whose preference it is, religion + practice + importance, parents' role, family expectations), dealbreakers, lifestyle, short psyche battery (attachment pick, TIPI-6, conflict style, life satisfaction), humor, and three free-texts.
- Full (~25 min, 7 sections) — ECR-S 12-item attachment, values ranking, family & culture deep-dive, relationship history ("what would an ex say…"), daily texture, affection (skippable), and the bilateral preference block (OkCupid-style accept/importance on 8 keys) + date-me-doc pitch.
Editing the questionnaire = editing that one config file; rendering and validation are config-driven.
Hard dealbreaker filters (mutual orientation, age windows, distance, kids, vice dealbreakers, do-not-match lists, bilateral non-negotiables) → weighted scoring (bilateral satisfaction geometric mean, values-rank overlap, religion/politics homogamy scaled by stated importance, lifestyle proximity, family alignment) → documented risk flags (anxious×avoidant, volatile×avoider, both-high-neuroticism, spender×saver). Scores are labeled "ideas, not verdicts" in the admin UI on purpose — the science says questionnaires can't predict chemistry, so the human + post-date feedback loop carry the real weight.
brief.ts generates fixed-schema Claude prompts (pair brief and ranked-pool brief) with
contact info excluded by design.
/admin (Google sign-in, ayushupneja@gmail.com only) — one nav across six sections:
- Desk — pool table → full profiles with decrypted photos, status workflow, notes + append-only log, the Evidence panel (transparent math, dossier prompts, propose-intro).
- Intros — the concierge pipeline (proposed → asked → consented → introduced → outcome) and the north star: % of applicants with a consented intro.
- Board — internal kanban (owner, status, blockers) for all work on the club.
- Numbers — first-party anonymous analytics, funnel, intro funnel, pool liquidity by gender.
- Pairs — every eligible pair ranked by the math engine.
- Library — the company docs (docs/company/, embedded at build time).
See docs/security-checklist.md. Highlights: private+encrypted storage; sharp re-encode
strips EXIF/GPS and neutralizes malicious files; strict CSP / HSTS / no-referrer
(edit tokens live in URLs); rate limiting (in-memory, per-instance) + honeypot on
submission; zod validation against the question config; no analytics or third-party
scripts anywhere; PII never logged.
npm run dev(uses.env.local, pulled viavercel env pull+ the three app secrets)npm run build && npm run lintbefore deploys;vercel deploy --prod --yes- Env vars (all three environments):
BLOB_READ_WRITE_TOKEN,DATA_ENCRYPTION_KEY,SESSION_SECRET,GOOGLE_CLIENT_ID,GOOGLE_CLIENT_SECRET,ADMIN_EMAIL,OAUTH_REDIRECT_BASE(OAuth client lives in GCP projectaj-apartment-shortlist, shared with Creator OS; callback URIs registered for redknotclub.com, redthreadintros.vercel.app, and localhost:3000) - Losing
DATA_ENCRYPTION_KEYloses all data. It's in Vercel env +.env.local. - Deployment protection is
preview-only so production is public.