A community hub of curated experiences — movement, discovery, connection — for people in their next chapter of life (50+). Members browse a curated catalog, reserve spots (free RSVP today, Stripe later), and partner-ticketed events capture the registration on-platform before handing off to the partner site. Behind it: a scraping pipeline that discovers events per market, human approval gates, a marketing stack (email campaigns, journeys, Meta ads), and an internal AI wiki.
| Layer | Tech |
|---|---|
| Frontend | Next.js 15 App Router on Vercel, Tailwind + shadcn/ui, recharts |
| Backend | Convex (database, functions, crons, file storage, HTTP actions) |
| Auth | Convex Auth — passwordless email OTP primary (ADR-009) + Google SSO; password kept as legacy sign-in only |
Resend via @convex-dev/resend component; marketing on a separate subdomain (ADR-012) |
|
| Ads/attribution | Meta pixel + Conversions API (ADR-013), first-touch attribution (ADR-011), first-party usage analytics |
| LLMs | OpenAI (extraction/validation/wiki/marketing), Perplexity (discovery), Serper (source discovery) |
| Observability | Sentry (frontend, ADR-014), errorLogs table for Convex failures, /admin/logs |
| Testing | vitest + convex-test (unit), Playwright (e2e), GitHub Actions CI |
npm install
npm run dev # runs `next dev` + `convex dev` in parallelFirst run: convex dev provisions a dev deployment and writes
.env.local. Copy .env.local.example for the frontend vars, and set
Convex-side vars with npx convex env set KEY value (see the env table
below). There are two deployments — dev and prod — and each holds its own
env vars.
Bootstrap an admin (after signing up once through the UI):
npx convex run users:setRoleInternal '{"email":"you@example.com","role":"admin"}'Seeds: eventCategories:seed (21 default categories, admin-gated),
metrics:seedMockMetrics / attribution:seedMockAttribution (demo dashboards
— purge later with metrics:purgeMockMetrics), testing:seed (e2e fixtures,
requires ALLOW_TEST_SEED=true).
npm test # unit: reserve money-paths, registration derivation,
# event mutations, slot invariants, claims (convex-test)
npm run test:e2e:ui # Playwright with the step-through UI (watch the browser)
npm run test:e2e:headed # full visible-browser runE2E needs three flags on the dev deployment (never prod):
npx convex env set ALLOW_TEST_SEED true
npx convex env set TEST_STATIC_OTP 000000
npx convex env set DEV_OTP_LOG trueCI (.github/workflows/): ci.yml runs lint/typecheck/unit on every push;
e2e.yml runs nightly + on demand against an anonymous local Convex backend;
backup.yml exports the prod deployment nightly (30-day artifacts).
Vercel builds run npx convex deploy --cmd 'npm run build' (see
vercel.json), so functions/schema and frontend ship atomically — set
CONVEX_DEPLOY_KEY (production deploy key) in the Vercel env. Rollback
asymmetry and full ops procedures: documentation/RUNBOOK.md.
app/
(user)/ public storefront: experiences catalog + detail, calendar,
blog, reserve/[slotId] (+ confirmed), story/[reservationId],
account, ambassador/[slotId], preferences (onboarding)
admin/ console: events (board/table + editor), curate wizard,
reservations, markets, categories, blog, stories,
campaigns, journeys, audience, discovery, crawler, logs,
usage, kpi, attribution, users
wiki/ internal AI wiki ("Data Brain", noindexed, auth-gated)
auth/ OTP / Google / legacy password sign-in
api/ ics/[reservationId], out/[eventId] (tracked partner
redirect), unsubscribe (RFC 8058)
- Storefront:
events,experiences(public projections),slots,reservations(internal + external registrationType),waitlist,stories,ambassadors,adminEvents(aggregates) - Pipeline (see
documentation/scraping-pipeline.md):marketSources,eventDiscoveryJobs,crawlQueue,actions/*(discover, crawl, extract, validate), human gates in/admin/curate+/admin/events - Marketing:
campaigns,emailCampaigns+campaignWorker,journeys,audience,metaAds,marketingImport - Analytics:
usage(first-party events + rollup + dashboard),computedMetrics(weekly KPI cron),metrics,attribution - Platform:
auth,users,emails,errorLogs,retention(nightly purges),migrations(@convex-dev/migrations ledger),crons(9 jobs),lib/(auth, limiter, registration, slots, reportError, …)
| Var | Where | Purpose |
|---|---|---|
CONVEX_DEPLOYMENT, NEXT_PUBLIC_CONVEX_URL |
.env.local / Vercel |
written by convex dev; prod URL injected by convex deploy |
NEXT_PUBLIC_CONVEX_SITE_URL |
.env.local / Vercel |
.convex.site origin for HTTP actions (usage ingest) |
NEXT_PUBLIC_SITE_URL |
Vercel | canonical origin (sitemap, JSON-LD, .ics) |
NEXT_PUBLIC_META_PIXEL_ID |
Vercel | Meta browser pixel (ADR-013) |
NEXT_PUBLIC_SENTRY_DSN, SENTRY_ORG, SENTRY_PROJECT, SENTRY_AUTH_TOKEN |
Vercel | Sentry (ADR-014); no-op unset |
SITE_URL |
convex env | origin used inside emails |
OPENAI_API_KEY, OPENAI_MARKETING_MODEL, OPENAI_WIKI_MODEL |
convex env | LLM calls |
PERPLEXITY_API_KEY, SERPER_API_KEY |
convex env | discovery |
RESEND_API_KEY, RESEND_FROM, RESEND_MARKETING_FROM, RESEND_TEST_MODE, RESEND_WEBHOOK_SECRET |
convex env | email (ADR-008/012) |
UNSUBSCRIBE_SECRET, MARKETING_POSTAL_ADDRESS |
convex env | one-click unsubscribe + CAN-SPAM |
AUTH_GOOGLE_ID, AUTH_GOOGLE_SECRET |
convex env | Google SSO |
META_ACCESS_TOKEN, META_PIXEL_ID, META_AD_ACCOUNT_ID, META_PAGE_ID, META_TEST_EVENT_CODE |
convex env | Conversions API + insights import |
DEV_OTP_LOG, TEST_STATIC_OTP, ALLOW_TEST_SEED |
convex env (dev only) | local/e2e escape hatches — never on prod |
CONVEX_DEPLOY_KEY |
Vercel | build-time Convex deploy |
CONVEX_PROD_DEPLOY_KEY |
GitHub secret | nightly backup export |
documentation/DECISIONS.md— ADR log (the "why"); newest firstdocumentation/RUNBOOK.md— deploy, rollback, backups, key rotation, cronsdocumentation/scraping-pipeline.md— pipeline behavior spec (versioned)documentation/BRAND.md— voice/copy referencedocumentation/CHANGELOG.md— narrative history of big batchesdocumentation/TODO.md— intentionally deferred workCLAUDE.md— agent working conventions