Monorepo for the Italy-first Benefits Opportunity Engine MVP.
apps/web: Next.js web app for users and operatorsservices/api: FastAPI application and domain logicservices/worker: background worker and ingestion orchestration
- Copy
.env.exampleto.envand adjust values if needed. - Start local infra:
pnpm infra:up - Install frontend deps:
pnpm install - Install Python deps:
pip install -e services/apipip install -e services/worker
- Seed the database:
pnpm seed - Start all services:
pnpm dev
If Docker is not available, set DATABASE_URL=sqlite:///./benefits_engine.db in .env and run the API/seed commands against SQLite. The codebase keeps the Postgres/Redis/MinIO Compose stack ready, but the current repo is also verified against the SQLite fallback path.
apps/web-> Vercelservices/api-> Railway service usingservices/api/railway.tomlservices/worker-> Railway service usingservices/worker/railway.toml- Postgres -> Railway Postgres or Neon
- Email -> Resend
- Snapshot storage -> Railway volume with
SNAPSHOT_STORAGE_BACKEND=localor S3-compatible storage withSNAPSHOT_STORAGE_BACKEND=s3
Use sibling subdomains so the shared session cookie can cover both hosts.
app.yourdomain.com-> webapi.yourdomain.com-> API
Set these production env vars on both the web app and API:
SESSION_COOKIE_DOMAIN=.yourdomain.comSESSION_COOKIE_SECURE=trueSESSION_COOKIE_SAME_SITE=laxSESSION_COOKIE_NAME=boe_session
Set these on the API:
ENVIRONMENT=productionAPP_BASE_URL=https://app.yourdomain.comCORS_ALLOWED_ORIGINS=https://app.yourdomain.comAUTO_CREATE_SCHEMA=falseAUTO_SEED_ON_STARTUP=false
Set these on the web app:
NEXT_PUBLIC_APP_URL=https://app.yourdomain.comNEXT_PUBLIC_API_URL=https://api.yourdomain.comINTERNAL_API_URL=https://api.yourdomain.comNEXT_PUBLIC_GOOGLE_SITE_VERIFICATION=<google-search-console-token>NEXT_PUBLIC_GA_MEASUREMENT_ID=G-XXXXXXXXXXNEXT_PUBLIC_GOOGLE_SITE_VERIFICATION_APP=<optional app-host token>NEXT_PUBLIC_GOOGLE_SITE_VERIFICATION_APEX=<optional apex-host token>NEXT_PUBLIC_GA_MEASUREMENT_ID_APP=<optional app-host GA4 stream>NEXT_PUBLIC_GA_MEASUREMENT_ID_APEX=<optional apex-host GA4 stream>
The web app supports Google Search Console ownership and GA4 without further code changes.
Set these on Vercel:
NEXT_PUBLIC_GOOGLE_SITE_VERIFICATION=<google-search-console-token>NEXT_PUBLIC_GA_MEASUREMENT_ID=G-XXXXXXXXXX- optionally split them by host with:
NEXT_PUBLIC_GOOGLE_SITE_VERIFICATION_APP=<app token>NEXT_PUBLIC_GOOGLE_SITE_VERIFICATION_APEX=<apex token>NEXT_PUBLIC_GA_MEASUREMENT_ID_APP=<app GA4 stream>NEXT_PUBLIC_GA_MEASUREMENT_ID_APEX=<apex GA4 stream>
That will:
- emit the Google verification meta tag from the correct host shell
- load the GA4 client script with host-specific stream IDs when provided
Recommended production setup:
- add both
https://tispetta.euandhttps://app.tispetta.euas Google Search Console properties - keep
tispetta.euas a domain property because it owns the redirects and future marketing landing - keep
app.tispetta.euas a URL-prefix property for app-specific indexing checks
The API bootstrap command for Railway predeploy is:
cd services/api && python -m app.predeploy
That command now runs alembic upgrade head programmatically before any optional seed step. After the first bootstrap, keep AUTO_CREATE_SCHEMA=false in production so runtime startup does not mutate schema.
Local migration command:
cd services/api && alembic upgrade head
Magic-link delivery supports:
- Resend API when
RESEND_API_KEYis configured - SMTP fallback when
SMTP_HOSTand related settings are configured
For Resend SMTP, use:
SMTP_HOST=smtp.resend.comSMTP_PORT=465SMTP_USERNAME=resendSMTP_PASSWORD=<resend-api-key>SMTP_USE_SSL=true
Minimum live email setup on Railway:
ENVIRONMENT=productionRESEND_API_KEY=<resend-api-key>RESEND_FROM_EMAIL=login@updates.yourdomain.com
Without those values, the API falls back to preview-link behavior or SMTP fallback depending on the environment.
The Railway worker runs an internal scheduler and no longer relies on one-shot startup dispatch.
Set these on the worker:
WORKER_POLL_INTERVAL_SECONDS=5WORKER_ENDPOINT_REFRESH_INTERVAL_SECONDS=21600WORKER_FAMILY_REFRESH_INTERVAL_SECONDS=21600WORKER_SURVEY_REFRESH_INTERVAL_SECONDS=86400
That gives you:
- source endpoint refresh every 6 hours
- measure-family bootstrap refresh every 6 hours
- survey coverage recomputation every 24 hours
pnpm devpnpm infra:uppnpm seedpnpm test