0057: Swappable Notification Channels
Date: 2025-12-04
Status: Accepted
Context
The notification-service must support multiple modes of communication (Email, SMS, WebSocket) and potentially multiple providers for each mode (e.g., switching from SendGrid to AWS SES, or Twilio to MessageBird). Tightly coupling the core logic to a specific provider's SDK would make the system brittle and difficult to adapt to changing business needs or costs.
Decision
We will apply the Ports and Adapters (Hexagonal) pattern to notification delivery.
-
The Port (Interface): The application layer will define a generic
Channelinterface.type Channel interface {
Send(ctx context.Context, notification *Notification) error
Supports(channelType ChannelType) bool
} -
The Adapters (Infrastructure): Concrete implementations will reside in the infrastructure layer:
- EmailChannel: Uses
github.com/wneessen/go-mailto support SMTP and XOAUTH2. It will support CID Embedding for images to ensure emails render correctly in offline/hybrid environments without external CDNs. - SmsChannel:
- Generic Webhook Adapter: The primary extension point. It POSTs a standard JSON payload to a configured URL. This allows integrating with any SMS provider via a simple serverless function or bridge, keeping the core binary lightweight.
- Native Adapters: Optional, built-in adapters for market leaders (e.g., Twilio) where the SDK overhead is justified.
- WebsocketChannel: A "Push" adapter that publishes the notification payload to a NATS subject (
events.user.{id}.notification). It does not hold WebSocket connections itself; it relies on the NATS infrastructure to handle the active connections to frontend clients. - LogChannel: A dev-only adapter that prints to
stdout.
- EmailChannel: Uses
-
Factory Pattern: A
ChannelFactorywill be used to instantiate and retrieve the correct adapter at runtime based on thechannelfield in the notification request and the configuredprofile. -
NATS Auth Callout (WebSockets): For the WebSocket channel, security is handled by NATS delegating authentication to the
iam-servicevia an Auth Callout. Thenotification-serviceis only responsible for publishing the message to the correct subject.
Consequences
Positive
- Vendor Agnostic: We can switch providers by changing configuration, not code.
- Testability: We can easily mock the
Channelinterface for unit tests, or use theLogChannelfor local integration testing. - Extensibility: Adding a new channel type (e.g., Push Notifications) only requires implementing the interface and registering it in the factory.
- Lightweight Binary: By prioritizing the Generic Webhook adapter for SMS, we avoid bloating the binary with dozens of provider-specific SDKs.
Negative
- Abstraction Overhead: The generic interface may not support every specialized feature of a specific provider (e.g., Twilio-specific template logic).
- Configuration Complexity: Managing profiles and adapters requires a structured configuration file.