Notify REST API Reference
The Notify Service is the single delivery hub for all platform notifications.
Delivery channels are plugin-based — the core provides the
NotificationChannel interface and adapters (Email, SMS, Telegram, Slack,
WebSocket, Browser Push, Webhook) are installed as modules.
Built-in channels that require no adapter: WebSocket and Browser Push (FCM/APNs).
See the REST API Overview for authentication, error format, pagination, and rate limiting. See the WebSocket Protocol for the full real-time specification.
For layout brevity, the /api/v1 base path prefix is omitted from the
endpoint tables below.
Endpoints
| Endpoint | Type | Description |
|---|---|---|
POST /notifications | CRUD | Send a notification |
POST /notifications/batch | Action | Send to multiple recipients |
GET /notifications/:id | CRUD | Notification status |
GET /notifications | CRUD | Notification history (paginated) |
POST /notifications/:id/retry | Action | Retry a failed notification |
GET /notifications/failed | CRUD | Failed notifications queue (paginated) |
POST /notifications/templates | CRUD | Create a notification template |
GET /notifications/templates | CRUD | List templates |
POST /notifications/channels | CRUD | Register a channel adapter |
GET /notifications/channels | CRUD | List registered channels |
POST /api/v1/notifications — Send
POST https://api.septemcore.com/v1/notifications
Authorization: Bearer <access_token>
Content-Type: application/json
{
"channel": "email",
"recipient": {
"userId": "01j9pusr0000000000000001"
},
"subject": "Your invoice is ready",
"body": "Invoice #INV-2026-042 is available in your account.",
"priority": "normal",
"meta": { "invoiceId": "inv_042" }
}
Response 202 Accepted (fire-and-forget — delivery happens asynchronously):
{
"notificationId": "01j9pnot0000000000000001",
"status": "queued",
"channel": "email",
"createdAt": "2026-04-22T04:10:00Z"
}
POST /notificationsalways returns202 Acceptedimmediately.kernel.notify().send()is non-blocking — delivery and retries happen via RabbitMQ queue asynchronously.
Request Fields
| Field | Required | Description |
|---|---|---|
channel | ✅ | Registered channel name (e.g. email, sms, telegram, websocket) |
recipient.userId | ✅ (or recipient.address) | Platform user ID — core resolves linked channels |
recipient.address | ✅ (or recipient.userId) | Direct address (e.g. raw email or phone number) |
subject | ☐ | Subject line (email, push) |
body | ✅ | Notification body text |
priority | ☐ | low, normal (default), high, critical |
templateId | ☐ | If set, body may be omitted — template variables applied via variables |
variables | ☐ | Key-value map for template substitution |
meta | ☐ | Arbitrary JSON metadata stored with the record |
POST /api/v1/notifications/batch — Batch Send
POST https://api.septemcore.com/v1/notifications/batch
Authorization: Bearer <access_token>
Content-Type: application/json
{
"channel": "email",
"recipients": [
{ "userId": "01j9pusr0000000000000001" },
{ "userId": "01j9pusr0000000000000002" }
],
"templateId": "tmpl_invoice_ready",
"variables": { "planName": "Growth" },
"priority": "normal"
}
| Batch limit | Value |
|---|---|
| Max recipients per batch call | 500 |
| Exceeded | 400 Bad Request (problems/batch-limit-exceeded) |
| Background job threshold | batch > 100 recipients → async job |
When batch > 100 recipients the service returns 202 Accepted with a
job reference:
{
"jobId": "01j9pjob0000000000000001",
"status": "queued",
"total": 350
}
Track progress with GET /api/v1/notifications/jobs/:id:
{
"jobId": "01j9pjob0000000000000001",
"status": "running",
"total": 350,
"sent": 210,
"failed": 3
}
status values: queued, running, completed, failed.
GET /api/v1/notifications/:id — Status
{
"id": "01j9pnot0000000000000001",
"channel": "email",
"subject": "Your invoice is ready",
"status": "delivered",
"priority": "normal",
"retries": 0,
"sentAt": "2026-04-22T04:10:02Z",
"deliveredAt": "2026-04-22T04:10:04Z",
"failedAt": null,
"meta": { "invoiceId": "inv_042" }
}
| Status value | Meaning |
|---|---|
queued | In RabbitMQ, not yet attempted |
sending | Adapter is making the delivery attempt |
delivered | Adapter confirmed delivery |
failed | Max retries exhausted — see failed queue |
POST /api/v1/notifications/:id/retry — Manual Retry
Manually re-enqueue a failed notification:
POST https://api.septemcore.com/v1/notifications/01j9pnot0000000000000001/retry
Authorization: Bearer <access_token>
Response 202 Accepted — notification re-queued.
GET /api/v1/notifications/failed — Failed Queue
Lists notifications that exhausted all retry attempts (cursor-paginated):
GET https://api.septemcore.com/v1/notifications/failed
?channel=email
&since=2026-04-01T00:00:00Z
&limit=20
Authorization: Bearer <access_token>
Retry Policy
Delivery retries happen transparently via RabbitMQ with exponential backoff. The calling service is never blocked.
| Parameter | Value |
|---|---|
| Max retries | 5 (per-adapter, configurable) |
| Backoff schedule | 30 s → 60 s → 120 s → 240 s → 480 s (±10% jitter) |
| Timeout per attempt | 10 s (per-adapter, configurable) |
| After max retries | Status: failed. Recorded in notifications_failed table. Visible in Admin UI → Notifications → Failed |
| Manual retry | POST /notifications/:id/retry |
| Automatic fallback | Not provided — no automatic channel fallback |
Note: Retry does NOT block the caller.
POST /notificationsreturns202 Acceptedimmediately. RabbitMQ queue per tenant:notify.outgoing.{tenantId}(one slow tenant cannot affect others). Queue policy:x-max-length: 10000, overflow:reject-publish→503.
Templates
Templates store reusable notification content with variable substitution.
| Endpoint | Description |
|---|---|
POST /notifications/templates | Create template |
GET /notifications/templates | List templates (cursor-paginated) |
GET /notifications/templates/:id | Template details |
PATCH /notifications/templates/:id | Update template |
DELETE /notifications/templates/:id | Delete template |
POST /api/v1/notifications/templates
POST https://api.septemcore.com/v1/notifications/templates
Authorization: Bearer <access_token>
Content-Type: application/json
{
"id": "tmpl_invoice_ready",
"name": "Invoice Ready",
"channel": "email",
"subject": "Your {{planName}} invoice is ready",
"body": "Invoice {{invoiceNumber}} for your {{planName}} plan is now available.",
"variables": ["planName", "invoiceNumber"]
}
Variable syntax: {{variableName}}. All declared variables must be provided
in POST /notifications variables map — missing variables → 400 Bad Request.
Channels
Channels are installed by registering adapter modules via
module.manifest.json (notificationChannels field). After installation,
the channel automatically appears in Settings → Notifications.
| Endpoint | Description |
|---|---|
GET /notifications/channels | List registered channels |
POST /notifications/channels | Register a custom channel adapter |
GET /notifications/channels/:id | Channel details and config |
DELETE /notifications/channels/:id | Unregister a channel |
Channel Object
{
"id": "email",
"name": "Email (SendGrid)",
"type": "email",
"iconUrl": "https://cdn.platform.io/icons/sendgrid.svg",
"status": "active",
"config": {
"apiKey": "[redacted]",
},
"registeredAt": "2026-04-01T09:00:00Z"
}
Built-in channels (no adapter required):
| Channel ID | Type | Notes |
|---|---|---|
websocket | real-time | nhooyr.io/websocket. See WebSocket Protocol |
push | browser push | Service Worker + FCM/APNs |
Plugin channels (installed as modules):
| Adapter | Type |
|---|---|
| Email (SMTP / SendGrid / Mailgun) | email |
| SMS (Twilio / Vonage) | sms |
| Telegram | social |
| Slack | social |
| Discord | social |
| Webhook | webhook |
Rate Limiting
| Limit | Value | Env var |
|---|---|---|
| Per-tenant | 100 notifications / minute | NOTIFY_RATE_LIMIT_PER_TENANT |
| Per-module | 50 notifications / minute | — |
| Batch max recipients | 500 | — |
| Exceeded | 429 Too Many Requests — message is not lost, queued with delay | — |
Principle: Without rate limiting, a buggy module loop (
send()for 100K users) would get the platform's IP banned by SendGrid/Telegram, affecting all tenants.
Notification History Retention
| Data | Retention | Storage |
|---|---|---|
| Notification history | 90 days (NOTIFY_HISTORY_RETENTION_DAYS) | PostgreSQL |
| Failed notifications | 180 days | PostgreSQL |
| Long-term audit trail | 7 years | Audit Service (every send recorded) |
Error Reference
| Error type | Status | Trigger |
|---|---|---|
problems/channel-not-found | 404 | Specified channel is not registered for this tenant |
problems/batch-limit-exceeded | 400 | More than 500 recipients in one batch call |
problems/template-not-found | 404 | templateId does not exist |
problems/template-variable-missing | 400 | Required template variable not provided |
problems/notification-not-failed | 400 | Retry attempted on a non-failed notification |
problems/rate-limit-exceeded | 429 | Per-tenant or per-module notification rate exceeded |
problems/queue-full | 503 | RabbitMQ per-tenant queue at x-max-length: 10000 |