Migration history
Pivots that shaped the current architecture, and why.
2026-04-19 · Vercel-only
Original plan: Vercel (web) + Fly.io (agent worker + bot webhook).
Why we pivoted. Every fix during the audit pushed code toward serverless compatibility (pino, Mastra, Sentry lazy import, etc.). Keeping Fly meant carrying two runtimes through every change. Analysis across six axes (speed, cost, optimization, maintenance, scalability, lock-in) showed Vercel-only wins on five and ties on lock-in.
What changed.
- Created
apps/web/src/app/api/inngest/route.tsusinginngest/next. - Created
apps/web/src/app/api/telegram/webhook/route.tsimporting@soma/bot+ grammY'swebhookCallback('std/http'). - Made
@soma/botdual-purpose:exportsfield letsapps/webimport the grammYBotinstance;apps/bot/src/polling.tsstays for local dev. - Deleted Fly apps (
fly apps destroy soma-agent soma-bot). - Deleted
infra/fly/,infra/docker/, the Hono servers inapps/agent+apps/bot/src/server.ts. - Added
grammy,inngest,@soma/bottoapps/webdeps +transpilePackages. - Inngest synced via
PUT /api/inngest; Telegram webhook registered viasetWebhook.
Net effect: 1 deploy target, 1 domain, 1 set of logs, ~$77/year saved, git push → prod in 30 seconds, no more Docker / pnpm-hoist / worker-thread debugging.
2026-04-19 · Cron optimization
Two */15 * * * * crons — for Gmail and GCal ingestion — accounted for 192 Inngest invocations per day, mostly no-op on idle accounts.
What changed.
- Gmail ingestion moved to push-only via Google Pub/Sub webhook (
/api/webhooks/gmailfiresgmail.ingestevents directly). - GCal sync reduced to hourly (
0 * * * *) — calendars change at human cadence. - Morning brief stays at daily (
0 8 * * *). factExtractwas defined but never registered ininngestFunctions— bug fixed.
Before: 193 invocations/day. After: 25/day. −87% invocations, cheaper Anthropic/Voyage costs on fact extraction, less trace noise in Langfuse.
2026-04-19 · Docs stack
Started with Notion (generated handbook via Claude Code), then moved to Astro Starlight, then settled on Fumadocs (Next.js + MDX).
Why Fumadocs. Same Next.js + Tailwind stack as the app → we can import @soma/ui tokens directly, share components if needed, and developers don't context-switch. Modern OSS-product aesthetic (Biome, Polar, Cal.com use it). Static export ships to Cloudflare Pages as a zero-runtime site.
Starlight was simpler but meant introducing Astro into the monorepo. For a docs site that will live for years, the per-change cost of an extra build tool compounds.