Notification Service — Overview
The Notify Service is the single notification delivery gateway for
all modules. Modules never send emails, SMS messages, or Telegram
messages directly — they call kernel.notify().send() and the service
handles delivery, retries, history, and rate limiting.
The service is built around a pluggable channel architecture. Two
channels are built in for every deployment. All other channels
(email, sms, telegram, discord, slack, webhook, etc.)
are installed as adapters registered via the module manifest.
Technical Stack
| Component | Technology | Role |
|---|---|---|
| Go runtime | nhooyr.io/websocket + goroutines | WebSocket server (thousands of concurrent connections) |
| Message queue | RabbitMQ (rabbitmq/amqp091-go) | Async delivery queue + retry |
| Replay buffer | Valkey (AOF persistence) | WebSocket reconnect replay — 100 messages, 1 h TTL |
| Storage | PostgreSQL | Notification history (90 days), failed log (180 days) |
Built-in Channels
Two channels are always present — no adapter installation required:
| Channel | Transport | Use case |
|---|---|---|
| WebSocket | wss://notify.platform.io/ws | Real-time in-browser notifications, live dashboard updates |
| Browser Push | Service Worker + FCM / APNs | Push notifications when the browser tab is not active |
Pluggable Channels (Adapters)
All other channels are delivered via registered adapters. An adapter is
a module that implements the NotificationChannel interface and declares
itself in module.manifest.json under notificationChannels. When
installed, the channel automatically appears in Settings → Notifications.
| Adapter | Typical use case |
|---|---|
| Email (SMTP / SendGrid / Mailgun) | Transactional emails, newsletters |
| SMS (Twilio / Vonage / any gateway) | OTP codes, alerts |
| Telegram | Bot notifications |
| Discord | Dev alerts to a channel |
| Slack | Team notifications |
| WhatsApp / Viber / any messenger | Regional preference |
| Webhook | POST to any external URL |
The platform does not bundle specific SMTP or SMS providers. A module installs the adapter for whichever provider the tenant prefers.
Async Delivery Model
Every send() call returns 202 Accepted immediately. Delivery is
asynchronous via RabbitMQ. This means the caller is never blocked
by a slow external provider (SendGrid latency, Telegram rate limit,
etc.).
Module code: kernel.notify().send(…)
│
│ POST /api/v1/notifications → 202 Accepted
▼
Notify Service enqueues to RabbitMQ
Returns { notificationId }
│
│ Worker dequeues and calls adapter
▼
Adapter delivers (email/telegram/…)
Status stored in PostgreSQL
│
▼
Module polls GET /api/v1/notifications/:id
or receives notify.notification.sent event
If delivery fails, the service retries automatically using exponential
backoff (up to 5 attempts). After exhausting retries the notification
is written to the failed table and made visible in Admin →
Notifications → Failed.
Tenant Isolation
Every tenant has an isolated RabbitMQ queue:
notify.outgoing.{tenantId}. One tenant cannot block or delay
notifications for another tenant — queue exhaustion only affects the
tenant whose queue is full.
Queue policy per tenant queue:
| Parameter | Value |
|---|---|
| Max length | 10 000 messages (x-max-length: 10000) |
| Overflow policy | x-overflow: reject-publish |
| On overflow | 503 Service Unavailable for new batch calls |
| Env override | NOTIFY_RABBITMQ_MAX_QUEUE_LENGTH=10000 |
REST API at a Glance
| Method | Endpoint | Description |
|---|---|---|
POST | /api/v1/notifications | Send notification |
POST | /api/v1/notifications/batch | Send to multiple recipients |
GET | /api/v1/notifications/:id | Notification status |
GET | /api/v1/notifications | Notification history (paginated) |
POST | /api/v1/notifications/:id/retry | Manual retry of failed notification |
GET | /api/v1/notifications/failed | Failed notifications (paginated) |
POST | /api/v1/notifications/templates | Create template |
GET | /api/v1/notifications/templates | List templates |
POST | /api/v1/notifications/channels | Register channel adapter |
GET | /api/v1/notifications/channels | List registered channels |
History Retention
| Store | Retention | Purpose |
|---|---|---|
| PostgreSQL notification history | 90 days (NOTIFY_HISTORY_RETENTION_DAYS) | Standard delivery log |
PostgreSQL notifications_failed | 180 days | Incident investigation |
| Audit Service | 7 years | Every send recorded as audit event |
Related Pages
- Sending Notifications —
send(),sendBatch(), priorities - Channels — pluggable channel interface, registration
- Templates —
sendFromTemplate(), variable substitution (Batch 15) - WebSocket Protocol — heartbeat, reconnect, replay buffer (Batch 15)
- Retry & DLQ — backoff schedule, manual retry, fallback policy (Batch 15)
- Rate Limiting — per-tenant / per-module limits (Batch 15)