Feature: Notification System
Relevant source files
The following files were used as context for generating this wiki page:
- apps/backend/src/notification/services/notification.service.ts
- apps/backend/src/notification/providers/notification.provider.interface.ts
- apps/backend/src/notification/entities/notification-preference.entity.ts
- apps/backend/src/notification/controllers/webhook.controller.ts
- apps/backend/src/notification/entities/notification.entity.ts
- apps/backend/src/notification/services/event-notification.service.ts
- apps/backend/src/notification/controllers/admin-notification.controller.ts
The Notification System is a modular and event-driven component within the OnChain Sage backend. Its primary purpose is to deliver timely and relevant alerts to users based on on-chain events, trading signals, and other system activities. The system is designed to be highly configurable, allowing users to specify which events they want to be notified about and through which channels.
The architecture supports multiple delivery channels, including Email, Webhooks, and In-App notifications. It leverages a queue for asynchronous processing to ensure reliability and performance. A key feature is its ability to listen for on-chain events and trigger notifications accordingly, which is central to the platform's value proposition of providing real-time insights.
System Architecture
The notification system is built around a set of services, controllers, and entities that work together to create, manage, and deliver notifications. The flow is initiated either through a direct API call or an internal system event.
High-Level Data Flow
The following diagram illustrates the general flow of a notification from creation to delivery.
This diagram shows that events or API calls trigger the NotificationService or EventNotificationService. User preferences are checked before the notification is added to a queue for delivery by a channel-specific provider.
Sources: apps/backend/src/notification/services/notification.service.ts:88-103, apps/backend/src/notification/services/event-notification.service.ts:33-40
Core Components
NotificationService
This is the primary service for managing the lifecycle of notifications. It handles creation, retrieval, and updates.
create(createNotificationDto): Creates a new notification. It can use a template for content rendering and checks user preferences before queueing the notification for delivery. If a user has disabled notifications for a specific event type, the notification is saved but marked as delivered without being sent.findAll(userId, paginationDto): Retrieves a paginated list of all notifications for a user.findUnread(userId, paginationDto): Retrieves a paginated list of unread notifications.markAsRead(id, userId): Marks a specific notification as read.markAllAsRead(userId): Marks all of a user's notifications as read.updatePreferences(preferencesDto, userId): Allows a user to update their notification preferences.
Sources: apps/backend/src/notification/services/notification.service.ts
EventNotificationService
This service is responsible for handling notifications triggered by on-chain events. It listens for a global onchain.event and orchestrates the notification process based on user preferences.
@OnEvent('onchain.event'): This decorator registers thehandleOnChainEventmethod to listen for system-wide on-chain events.handleOnChainEvent(event): When an event is received, this method:- Fetches a list of users who have subscribed to this
EventType. - For each user, it retrieves their specific
NotificationPreference. - If enabled, it iterates through the user's preferred channels (
EMAIL,WEBHOOK,IN_APP). - It dispatches the notification to the appropriate handler (e.g.,
handleEmailNotification), which includes rate-limiting checks.
- Fetches a list of users who have subscribed to this
Sources: apps/backend/src/notification/services/event-notification.service.ts:33-40
AdminNotificationController
This controller provides administrative endpoints for managing and monitoring the notification system. Access is restricted to users with an AdminGuard.
| Method | Endpoint | Description |
|---|---|---|
GET | /admin/notifications/failed | Retrieves a paginated list of failed notification delivery records. |
POST | /admin/notifications/retry | Retries sending a list of failed notifications by their delivery record IDs. |
GET | /admin/notifications/stats | Provides statistics on notification delivery, grouped by channel and status. |
POST | /admin/notifications/templates | Creates a new notification template. |
GET | /admin/notifications/templates | Retrieves all notification templates. |
Sources: apps/backend/src/notification/controllers/admin-notification.controller.ts
Data Models
The system relies on several key database entities to store notification data, user preferences, and delivery status.
Entity Relationship Diagram
This diagram shows the relationships between the main notification entities.
Notification Entity
This entity represents a single notification to be sent to a user.
| Field | Type | Description |
|---|---|---|
id | uuid | Primary key. |
userId | uuid | The ID of the user receiving the notification. |
title | string | The title of the notification. |
content | text | The main body of the notification. |
status | NotificationStatus | The current delivery status (pending, processing, delivered, failed). |
priority | NotificationPriority | The priority level (low, medium, high, urgent). |
channels | NotificationChannel[] | An array of channels to deliver the notification through. |
metadata | jsonb | Arbitrary data associated with the notification. |
read | boolean | Flag indicating if the user has read the notification. |
Sources: apps/backend/src/notification/entities/notification.entity.ts:39-111
NotificationPreference Entity
This entity stores a user's preferences for a specific type of event.
| Field | Type | Description |
|---|---|---|
id | uuid | Primary key. |
userId | uuid | The ID of the user. |
eventType | EventType | The type of event this preference applies to (e.g., SIGNAL_REGISTERED). |
enabled | boolean | Whether notifications for this event type are enabled. |
channels | NotificationChannel[] | The preferred delivery channels for this event type. |
emailAddress | string | The email address to use if the EMAIL channel is enabled. |
webhookUrl | string | The webhook URL to use if the WEBHOOK channel is enabled. |
Sources: apps/backend/src/notification/entities/notification-preference.entity.ts
Notification Channels
The system supports multiple delivery channels, defined by the NotificationChannel enum.
// Source: apps/backend/src/notification/entities/notification.entity.ts:25-31
export enum NotificationChannel {
EMAIL = 'email',
PUSH = 'push',
WEBHOOK = 'webhook',
IN_APP = 'in-app',
}
A provider-based pattern is used for extensibility. Each channel is implemented by a provider that adheres to the INotificationProvider interface, which is not fully defined in the provided files but is referenced conceptually.
Sources: apps/backend/src/notification/providers/notification.provider.interface.ts, apps/backend/src/notification/entities/notification.entity.ts:25-31
Event Handling and Rate Limiting
A critical feature is the system's ability to react to on-chain events. The EventNotificationService plays a central role in this process.
On-Chain Event Notification Flow
This sequence diagram details how an onchain.event is processed.
Rate Limiting
To prevent spamming users, the EventNotificationService implements a Redis-based rate limiter using a sliding window log approach.
- Key: A unique string per user, channel, and event type (e.g.,
email:userId:SIGNAL_REGISTERED). - Logic: Before sending a notification, the
checkRateLimitfunction is called.- It retrieves all timestamps for the key from a Redis list.
- It filters out timestamps that are older than the defined window.
- If the count of valid timestamps is less than the maximum allowed count, the request is allowed.
- If allowed, the current timestamp is pushed to the list, the list is trimmed to the max count, and the key's expiration is updated.
// Source: apps/backend/src/notification/services/event-notification.service.ts:188-213
private async checkRateLimit(
key: string,
maxCount: number,
windowSeconds: number,
): Promise<boolean> {
const currentTime = Math.floor(Date.now() / 1000);
const windowStart = currentTime - windowSeconds;
// Get all timestamps within the window
const timestamps = await this.redis.lrange(key, 0, -1);
const validTimestamps = timestamps
.map(Number)
.filter((timestamp) => timestamp > windowStart);
// If we're at the limit, return false
if (validTimestamps.length >= maxCount) {
return false;
}
// Add current timestamp and trim old ones
await this.redis.lpush(key, currentTime);
await this.redis.ltrim(key, 0, maxCount - 1);
await this.redis.expire(key, windowSeconds);
return true;
}
This mechanism ensures that users are not overwhelmed with notifications, with different limits for different channels (e.g., 10 emails/hour, 5 webhooks/minute).
Sources: apps/backend/src/notification/services/event-notification.service.ts:88-94, apps/backend/src/notification/services/event-notification.service.ts:120-126, apps/backend/src/notification/services/event-notification.service.ts:188-213