Appearance
API Reference
GrydNotifications exposes three REST API controllers for sending notifications, managing templates, and handling in-app notifications.
All endpoints inherit from GrydBaseController and return RFC 7807 ProblemDetails on errors.
Error Code Convention
Error codes use a suffix convention to determine HTTP status:
| Suffix | HTTP Status | Example |
|---|---|---|
_NOT_FOUND | 404 Not Found | NOTIFICATION_NOT_FOUND |
_ALREADY_EXISTS | 409 Conflict | NOTIFICATION_TEMPLATE_ALREADY_EXISTS |
_FORBIDDEN | 403 Forbidden | NOTIFICATION_INAPP_FORBIDDEN |
_UNAUTHORIZED | 401 Unauthorized | NOTIFICATION_UNAUTHORIZED |
_UNPROCESSABLE | 422 Unprocessable | NOTIFICATION_UNPROCESSABLE |
| (other/none) | 400 Bad Request | NOTIFICATION_DELIVERY_FAILED |
Notifications
Base path: /api/v1/notifications
Send Notification
Send a notification through the unified pipeline (auto-resolves channel provider).
http
POST /api/v1/notifications/send
Content-Type: application/json
{
"channel": "Email",
"recipients": [
{ "address": "user@example.com", "displayName": "John Doe" }
],
"subject": "Order Confirmed",
"htmlBody": "<h1>Your order has been confirmed</h1>",
"textBody": "Your order has been confirmed",
"templateSlug": null,
"templateData": null,
"priority": "Normal"
}| Field | Type | Required | Description |
|---|---|---|---|
channel | NotificationChannel | ✅ | Email, Push, InApp, Sms |
recipients | RecipientDto[] | ✅ | At least one recipient |
subject | string | ❌ | Email subject or notification title |
htmlBody | string | ❌ | HTML content (email) |
textBody | string | ❌ | Plain text fallback |
templateSlug | string | ❌ | Use a stored Scriban template |
templateData | object | ❌ | Data for template rendering |
priority | NotificationPriority | ❌ | Low, Normal, High, Critical |
Response: 200 OK — NotificationResultDto
json
{
"notificationId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"status": "Sent",
"statusDisplay": "Sent"
}Send Email (Direct)
Bypass the unified pipeline and send an email directly.
http
POST /api/v1/notifications/email
Content-Type: application/json
{
"to": ["user@example.com"],
"cc": ["manager@example.com"],
"bcc": [],
"subject": "Monthly Report",
"htmlBody": "<h1>Report attached</h1>",
"fromAddress": "reports@myapp.com",
"fromName": "Reports System"
}Response: 200 OK — NotificationResultDto
Send Push Notification (Direct)
Send a push notification to specific device tokens or users.
http
POST /api/v1/notifications/push
Content-Type: application/json
{
"title": "New Message",
"body": "You have a new message from John",
"deviceTokens": ["token1", "token2"],
"data": {
"messageId": "abc123",
"type": "new_message"
}
}Response: 200 OK — NotificationResultDto
Schedule Notification
Schedule a notification for future delivery.
http
POST /api/v1/notifications/schedule
Content-Type: application/json
{
"notification": {
"channel": "Email",
"recipients": [{ "address": "user@example.com" }],
"subject": "Reminder",
"htmlBody": "<p>Don't forget your appointment tomorrow!</p>"
},
"scheduledAt": "2026-02-21T09:00:00Z"
}Response: 200 OK — NotificationResultDto with status: "Scheduled"
Cancel Scheduled Notification
http
DELETE /api/v1/notifications/schedule/{id}Response: 200 OK | 404 Not Found
Error codes: NOTIFICATION_NOT_FOUND, NOTIFICATION_ALREADY_CANCELLED
Retry Failed Notification
Manually retry a failed notification.
http
POST /api/v1/notifications/{id}/retryResponse: 200 OK — NotificationResultDto | 404 Not Found
Get Notification by ID
http
GET /api/v1/notifications/{id}Response: 200 OK — NotificationDetailDto
json
{
"id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"channel": "Email",
"channelDisplay": "Email",
"status": "Sent",
"statusDisplay": "Sent",
"priority": "Normal",
"subject": "Order Confirmed",
"htmlBody": "<h1>Your order has been confirmed</h1>",
"templateSlug": "order-confirmation",
"fromAddress": "noreply@myapp.com",
"fromName": "My App",
"retryCount": 0,
"maxRetries": 3,
"sentAt": "2026-02-20T14:30:00Z",
"createdAt": "2026-02-20T14:29:58Z",
"recipients": [
{ "address": "user@example.com", "displayName": "John Doe" }
],
"deliveryAttempts": [
{
"attemptNumber": 1,
"providerName": "MailKit",
"isSuccess": true,
"durationMs": 342.5,
"attemptedAt": "2026-02-20T14:30:00Z"
}
]
}List Notifications (Paged)
http
GET /api/v1/notifications?page=1&pageSize=20&channel=Email&status=Sent&fromDate=2026-02-01&toDate=2026-02-28Query Parameters:
| Parameter | Type | Default | Description |
|---|---|---|---|
page | int | 1 | Page number |
pageSize | int | 20 | Page size |
channel | NotificationChannel? | — | Filter by channel |
status | NotificationStatus? | — | Filter by status |
fromDate | DateTime? | — | Created after this date |
toDate | DateTime? | — | Created before this date |
Response: 200 OK — PagedResult<NotificationSummaryDto>
Get Statistics
http
GET /api/v1/notifications/stats?fromDate=2026-02-01&toDate=2026-02-28Response: 200 OK — NotificationStatsDto
json
{
"totalNotifications": 15230,
"pendingNotifications": 12,
"sentNotifications": 14800,
"failedNotifications": 45,
"deadLetteredNotifications": 3,
"scheduledNotifications": 8,
"queuedNotifications": 362
}Notification Templates
Base path: /api/v1/notification-templates
Create Template
http
POST /api/v1/notification-templates
Content-Type: application/json
{
"slug": "order-confirmation",
"name": "Order Confirmation Email",
"subjectTemplate": "Order #{{order_id}} Confirmed",
"htmlBodyTemplate": "<h1>Hi {{customer_name}}</h1><p>Your order #{{order_id}} for {{total | math.format \"C2\"}} is confirmed.</p>",
"textBodyTemplate": "Hi {{customer_name}}, your order #{{order_id}} is confirmed.",
"locale": "en-US",
"requiredVariables": "order_id,customer_name,total"
}Response: 201 Created — NotificationTemplateDto
json
{
"id": "a1b2c3d4-...",
"slug": "order-confirmation",
"name": "Order Confirmation Email",
"locale": "en-US",
"version": 1,
"createdAt": "2026-02-20T14:30:00Z"
}Error codes: NOTIFICATION_TEMPLATE_ALREADY_EXISTS (409)
Update Template
http
PUT /api/v1/notification-templates/{id}
Content-Type: application/json
{
"templateId": "a1b2c3d4-...",
"name": "Order Confirmation Email (Updated)",
"subjectTemplate": "Order #{{order_id}} — Thank you!",
"htmlBodyTemplate": "<h1>Thanks, {{customer_name}}!</h1>"
}WARNING
The templateId in the body must match the id in the URL. A mismatch returns 400 Bad Request.
Response: 200 OK — NotificationTemplateDto | 404 Not Found
Delete Template
http
DELETE /api/v1/notification-templates/{id}Response: 200 OK | 404 Not Found
List Templates (Paged)
http
GET /api/v1/notification-templates?page=1&pageSize=20&locale=en-US| Parameter | Type | Default | Description |
|---|---|---|---|
page | int | 1 | Page number |
pageSize | int | 20 | Page size |
locale | string? | — | Filter by locale |
Response: 200 OK — PagedResult<NotificationTemplateDto>
Render Template (Preview)
Preview how a template renders with provided data — without actually sending anything.
http
POST /api/v1/notification-templates/render
Content-Type: application/json
{
"templateSlug": "order-confirmation",
"locale": "en-US",
"data": {
"order_id": "ORD-12345",
"customer_name": "John",
"total": 199.99
}
}Response: 200 OK — RenderedNotification
json
{
"subject": "Order #ORD-12345 Confirmed",
"htmlBody": "<h1>Hi John</h1><p>Your order #ORD-12345 for $199.99 is confirmed.</p>",
"textBody": "Hi John, your order #ORD-12345 is confirmed.",
"templateSlug": "order-confirmation",
"locale": "en-US",
"templateVersion": 1
}User Notifications (In-App)
Base path: /api/v1/user-notifications
List User Notifications
http
GET /api/v1/user-notifications?userId={userId}&page=1&pageSize=20&isRead=false&category=alerts| Parameter | Type | Default | Description |
|---|---|---|---|
userId | Guid | required | User ID |
page | int | 1 | Page number |
pageSize | int | 20 | Page size |
isRead | bool? | — | Filter read/unread |
category | string? | — | Filter by category |
Response: 200 OK — PagedResult<UserNotificationDto>
json
{
"items": [
{
"id": "...",
"userId": "...",
"title": "New Comment",
"body": "John commented on your task",
"icon": "💬",
"category": "comments",
"actionUrl": "/tasks/123",
"priority": "Normal",
"isRead": false,
"createdAt": "2026-02-20T14:30:00Z"
}
],
"totalCount": 42,
"pageNumber": 1,
"pageSize": 20
}Get Unread Count (Badge)
http
GET /api/v1/user-notifications/unread-count?userId={userId}Response: 200 OK — int
json
42Mark as Read
http
PUT /api/v1/user-notifications/{id}/read?userId={userId}Response: 200 OK | 404 Not Found | 403 Forbidden
Error codes: NOTIFICATION_INAPP_NOT_FOUND, NOTIFICATION_INAPP_FORBIDDEN
Mark All as Read
http
PUT /api/v1/user-notifications/read-all?userId={userId}Response: 200 OK
Delete User Notification
http
DELETE /api/v1/user-notifications/{id}?userId={userId}Response: 200 OK | 404 Not Found | 403 Forbidden
Enums
NotificationChannel
| Value | Description |
|---|---|
Email | Email via SMTP (MailKit) or SendGrid |
Push | Push notification via FCM |
InApp | In-app drawer notification |
Sms | SMS (future) |
NotificationStatus
| Value | Description |
|---|---|
Pending | Created, not yet processed |
Queued | In the processing queue |
Sending | Actively being sent |
Sent | Successfully delivered |
Failed | Delivery failed (may retry) |
DeadLettered | Failed after all retries |
Scheduled | Scheduled for future delivery |
Cancelled | Cancelled by user |
NotificationPriority
| Value | Description |
|---|---|
Low | Batch-friendly, can be delayed |
Normal | Standard delivery |
High | Prioritized in queue |
Critical | Immediate processing |