@lssm/integration.opa-notification-bridge
v2.0.2
Published
Bridge implementing the organization-planning CommunicationSendAdapterPort + JobsScheduleAdapterPort over injected email (EmailOutboundProvider), SMS (SmsProvider), and durable job-queue (JobQueue) transports. Feature-flag gated, fail-closed, no secrets i
Readme
@lssm/integration.opa-notification-bridge
Binds the organization-planning communication + jobs adapter ports to injected notification transports — without reimplementing any provider SDK:
CommunicationSendAdapterPort(sendConfirmation/sendReminder/sendFollowUp) → an injectedEmailOutboundProvider(gmail/postmark/resend) for email, or an injectedSmsProvider(Twilio, …) for SMS.JobsScheduleAdapterPort(scheduleReminder) → an injected durableJobQueue(Scaleway SQS / GCP Cloud Tasks / Pub-Sub) — reminders are enqueued so delivery survives restarts; confirmations/follow-ups send immediately.
Posture: feature-flag gated + fail-closed
- Email and SMS sends are gated behind
OPA_NOTIFY_EMAIL/OPA_NOTIFY_SMS(OPA_NOTIFY_FLAGS), both defaulting safe (off). - An unconfigured channel — flag off, transport absent, missing recipient, or no
resolved target — yields a no-op receipt (
success: true,outputRefs: ['opa-notify-noop:<reason>']). It never throws. - No secrets in code. Every transport is injected via the constructor.
Usage
import { createOpaNotificationBridge, OPA_NOTIFY_FLAGS } from '@lssm/integration.opa-notification-bridge';
const { communication, jobs } = createOpaNotificationBridge({
email, // e.g. GmailOutboundProvider / PostmarkEmailProvider
sms, // e.g. TwilioSmsProvider
jobs: jobQueue, // e.g. ScalewaySqsQueue / GcpCloudTasksQueue / MemoryJobQueue
defaultFromEmail: '[email protected]',
flags: { [OPA_NOTIFY_FLAGS.EMAIL]: true, [OPA_NOTIFY_FLAGS.SMS]: false },
resolveNotification: (handoff, kind) => ({
channel: 'email',
email: lookupRecipientEmail(handoff),
body: renderBody(handoff, kind),
}),
});
// worker-1 threads `communication` + `jobs` into buildOpaOps.The host owns resolveNotification (recipient/PII derivation) — the bridge never
reads recipient data from the handoff itself.
Booking delivery uses enqueueBookingNotification. Its durable dedupe key is
derived from tenant, booking, lifecycle version, kind, and recipient digest.
Only manage access, approval, and reminder kinds are accepted: Google
sendUpdates=all owns generic auto-confirm create/change/cancel delivery.
Outbox payloads contain a protected delivery ref and recipient digest, never a
recipient address or manage bearer; the delivery worker dereferences protected
material only when sending.
Delivery adapters receive the tenant reference while resolving protected
material and receive the same tenant reference plus the durable dedupe key when
sending. Hosts must forward that key to the protected sender as its idempotency
key. The Postgres host atomically claims canonical outbox rows with an expiring
owner lease, then settles or retries only with the same tenant and owner fence.
Delivery claims are likewise expiring and token-fenced by tenant and dedupe key:
crashed claims are reclaimable, while stale workers cannot complete or release a
successor's claim. Immediately before provider dispatch, the host persists a
dispatch_ambiguous marker. A lost provider acknowledgement therefore stays
fail-closed instead of becoming retryable; an operator must reconcile the exact
tenant/dedupe-bound provider receipt before completion resumes without another
send.
