Files
2026-05-27 23:29:53 +01:00

3.5 KiB

Change: campaigns

  • Change ID: campaigns
  • Package: @suaveplan/campaigns
  • Path: packages/communication/emails/campaigns/
  • Tier: universal
  • Date: 2026-05-24

Summary

Land @suaveplan/campaigns on genesis at packages/communication/emails/campaigns/. The package combines audience targeting, template binding, channel dispatch, and schedule management into managed send workflows. It exposes a state-machine-driven CampaignEngine, a throttle-aware BatchPlanner, a composable six-step CampaignWorkflow, and a pluggable CampaignStore interface with an in-memory reference implementation.

Why

@suaveplan/campaigns is the orchestration layer above @suaveplan/emailer, @suaveplan/email-templates, @suaveplan/suppression, and @suaveplan/contacts. Without it, callers have to hand-roll lifecycle FSMs and throttle math, which has caused drift between historical implementations.

Scope

  • Engine: CampaignEngine with create, schedule, send, pause, resume, cancel, complete, fail, duplicate, updateStats, getById, list. Injectable Clock/IdGenerator/LoggerLike.
  • State machine: CampaignStateMachine with nine canonical transitions, transition/canTransition/getAvailableEvents.
  • Batch planner: BatchPlanner with planBatches and calculateTotalDuration, throttle-aware delay calculation, injectable Clock.
  • Workflow: CampaignWorkflow with buildSendPipeline (6 steps) and run (sequential executor that wraps step errors in CampaignsError(WORKFLOW_STEP_FAILED)).
  • Store: CampaignStore interface + InMemoryCampaignStore reference implementation.
  • Errors: CAMPAIGNS_CODES (8 codes) + CampaignsError with explicit public ctor.
  • Telemetry: 5 counters + 2 histograms via @opentelemetry/api.
  • Types: 13 exported types/interfaces including Campaign, CampaignStatus, CampaignType, AudienceConfig, ThrottleConfig, ABTestConfig, CampaignStats, CreateCampaignInput, etc.

Out of scope (deliberate)

None. The package lands at full functional completeness. Storage adapters (SurrealDB, Postgres) and the HTTP plugin live in sibling packages and are tracked by their own OpenSpec changes.

Deviations from upstream

  • Single-folder feature layout (engine/, state-machine/, batch-planner/, workflow/, store/, types/, error-codes/, telemetry/) instead of core/<feature>/ nested under upstream's core/. Aligns with the 3-level package layout rule.
  • Errors thrown by every public method go through CampaignsError + CAMPAIGNS_CODES instead of raw Error.
  • Zod-validated boundaries on CampaignEngineOptions, CreateCampaignInput, BatchPlannerOptions, BatchConfig, ThrottleConfig.
  • OTel instrumentation added throughout (5 counters + 2 histograms).
  • Injectable Clock, IdGenerator, LoggerLike on engine; injectable Clock on batch planner and in-memory store.
  • CampaignWorkflow.run added so the package owns its own sequential executor.

Verified by

  • bunx turbo lint typecheck test --filter=@suaveplan/campaigns — green.
  • bunx turbo test:coverage --filter=@suaveplan/campaigns — 100/100/100/100 (statements/branches/functions/lines).
  • bunx turbo build --filter=@suaveplan/campaigns — emits dist/.
  • bun scripts/lint/check-feature-layout.ts — no campaigns violations.
  • bun scripts/lint/check-test-runner-imports.ts — no campaigns violations.
  • grep -rn "biome-ignore\|TODO\|FIXME\|XXX" packages/communication/emails/campaigns/src/ — empty.