기술 블로그

← 모든 글

· XingAI Invest AI

Worker-Cache Email Digests: Notifications Without Moving Decisions Into the API

UI는 한국어입니다. 글 본문은 아직 영어 또는 중국어만 있습니다.

The product need

An investment dashboard is useful when the user opens it. A morning decision brief needs to arrive before the user asks.

For Invest AI, that meant sending daily AI picks by email at fixed Central Time checkpoints. The tempting implementation is a cron endpoint:

cron -> FastAPI -> compute today's picks -> send email

We did not choose that.

The boundary

Invest AI follows a strict rule:

Worker/core computes decisions.
FastAPI reads cache.
Frontend renders.

So the email job also reads the worker cache. It does not calculate market regime, consensus, allocation, or ranking. The email is a delivery surface, not a decision engine.

What gets emailed

The scheduler reads:

  • consensus
  • market_regime
  • top_signals
  • explicit signal-driver fields already written by the worker

That makes the email reproducible: it describes the same cached decision package the dashboard would show.

Why this matters

Notification systems are sneaky. If the email says "Buy KVUE" but the dashboard says "Risk-Off, protect capital," trust breaks immediately. Keeping both surfaces tied to the same cache prevents that split.

The worker can send a short daily brief. The dashboard can expose the full state. The API stays boring.

Operational detail

We send through Resend. Because brand-domain verification can fail or lag, the worker tries the configured sender first and falls back to Resend's default sender when the provider rejects the brand domain. That is not the final brand state, but it prevents the scheduled product loop from silently dropping.

Takeaway

If a notification contains decision semantics, treat it like another decision surface. It should read the same worker-owned cache as the product UI, not invent a second answer in a cron endpoint.

Further reading: ADR-016 (docs/adr/016-worker-cache-email-digest.md) and ADR-012 (docs/adr/012-decision-cache-boundary.md).