# Campaign action routes ## Purpose This module owns the campaign action routes — state-machine transitions (`/send`, `/pause`, `/cancel`), the `/duplicate` shallow-copy endpoint, the `/test` ad-hoc send, and the two delivery-tracking-backed read endpoints (`/stats` and `/events`). State transitions are authoritative: invalid transitions raise `CAMPAIGNS_API.INVALID_STATE_TRANSITION` mapped to HTTP 409 so clients can drive UI affordances (enable/disable a "Pause" button) off a stable code rather than parsing error messages. The action routes consume the in-domain `CampaignStateMachine` from `@suaveplan/campaigns`. Every transition decision flows through that single source of truth — no string-comparisons of `status` are duplicated at the route layer, eliminating drift between the engine and the HTTP surface. ## Features - 3 transition routes: `buildSendCampaignRoute`, `buildPauseCampaignRoute`, `buildCancelCampaignRoute`. - 1 clone route: `buildDuplicateCampaignRoute` — copies metadata and audience/template config, resets stats, clears `sentAt`/`completedAt`. - 1 test-send route: `buildTestSendRoute` — validates a recipient list of 1–10 email addresses and echoes them back. - 2 reporting routes consuming `DeliveryEventStore`: `buildCampaignStatsRoute`, `buildCampaignEventsRoute`. - Transition guard raises `INVALID_STATE_TRANSITION` (HTTP 409) when `CampaignStateMachine.transition` rejects the (status, event) pair. ## Usage: Basic ```ts import { buildSendCampaignRoute } from "@suaveplan/api-plugin-campaigns"; const entry = buildSendCampaignRoute(); app.post(entry.route.path, (c) => entry.handler(c)); ``` ## Usage: Advanced ```ts import { buildCampaignEventsRoute, buildCampaignStatsRoute, buildCancelCampaignRoute, buildDuplicateCampaignRoute, buildPauseCampaignRoute, buildSendCampaignRoute, buildTestSendRoute, } from "@suaveplan/api-plugin-campaigns"; const actionEntries = [ buildSendCampaignRoute(), buildPauseCampaignRoute(), buildCancelCampaignRoute(), buildDuplicateCampaignRoute(), buildTestSendRoute(), buildCampaignStatsRoute(), buildCampaignEventsRoute(), ]; ``` ## API Reference - `buildSendCampaignRoute(): PluginRouteEntry` - `buildPauseCampaignRoute(): PluginRouteEntry` - `buildCancelCampaignRoute(): PluginRouteEntry` - `buildDuplicateCampaignRoute(): PluginRouteEntry` - `buildTestSendRoute(): PluginRouteEntry` - `buildCampaignStatsRoute(): PluginRouteEntry` - `buildCampaignEventsRoute(): PluginRouteEntry` - `TestSendSchema`, `EventQuerySchema`, `DeliveryStatsResponseSchema` — Zod schemas re-exported for documentation generators and tests. ## Implementation Notes `applyTransition` wraps `CampaignStateMachine.transition` in a `CampaignsApiError` so a failed transition surfaces as `CAMPAIGNS_API.INVALID_STATE_TRANSITION` rather than the domain-package's `CampaignsError` (which carries an unrelated namespace). Wrapping at this seam keeps the route layer's error vocabulary self-contained: every error a client sees from the campaigns plugin carries a `CAMPAIGNS_API.*` code. The `/duplicate` handler intentionally copies all configuration fields (template, audience, throttle, abTestConfig) but resets the volatile fields (`id`, `status`, `stats`, `sentAt`, `completedAt`, `createdAt`, `updatedAt`). The `(copy)` suffix is appended to the name so the duplicate is visually distinct in listing UIs without forcing the operator to provide a new name up front. The route handlers' OTel attributes record `route` plus `outcome` so an operator can chart per-route success/error counters without parsing log messages.