196 lines
5.7 KiB
Markdown
196 lines
5.7 KiB
Markdown
# OpenSpec Change: enterprise-delivery-tracking
|
|
|
|
- **Change ID**: enterprise-delivery-tracking
|
|
- **Package**: `@suaveplan/delivery-tracking`
|
|
- **Category**: universal
|
|
- **Target path**: `universal/email/delivery-tracking`
|
|
- **Status**: IN PROGRESS
|
|
- **Date**: 2026-04-24
|
|
- **Reference spec**: `/mnt/s/web/suaveplan/enterprise/delivery-tracking.md`
|
|
|
|
---
|
|
|
|
## Summary
|
|
|
|
Deliver `@suaveplan/delivery-tracking` on the genesis branch from scratch at enterprise quality. The package provides channel-agnostic delivery event tracking: a typed `DeliveryEvent` model covering all SMTP/webhook status transitions (`queued`, `sent`, `delivered`, `bounced`, `complained`, `unsubscribed`, `opened`, `clicked`), a `DeliveryTracker` facade with pluggable store adapters, webhook payload normalization for major ESP providers (generic interface — no provider SDK deps), bounce classification (hard/soft/transient), unsubscribe propagation to `@suaveplan/suppression`, and per-campaign/per-recipient analytics aggregation. Genesis adds structured errors, Zod schemas, OTel instrumentation, and property-based tests.
|
|
|
|
---
|
|
|
|
## Current-state audit (genesis)
|
|
|
|
The package does not exist on genesis yet — this is a fresh build.
|
|
|
|
---
|
|
|
|
## Available workspace deps on genesis
|
|
|
|
| Package | Role |
|
|
|---|---|
|
|
| `@suaveplan/error` (`workspace:^`) | `DeliveryTrackingError`; `DLVR.*` error codes |
|
|
| `@suaveplan/types` (`workspace:^`) | `LoggerLike`, `Clock`, `EventBusLike`, `StorageLike` |
|
|
| `@suaveplan/events` (`workspace:^`) | Delivery event propagation |
|
|
| `@suaveplan/suppression` (`workspace:^`) | Unsubscribe/bounce → suppression list write |
|
|
| `@suaveplan/crypto` (`workspace:^`) | HMAC-SHA256 webhook signature verification |
|
|
| `zod` (`catalog:forms`) | Schema validation |
|
|
| `@opentelemetry/api` (`catalog:observability`) | Spans + counters |
|
|
|
|
Dev deps: `vitest`, `@vitest/coverage-istanbul`, `fast-check` (all `catalog:testing`).
|
|
|
|
---
|
|
|
|
## Public API surface
|
|
|
|
### Core types
|
|
|
|
```ts
|
|
type DeliveryEventType =
|
|
| 'queued' | 'sent' | 'delivered' | 'deferred'
|
|
| 'bounced' | 'complained' | 'unsubscribed'
|
|
| 'opened' | 'clicked'
|
|
|
|
type BounceType = 'hard' | 'soft' | 'transient'
|
|
|
|
interface DeliveryEvent {
|
|
id: string
|
|
messageId: string
|
|
campaignId?: string
|
|
recipientEmail: string
|
|
type: DeliveryEventType
|
|
timestamp: Date
|
|
bounceType?: BounceType
|
|
bounceCode?: string
|
|
clickUrl?: string
|
|
userAgent?: string
|
|
ipAddress?: string
|
|
metadata: Record<string, unknown>
|
|
}
|
|
|
|
interface DeliveryAnalytics {
|
|
campaignId: string
|
|
total: number
|
|
sent: number
|
|
delivered: number
|
|
bounced: number
|
|
complained: number
|
|
unsubscribed: number
|
|
opened: number
|
|
clicked: number
|
|
deliveryRate: number
|
|
openRate: number
|
|
clickRate: number
|
|
bounceRate: number
|
|
}
|
|
```
|
|
|
|
### `DeliveryTracker`
|
|
|
|
```ts
|
|
interface DeliveryTracker {
|
|
record(event: DeliveryEventInput): Promise<DeliveryEvent>
|
|
get(eventId: string): Promise<DeliveryEvent | undefined>
|
|
listByMessage(messageId: string): Promise<DeliveryEvent[]>
|
|
listByRecipient(email: string, options?: ListOptions): Promise<PaginatedResult<DeliveryEvent>>
|
|
analytics(campaignId: string): Promise<DeliveryAnalytics>
|
|
}
|
|
|
|
function createMemoryDeliveryTracker(options?: MemoryTrackerOptions): DeliveryTracker
|
|
```
|
|
|
|
### Webhook normalizer
|
|
|
|
```ts
|
|
interface WebhookNormalizeOptions {
|
|
secret?: string // HMAC secret for signature verification
|
|
providerHint?: string // 'sendgrid' | 'mailgun' | 'ses' | 'postmark' | 'generic'
|
|
}
|
|
|
|
function normalizeWebhookPayload(
|
|
body: unknown,
|
|
headers: Record<string, string>,
|
|
options?: WebhookNormalizeOptions
|
|
): DeliveryEventInput[]
|
|
```
|
|
|
|
### Error codes
|
|
|
|
```ts
|
|
type DeliveryTrackingErrorCode =
|
|
| 'DLVR_EVENT_NOT_FOUND'
|
|
| 'DLVR_INVALID_PAYLOAD'
|
|
| 'DLVR_SIGNATURE_FAILED'
|
|
| 'DLVR_STORE_FAILED'
|
|
| 'DLVR_ANALYTICS_FAILED'
|
|
```
|
|
|
|
---
|
|
|
|
## Genesis additions
|
|
|
|
1. **`DeliveryTrackingError`** extends `SuaveplanError` with five stable codes.
|
|
2. **Zod schemas** for `DeliveryEvent`, `DeliveryAnalytics`, `WebhookNormalizeOptions`, all provider payload shapes.
|
|
3. **Suppression auto-propagation** — `bounced` (hard) and `complained` events automatically call `@suaveplan/suppression` to add the recipient.
|
|
4. **Webhook signature verification** — constant-time HMAC via `@suaveplan/crypto`; configurable clock-skew window.
|
|
5. **OTel instrumentation** — counter `delivery.event_total` with attribute `delivery.event_type`; histogram `delivery.analytics_duration_ms`.
|
|
6. **Property-based tests** — analytics rate calculations for arbitrary event sets.
|
|
|
|
---
|
|
|
|
## File layout
|
|
|
|
```text
|
|
universal/email/delivery-tracking/
|
|
package.json
|
|
tsconfig.json
|
|
vitest.config.ts
|
|
src/
|
|
index.ts
|
|
error-codes.ts
|
|
constants/
|
|
index.ts
|
|
types/
|
|
common.ts
|
|
common.md
|
|
core/
|
|
delivery-tracker/
|
|
delivery-tracker.ts
|
|
delivery-tracker.md
|
|
delivery-tracker.types.ts
|
|
delivery-tracker.test.ts
|
|
index.ts
|
|
memory-tracker/
|
|
memory-tracker.ts
|
|
memory-tracker.md
|
|
memory-tracker.test.ts
|
|
index.ts
|
|
analytics/
|
|
analytics-aggregator/
|
|
analytics-aggregator.ts
|
|
analytics-aggregator.md
|
|
analytics-aggregator.test.ts
|
|
index.ts
|
|
webhook/
|
|
webhook-normalizer/
|
|
webhook-normalizer.ts
|
|
webhook-normalizer.md
|
|
webhook-normalizer.test.ts
|
|
index.ts
|
|
signature-verifier/
|
|
signature-verifier.ts
|
|
signature-verifier.md
|
|
signature-verifier.test.ts
|
|
index.ts
|
|
__benchmarks__/
|
|
delivery-tracking.bench.ts
|
|
tests/
|
|
integration.test.ts
|
|
property.test.ts
|
|
```
|
|
|
|
---
|
|
|
|
## Impact
|
|
|
|
**Downstream consumers**: `@suaveplan/campaigns`, any server-side email dispatch service.
|
|
|
|
**Workspace dep prerequisites**: `@suaveplan/error`, `@suaveplan/types`, `@suaveplan/events`, `@suaveplan/suppression`, `@suaveplan/crypto`, `@suaveplan/pagination`.
|