Skip to main content

QueuePort (constitution category 7)

  • Interface in packages/core: publish(queue, payload, opts), schedule(queue, payload, runAt). Payloads are Zod-validated envelopes: { job_id: uuidv7, tenant_id, correlation_id, kind, data }.
  • Binding per module composition root (R21). F1 default adapter: Vercel Queues. New vendors (QStash, etc.): ADR before first import.

Consumers

  • Idempotency MANDATORY: check/insert into core.processed_jobs (job_id, consumer, processed_at) inside the work transaction; duplicate ⇒ ack and skip. TTL cleanup job.
  • Retry policy (industry default, override per queue with rationale in module spec): 5 attempts, exponential backoff with jitter (base 30s, cap 1h).
  • Exhausted retries and hard failures MUST persist to core.dead_letters (job_id, queue, payload, attempts, last_error, status: pending_review|replayed|discarded, correlation_id) + BetterStack alert. Replay tooling lives in Softcrum Ops. High-volume successes are metrics, not rows.
  • Poison messages: schema-invalid payloads go straight to dead_letters (no retries).

Notification queues (with standards/notifications.md)

  • One queue per channel per rail: notif.{channel}.transactional and notif.{channel}.marketing.
  • Transactional ALWAYS has priority: marketing workers yield (pause/drain) when transactional depth > threshold.
  • Rate limiting: token bucket in Upstash per PROVIDER (global — e.g., Resend 10 rps is per team across ALL tenants) and per TENANT (fair use). 429 ⇒ backoff, never drop.
  • Email blasts: Resend batch endpoint (100/request) exclusively; NEVER sync contacts to Resend Audiences (our store is the source of truth).

Cron fan-out

  • Per-tenant fan-out pattern (existing) for: partition pre-creation, retention export+purge, nightly reconciliation, data-freshness recomputes (tier per entitlement: nightly/12h/6h/3h/1h), points_expiring_soon scans, usage snapshots (30 min).