SOMA docs
Surfaces

Inngest workflows

8 functions, ~25 invocations/day. Event-driven where possible, cron only where necessary.

Registered at https://soma-ai.cc/api/inngest. Handler served by inngest/next.

The catalog

FunctionTriggerWhat it does
morningBriefCroncron 0 8 * * * · 1/dayFan-out brief.generate per user
generateBriefevent brief.generateAssemble context → Opus synthesis → deliver
weeklyReviewevent review.weeklyOpus-driven deep synthesis over the week's events + facts
gmailIngestevent gmail.ingestPull recent Gmail messages → upsert sources, person entities, email_received events
gcalSyncCroncron 0 * * * * · 24/dayFan-out gcal.sync per Google-connected user
gcalSyncevent gcal.sync21-day horizon, upsert events (dedup on calendar id)
healthImportevent health.importParse Apple Health XML → workouts/sleep/metrics events
factExtractevent conversation.turnExtract facts from turn → upsert with cosine dedup (τ=0.85) → update confidence via moving average

Concurrency & retries

  • Per-user limit = 1 on ingestion workflows (gmailIngest, gcalSync, healthImport) — prevents double-ingestion races when a user triggers multiple syncs in quick succession.
  • Retries 1–3 per function, depending on the failure mode (transient network = retry; bad data = fail fast).
  • Langfuse trace on every function. Tags: ['workflow', '<name>'].

Cron budget

:::tip Cron cost cut by 87% during migration. The original setup had two */15 * * * * crons for Gmail and GCal — 192 polling invocations per day, mostly no-op on idle accounts. Gmail is now push-only via Pub/Sub webhook; GCal pulls hourly since calendars move at human cadence. :::

Before:

  • gmailIngestCron · 96/day
  • gcalSyncCron · 96/day
  • morningBriefCron · 1/day
  • Total: 193/day

After:

  • gmailIngest · triggered by /api/webhooks/gmail (Pub/Sub)
  • gcalSyncCron · 24/day (hourly)
  • morningBriefCron · 1/day
  • Total: 25/day, ~87% reduction

Trigger topology

flowchart TB
  subgraph Time
    morn["cron 0 8 * * *"]
    hr["cron 0 * * * *"]
  end
  subgraph Push
    gps["/api/webhooks/gmail<br/>(Google Pub/Sub)"]
    chat["/api/chat onFinish"]
    tg["/api/telegram/webhook"]
    hi["/api/health/import"]
  end

  morn --> mbc[morningBriefCron]
  hr --> gcc[gcalSyncCron]
  mbc --> genBrief[generateBrief]
  gcc --> gcalSync[gcalSync]
  gps --> gmailIngest[gmailIngest]
  chat --> fe[factExtract]
  tg --> fe
  hi --> health[healthImport]

Adding a function

  1. Write the function in packages/agent/src/inngest/functions/<name>.ts.
  2. Export it and add to inngestFunctions in packages/agent/src/inngest/index.ts.
  3. Deploy. Next invocation of PUT /api/inngest auto-registers with Inngest Cloud.
  4. Wrap the handler body in startTrace(...)flushLangfuse() for observability.

See the Claude skill soma-inngest-workflows for detailed patterns.