@zucker-framework/notification
v1.0.16
Published
Notification providers and durable delivery primitives for Zucker applications
Readme
Notification and mail
The existing notification providers and durable-delivery contracts remain available. Email providers also implement MailProvider.deliver(envelope, context), returning the provider receipt, status and error code. The envelope includes sender, recipients, reply-to, CC/BCC, HTML/text and attachments. Provider options are explicit; packages do not read application environment variables. SMTP does not claim native idempotency support.
Configure ZuckerMailModule.forRoot() / forRootAsync() once at the application composition root. Supply MailServiceOptions: the existing DatabaseAdapter, provider order, template catalog configuration, sender/rate policy, and optional recipient-locale, content-rendering and tracking-metadata callbacks. Feature modules inject MailService, MailTemplateCatalog or MailHistoryService; they do not register a second sender. Shutdown aborts queued/in-flight sends and closes providers.
MailTemplateCatalog maps configured locales to database content columns, lets active database overrides supersede immutable built-ins, and provides management/seed APIs. Host template text, preview samples and content slots remain outside the package. Template interpolation uses the core parser, escapes HTML, rejects dangerous URL schemes and preserves unknown variables. MailService.sendResolved() rejects missing data before dispatch.
MailHistoryService queries, paginates, deduplicates and expires the configured delivery history model. It uses the existing email-history fields (recipient, userId, template, provider, messageId, status, errorMessage, metadata, createdAt). Successful delivery waits for a best-effort final receipt write; that history does not constitute a durable outbox or guarantee exactly-once dispatch across process crashes.
MailEngagementService receives a parameterized PostgreSQL query callback and a validated table name. It atomically merges first/last delivery/open/click/bounce/complaint timestamps in JSONB and requests retries for recent callbacks that precede the history row. Statistics (stats() overall, byTemplate() per template) deduplicate message IDs, report delivered/bounced/complained counts and calculate opens/clicks inside the delivered cohort; tracking eligibility and the reporting window are host policy. MailServiceOptions.linkTracking adds per-message query parameters, such as UTM values, to links that point at the host's own origins when a message is sent, so visits and conversions from mail reach the host's existing attribution. ResendWebhookService verifies Svix signatures and persists engagement before calling the host's suppression callback. createResendWebhookController in the admin package supplies the public HTTP endpoint and retryable acknowledgement behavior.
MailCampaignService uses the Framework queue package and the host's campaign model. Configure it once with the existing Redis URL, queue name, worker enablement, concurrency/deduplication window, validation, audience count, batch size and a loadBatch(params, cursor, limit) callback. The callback supplies recipients ordered by stable ID, strictly after the cursor; the Framework worker owns iteration and progress. Rows use pending/running/completed/failed states and atomic claims. Recovery of interrupted running rows completes before the worker starts; this recovery mode requires exactly one campaign worker. Producer-only instances disable their worker. Queue-add failures mark the row failed. Each campaign resolves its template once and persists batch progress. dispatchMailBatch performs bounded concurrency, recent-recipient deduplication, failure sampling and daily-quota stopping. Audience policy stays in the host.
Dependencies flow from admin → notification → queue/core; mail never imports application models or product modules. These APIs are additive source changes. Publish a new immutable package version and update consumer pins before registry-based deployment; local workspace verification alone does not publish them.
Alert delivery
AlertNotificationService performs best-effort fan-out using host-provided channels, default severity routing, a deduplication window and fingerprint policy. It reuses InMemoryNotificationDedup; one singleton disposes the cache timer and drains active sends during shutdown. Results distinguish sent, partial, failed, skipped and deduplicated delivery. Attempts are marked before dispatch, so concurrent failures remain throttled; this is not durable delivery.
createWebhookAlertChannel({ url, format }) supports DingTalk, WeCom, Slack and generic JSON payloads through the existing WebhookProvider. DingTalk/WeCom application acknowledgements are checked in addition to HTTP status. WebhookProvider.sendPayload accepts custom JSON envelopes and optional response validation under the same timeout; the existing send(NotifyMessage) behavior remains available. Webhook URLs are never included in success logs. Hosts own redaction and recipient routing; email channels must be explicitly supplied by a real sender/recipient policy rather than a placeholder transport.
Provider canaries
runMailProviderCanary receives an owned provider and explicit message/key. It delivers once, or twice with the same key to prove a stable messageId, then closes the provider on every outcome. runWebhookProviderCanary reuses WebhookProvider with redirects disabled, an injected fetch if required, and a bounded timeout; it sends only once and never claims webhook idempotency. Canary errors have stable codes and optional HTTP status. Environment authorization, recipient/host allowlists, run IDs, headers and message content remain caller policy. No probe runs on import or startup.
PostgreSQL durable delivery
PostgresNotificationDeliveryRepository implements NotificationDeliveryRepository against a minimal NotificationSqlClient.query(sql, parameters) port. Apply the exported notificationDeliverySchemaSql through the consumer's migrations, or map the equivalent table and constraints in its schema. This adapter owns no pool, environment configuration or recipients. Use the host's existing transaction client when a business result, private delivery envelope and notification metadata must commit together.
The repository provides atomic idempotent enqueue, SKIP LOCKED claims, monotonic attempts, random lease tokens, fenced renew/completion/retry/failure writes and expired-lease recovery. idempotencyFingerprint must cover the immutable delivery identity; reusing a key with different content is an error. PostgreSQL clients must preserve UTC Date values, including their session timezone when a driver serializes naive timestamps. Keep recipients and message content in consumer-private storage referenced by deliveryRef; metadata and public status responses should not expose them.
Compose it with DurableNotificationWorker in one application-owned host. Forward the same key to a provider that guarantees idempotent delivery and respect that provider's deduplication retention window. SMTP has no such guarantee: the consumer must persist dispatch evidence before sending and stop for review after an ambiguous response or a crash beyond that boundary, instead of automatically sending again. Provider configuration, permitted recipients, immutable account scope, replay-age policy and recovery authority remain consumer policy. Account verification/reset bearer tickets should not be persisted in ordinary durable message content.
