Notifications Module — Admin
HTTP surface for the admin notification operations: composing and sending broadcasts (one-shot email and/or push to a targeted audience) and inspecting the notification log…
HTTP surface for the admin notification operations: composing and sending broadcasts (one-shot email and/or push to a targeted audience) and inspecting the notification log (per-message audit of every send across every channel and trigger).
Source:
api-modules/notifications/src/controllers/admin-broadcast.controller.ts,api-modules/notifications/src/controllers/admin-notification-log.controller.ts.Channels are plugged in via the per-channel modules (
notifications-email-mailer,notifications-push-fcm). Broadcast send / schedule are queue-backed — the controller returns immediately with a row whosestatusreflects scheduled/in-progress; per-recipient jobs run async.
Conventions
Authentication
All endpoints require a Better-Auth admin session and a role granting the matching notifications:* permission.
| Endpoint group | Permission |
|---|---|
POST /admin/notifications/broadcasts, POST /admin/notifications/broadcasts/:id/cancel | notifications: broadcast |
GET /admin/notifications/broadcasts, GET /admin/notifications/broadcasts/:id, GET /admin/notifications/log | notifications: view |
Response envelope
Successful responses are wrapped by ResponseInterceptor:
{
"data": <payload>,
"message": "Success",
"statusCode": 200,
"metadata": { /* optional, e.g. pagination */ }
}Error envelope
statusCode | errorCode examples |
|---|---|
| 400 | BAD_REQUEST, VALIDATION_ERROR |
| 401 | UNAUTHORIZED |
| 403 | FORBIDDEN |
| 404 | NOT_FOUND |
| 500 | INTERNAL_SERVER_ERROR, DATABASE_ERROR |
Domain types
BroadcastResponse
type BroadcastResponse = {
id: string;
status: string; // service-defined: "scheduled", "in_progress", "completed", "cancelled", "failed"
audienceType: string; // "all_customers" | "all_vendors" | "user_ids"
channels: string[]; // subset of ["email", "push"]
scheduledFor: string | null; // ISO (null = send-now)
totalRecipients: number | null; // null until the audience is materialised
sentCount: number;
failedCount: number;
createdAt: string; // ISO
};NotificationLogResponse
type NotificationLogResponse = {
id: string;
recipientUserId: string | null;
channel: string; // "email" | "push" | ...
provider: string; // e.g. "mailer", "fcm"
eventType: string; // domain event that triggered the send (or "broadcast")
broadcastId: string | null; // populated when triggered by a broadcast
subject: string | null; // for email
status: string; // "sent" | "failed" | provider-specific
error: string | null; // free-form provider error message
sentAt: string; // ISO
};Broadcasts
Base path: /admin/notifications/broadcasts.
POST /admin/notifications/broadcasts — Create + send (or schedule) a broadcast
Required permission: notifications: broadcast. The controller enqueues the broadcast and returns the persisted row immediately. If scheduleFor is omitted, the broadcast starts dispatching at once; if present, it is queued as a delayed job.
Body
{
"audience": { "type": "all_customers" },
"channels": ["email", "push"],
"content": {
"email": {
"subject": "Our biggest sale of the year",
"html": "<h1>Hi!</h1>...",
"text": "Hi! ..."
},
"push": {
"title": "Sale starts now",
"body": "Up to 50% off across the store",
"imageUrl": "https://cdn.example/banners/sale.png",
"data": { "deepLink": "/sale" }
}
},
"scheduleFor": "2026-06-01T03:00:00.000Z"
}Audience union
type | Extra fields | Behaviour |
|---|---|---|
"all_customers" | — | Every customer (non-vendor user) |
"all_vendors" | — | Every vendor-org member |
"user_ids" | userIds: string[] (1..10000) | Targeted list |
Channels + content
channels is a 1..2 subset of ["email", "push"]. The merged content block must include a sub-payload for every channel listed in channels. Channel-content shapes:
| Channel | Shape | Notes |
|---|---|---|
email | { subject (1..200), html (1..50_000), text? (max 20_000) } | subject trimmed |
push | { title (1..120), body (1..500), imageUrl? (URL, max 2048), data? Record<string,string> } | title / body trimmed |
Other fields
| Field | Type | Notes |
|---|---|---|
scheduleFor | ISO datetime? | Send-now if omitted. Past timestamps are rejected by the service |
Response 201 — BroadcastResponse.
Errors
| Status | Code | When |
|---|---|---|
| 400 | VALIDATION_ERROR | Body fails zod, including the cross-field "content must include every listed channel" rule |
GET /admin/notifications/broadcasts — List broadcasts
Required permission: notifications: view. Newest first.
Query
| Name | Type | Default | Notes |
|---|---|---|---|
page | int | 1 | >= 1 |
limit | int | 50 | 1..200 |
status | string? | — | Filter by status string |
Response 200 — paginated envelope of BroadcastResponse[].
GET /admin/notifications/broadcasts/:id — Broadcast detail
Required permission: notifications: view.
Errors
| Status | Code | When |
|---|---|---|
| 404 | NOT_FOUND | Unknown id |
POST /admin/notifications/broadcasts/:id/cancel — Cancel
Required permission: notifications: broadcast. Semantics:
- Scheduled: removes the delayed start job and marks the row cancelled.
- In-progress: marks the row cancelled; remaining per-recipient jobs skip when popped from the queue. In-flight provider calls already running are not aborted.
- Already completed / cancelled: no-op service-side, response still 200.
Response 200 — cancelled BroadcastResponse.
Errors
| Status | Code | When |
|---|---|---|
| 404 | NOT_FOUND | Unknown id |
Notification log
GET /admin/notifications/log — Audit of sends
Required permission: notifications: view. Newest first. Use to answer "did the customer get the e-mail?" from support, or to debug a stuck broadcast.
Query
| Name | Type | Default | Notes |
|---|---|---|---|
page | int | 1 | >= 1 |
limit | int | 50 | 1..200 |
recipientUserId | string? | — | Filter to one recipient |
eventType | string? | — | e.g. "order.placed", "broadcast" |
broadcastId | string? | — | All sends triggered by one broadcast |
Response 200 — paginated envelope of NotificationLogResponse[].
Related modules
admin-rbac— gates every endpoint vianotifications:view/notifications:broadcast. Seeadmin-rbac.md.notifications-email-mailer/notifications-push-fcm— channel plugins consumed by both broadcasts and per-event triggers; the log row'sproviderreflects which plugin handled the send.order,vendor,reviews— emit domain events that the notifications module subscribes to and turns into log entries (visible via this admin log).
Meta Catalog Module — Admin
HTTP surface for syncing eligible product variants to a Meta Commerce Catalog (Facebook + Instagram Shops) via the Catalog Batch API (POST /{catalog_id}/items_batch) and the…
Order Module — Admin
HTTP surface for platform-admin oversight of orders, returns, and vendor payouts. Read every order on the platform; perform ops actions (cancel on behalf of the customer, manually…