SOMA docs
Reference

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.ts using inngest/next.
  • Created apps/web/src/app/api/telegram/webhook/route.ts importing @soma/bot + grammY's webhookCallback('std/http').
  • Made @soma/bot dual-purpose: exports field lets apps/web import the grammY Bot instance; apps/bot/src/polling.ts stays for local dev.
  • Deleted Fly apps (fly apps destroy soma-agent soma-bot).
  • Deleted infra/fly/, infra/docker/, the Hono servers in apps/agent + apps/bot/src/server.ts.
  • Added grammy, inngest, @soma/bot to apps/web deps + transpilePackages.
  • Inngest synced via PUT /api/inngest; Telegram webhook registered via setWebhook.

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/gmail fires gmail.ingest events directly).
  • GCal sync reduced to hourly (0 * * * *) — calendars change at human cadence.
  • Morning brief stays at daily (0 8 * * *).
  • factExtract was defined but never registered in inngestFunctions — 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.