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

5.7 KiB

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

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

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

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

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

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.