Initial Commit
This commit is contained in:
@@ -0,0 +1,87 @@
|
||||
# Suppression route schemas
|
||||
|
||||
## Purpose
|
||||
|
||||
The suppression-route schemas describe the OpenAPI contract for the
|
||||
`/suppression` route tree. The package's local
|
||||
`CampaignSuppressionStore` interface is multi-channel — keyed on the
|
||||
`(tenantId, channel, channelValue)` triple — so the schemas surface
|
||||
`channel` (free-string with `"email"` default) and `channelValue`
|
||||
rather than the email-only `channelType` shape used by the upstream
|
||||
port. This keeps the same routes reusable for SMS, push, and future
|
||||
delivery channels without a v2 endpoint family.
|
||||
|
||||
The schemas back four endpoints: list (paginated), create (single
|
||||
entry, defends against duplicates at the store), delete (by `id`), and
|
||||
check (point lookup by `channel + channelValue`). The check endpoint
|
||||
is read-only and idempotent, and is intended to be hit on every
|
||||
outgoing-message decision in a sender pipeline.
|
||||
|
||||
## Features
|
||||
|
||||
- `SuppressionReasonSchema` — 5-member enum mirroring
|
||||
`CampaignSuppressionReason`.
|
||||
- `CreateSuppressionSchema` — body validator with `channel: "email"`
|
||||
default and optional `source` / `metadata`.
|
||||
- `SuppressionQuerySchema` — list filters with coerced pagination.
|
||||
- `SuppressionCheckQuerySchema` — required `channel` + `channelValue`.
|
||||
- `SuppressionIdParamSchema` — `:id` path param.
|
||||
- 3 response schemas: `SuppressionResponseSchema`,
|
||||
`SuppressionListResponseSchema`, `SuppressionCheckResponseSchema`.
|
||||
|
||||
## Usage: Basic
|
||||
|
||||
```ts
|
||||
import { CreateSuppressionSchema } from "@suaveplan/api-plugin-campaigns";
|
||||
|
||||
const parsed = CreateSuppressionSchema.parse(await c.req.json());
|
||||
await store.add({ tenantId, ...parsed });
|
||||
```
|
||||
|
||||
## Usage: Advanced
|
||||
|
||||
```ts
|
||||
import {
|
||||
SuppressionCheckQuerySchema,
|
||||
SuppressionCheckResponseSchema,
|
||||
} from "@suaveplan/api-plugin-campaigns";
|
||||
|
||||
const q = SuppressionCheckQuerySchema.parse({
|
||||
channel: c.req.query("channel"),
|
||||
channelValue: c.req.query("channelValue"),
|
||||
});
|
||||
const entry = await store.check(tenantId, q.channel, q.channelValue);
|
||||
return c.json(
|
||||
SuppressionCheckResponseSchema.parse({
|
||||
suppressed: entry !== null,
|
||||
entry,
|
||||
}),
|
||||
200
|
||||
);
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
- `CreateSuppressionSchema`, `SuppressionQuerySchema`,
|
||||
`SuppressionCheckQuerySchema`, `SuppressionIdParamSchema` — input
|
||||
validators.
|
||||
- `SuppressionResponseSchema`, `SuppressionListResponseSchema`,
|
||||
`SuppressionCheckResponseSchema` — response shapes.
|
||||
- `type CreateSuppressionBody`, `SuppressionQueryInput` — inferred TS
|
||||
types.
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
The `channel` field defaults to `"email"` rather than enumerating
|
||||
known channels because the suppression store is intentionally
|
||||
channel-agnostic (a tenant may opt into SMS or push channels without
|
||||
schema changes here). The trade-off: the OpenAPI consumer sees a
|
||||
free-string rather than a closed enum. The enforcement boundary moves
|
||||
to the recipient delivery pipeline, which always knows its channel
|
||||
identifier before consulting suppression.
|
||||
|
||||
`metadata` is `z.record(z.string(), z.unknown())` so adapters can
|
||||
attach provider-specific context (bounce subcodes, complaint headers,
|
||||
…) without a schema migration. The schema is **strict-on-input** only
|
||||
for fields it owns; per-channel extensions live inside `metadata` and
|
||||
are validated at the integration boundary, not here.
|
||||
Reference in New Issue
Block a user