88 lines
6.6 KiB
Markdown
88 lines
6.6 KiB
Markdown
# Add @suaveplan/delivery-tracking
|
|
|
|
**Package**: `@suaveplan/delivery-tracking`
|
|
**Status**: PROPOSED
|
|
**Type**: new-package
|
|
**Design**: ./design.md
|
|
**Spec delta**: ./specs/delivery-tracking/spec.md
|
|
|
|
## Why
|
|
|
|
Suaveplan ships a multi-channel campaign + transactional message pipeline. Without a channel-agnostic delivery event tracking surface, every channel (`emailer`, `notification-channel`, future SMS, push, in-app) has to invent its own way to capture sends/opens/clicks/bounces and roll them up for the dashboard. The upstream `@suaveplan/delivery-tracking` package solves that with a typed tracker, pluggable store, webhook parsers for the major email providers, and pixel/link generators. Genesis needs the same surface to unblock downstream consumers and to feed the OpenTelemetry-based analytics already wired up in the API host.
|
|
|
|
## What changes
|
|
|
|
This is a brand-new package on genesis. Public surface:
|
|
|
|
- ADDED: `DeliveryTracker` — Zod-validated write surface with lifecycle dedupe, typed `EventBus` emission, injectable `Clock`/`IdGenerator`, OTel counters.
|
|
- ADDED: `DeliveryAggregator` — campaign/contact rollups + time-series bucketing.
|
|
- ADDED: `DeliveryEventStore` interface + `InMemoryDeliveryEventStore` + `SurrealDeliveryEventStore` (positional SurQL).
|
|
- ADDED: `TrackingPixelGenerator` + `TrackingLinkWrapper` + shared `encodeToken`/`decodeToken` codec.
|
|
- ADDED: `WebhookEventParser` with `sendgrid`/`mailgun`/`ses`/`generic` built-ins and runtime `registerProvider`.
|
|
- ADDED: `DELIVERY_TRACKING_CODES` + `DeliveryTrackingError` (5 codes: `INVALID_INPUT`, `INVALID_TOKEN`, `UNSUPPORTED_PROVIDER`, `STORE_FAILURE`, `QUERY_FAILURE`).
|
|
- ADDED: `getDeliveryTrackingInstruments`/`resetDeliveryTrackingInstruments` OTel counter set.
|
|
- ADDED: Domain types — `DeliveryEvent`, `DeliveryEventType`, `DeliveryStats`, `CreateDeliveryEventInput`, `DeliveryEventQuery`, `TrackingConfig`, `TrackingTokenPayload`, `ClickTrackingTokenPayload`, `TimeSeriesBucket`, `DeliveryTrackerEventMap`.
|
|
|
|
## Out of scope
|
|
|
|
- HTTP route handlers for pixel/click redirect and webhook ingest. The package exposes the parser + tracker; mounting them into a Hono / api-plugin is the consumer's job.
|
|
- HMAC-signed tokens. The shared codec is XOR + base64url for URL compactness; consumers needing tamper resistance layer their own HMAC.
|
|
- Multi-tenant access control over reads. The aggregator returns whatever the store returns; authorization belongs to the API layer above.
|
|
|
|
## Source mapping
|
|
|
|
This change derives from `../suaveplan/enterprise/delivery-tracking.md`.
|
|
|
|
| Requirement | Source lines | Notes |
|
|
|---|---|---|
|
|
| Channel-agnostic delivery event tracking | L11-L13 | verbatim (purpose statement) |
|
|
| Zero browser / Node / React deps; Bun + Node compatible | L37 | verbatim — package is server-tier (uses `Buffer`) but no React/DOM dependency |
|
|
| Zod schemas for public types | L38 | enforced via `createDeliveryEventInputSchema` |
|
|
| Deterministic behaviour — injectable Clock + RNG | L40 | `Clock`, `IdGenerator` from `@suaveplan/types` are injectable |
|
|
| OTel instrumentation via shared adapter | L41 | `getDeliveryTrackingInstruments()` lazy-initialised counter set |
|
|
| Structured errors extending `@suaveplan/error` | L42 | `DeliveryTrackingError` + `defineCodes` |
|
|
| 100% test coverage (Istanbul) | L50 | enforced in `vitest.config.ts` thresholds |
|
|
| 100% docs coverage; sibling `.md` per impl file | L51 | every `.ts` outside the §4-exempt set has a sibling `.md` ≥200 words |
|
|
| File-size budget 250 soft / 300 hard / 500 test | L52 | every file inside budget |
|
|
| No mocks — real services / real OTel exporter | L53 | `InMemoryMetricExporter` from `@opentelemetry/sdk-metrics` |
|
|
| `sideEffects: false` + workspace internal deps | L55-L56 | verified in `package.json` |
|
|
| Public API stability via Zod schemas | L60 | `createDeliveryEventInputSchema` is the boundary contract |
|
|
| Structured logging support via `@suaveplan/logger` | L62 | consumed via `LoggerLike` from `@suaveplan/types` (caller-injected) |
|
|
| Plugin / extension interface | L69 | `WebhookEventParser` accepts custom `ProviderParser` registrations |
|
|
| Observability surface — counters + histograms | L70 | five named counters cover ingest, dedupe, webhook, token issuance, token rejection |
|
|
| Configurability — Zod-validated options w/ defaults | L71 | `DeliveryTrackerOptions`, `TrackingConfig` documented per-field |
|
|
| Resilience — typed errors on every IO path | L73 | every adapter exception wrapped in `STORE_FAILURE`/`QUERY_FAILURE` |
|
|
| Security — input validation at the boundary | L74 | every tracker write parses through Zod first |
|
|
|
|
### Requirements with no source line
|
|
|
|
| Requirement | Justification |
|
|
|---|---|
|
|
| Genesis runner-neutral test imports (`@suaveplan/testing/runner`) | Genesis-wide policy (CLAUDE.md rule 11), not package-specific. |
|
|
| Positional SurQL for SurrealDB store | Genesis convention established by `api-plugin-events` to keep query bodies auditable; upstream already followed this pattern. |
|
|
| Genesis `0.1.0` start version | Genesis lifecycle rule (CLAUDE.md). |
|
|
|
|
## Verification approach
|
|
|
|
Every requirement in the spec delta names concrete `it(...)` titles via `**Verified by:**` lines. The packaged test surface:
|
|
|
|
- `src/errors/delivery-tracking-error.test.ts` — registry + class behaviour.
|
|
- `src/otel/instruments.test.ts` — lazy construction + reset.
|
|
- `src/stores/memory/memory-store.test.ts` — interface contract, `computeStats`, filter/paging behaviour.
|
|
- `src/stores/surreal/surreal-store.test.ts` — positional SurQL bodies + error wrapping. Stubs the `SurrealAdapter.query` boundary because the surface is intentionally a thin SurQL emitter; a full testcontainer integration test belongs to `testing-integration-surrealdb` consumers per CLAUDE.md.
|
|
- `src/core/tracker/tracker.test.ts` — Zod boundary, lifecycle dedupe, bus emission, batch ingest, real `InMemoryMetricExporter` counter assertions.
|
|
- `src/core/aggregator/aggregator.test.ts` — rollups + time-series bucketing.
|
|
- `src/core/tracking-pixel/token-codec.test.ts` and `src/core/tracking-pixel/tracking-pixel.test.ts` — token round-trip + URL/HTML formatting.
|
|
- `src/core/tracking-link/tracking-link.test.ts` — single + bulk wrap + parse.
|
|
- `src/core/webhook-parser/webhook-parser.test.ts` — every built-in provider + extensibility + unsupported error.
|
|
|
|
## Impact
|
|
|
|
### Affected dependents
|
|
|
|
No dependents on genesis yet — this is a fresh landing. Run `bunx turbo ls --filter=...@suaveplan/delivery-tracking` to enumerate once the package is published. The `notification-channel` and `emailer` migrations being executed concurrently will consume this surface; their proposals own the wire-in step.
|
|
|
|
## Migration impact
|
|
|
|
None — additive only.
|