Skip to content

Push Notification Infrastructure Codemap

Push Notification Infrastructure Codemap

Architecture Overview

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ Novu Framework (@novu/framework) β”‚
β”‚ Workflows registered via getWorkflowFactory() β†’ workflow() β”‚
β”‚ Channels: step.push(), step.email(), step.inApp(), step.sms() β”‚
β”‚ Provider: OneSignal (via novu.subscribers.credentials.update) β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Packages involved: server/src/notification/, server/src/instantdb/, server/src/bullmq/, server/src/moderation/, server/src/templates/


1. Push Notification Type Definitions

File: server/src/notification/enums/push.enum.ts

42 push notification types across 6 categories:

CategoryTypes
Account & AuthWELCOME, EMAIL_VERIFICATION_REMINDER, PASSWORD_RESET_REQUEST, PASSWORD_CHANGED
Course & ConsultationCOURSE_PURCHASE_CONFIRMATION, COURSE_AVAILABLE, UPCOMING_CONSULTATION_REMINDER, CONSULTATION_COMPLETED_FEEDBACK_REQUEST, CONSULTATION_CANCELLED, COURSE_COMPLETION_BADGE, NEW_COURSE_IN_CATEGORY
Withdrawals & EarningsWITHDRAWAL_REQUEST_RECEIVED, WITHDRAWAL_APPROVED, WITHDRAWAL_DENIED, EARNINGS_UPDATE
EngagementINACTIVE_USER_REENGAGEMENT, COURSE_DISCOUNT_ALERT, INSTRUCTOR_MESSAGE_NOTIFICATION
Admin & SupportNEW_COURSE_SUBMITTED, COURSE_APPROVED, COURSE_REJECTED, USER_REPORTED_ALERT
Financial & SystemNEW_WITHDRAWAL_REQUEST, PAYOUT_PROCESSING_FAILURE, REVENUE_MILESTONE_ACHIEVEMENT, SUSPICIOUS_LOGIN_ATTEMPT, API_KEY_EXPIRY_WARNING, SERVICE_DOWNTIME_ALERT

2. Core Workflow Registration & Factory

File: server/src/notification/workflows.service.ts

Factory Pattern (workflows.service.ts:120-131)

private getWorkflowFactory()
β”œβ”€β”€ NODE_ENV=TEST β†’ returns mock { id, trigger: async => ({}) }
└── Production β†’ const { workflow } = require('@novu/framework')
β†’ returns `workflow(id, steps, { payloadSchema })`

Returns Novu’s workflow() function. All workflows follow:

workflow(Workflows.XXX, async ({ step, payload }) => {
await step.push('step-name', async () => {
// data retrieval happens INSIDE step callbacks
// renders templates, constructs { subject, body, data }
});
}, { payloadSchema: z.object({...}) });

Workflow Registration (workflows.service.ts:1422-1449)

getWorkFlows() returns an array of 23 workflow instances:

#WorkflowLinePush?
1consultationReminderWorkflow()490βœ… (conditional)
2consultationRescheduledWorkflow()592❌ (inApp only)
3consultationRescheduleApprovedWorkflow()629❌ (inApp only)
4consultationCancelledWorkflow()668❌ (email + inApp)
5consultationEnrollmentWorkflow()738❌ (email + inApp)
6bookingConfirmationWorkflow()818❌ (email + inApp)
7orderNotificationWorkflow()911❌ (email only)
8productEnrollmentWorkflow()967❌ (email only)
9productGiftReceivedWorkflow()999❌ (email only)
10productGiftSentWorkflow()1028❌ (email only)
11pushNotificationWorkflow()132βœ… Primary push
12inAppNotificationWorkflow()165❌ (inApp only)
13emailWorkflow()201❌ (email only)
14productUpdateWorkflow()1059❌ (email only)
15sendPaymentNotificationWorkflow()1089❌ (email only)
16sendUserActionNotificationWorkflow()1120❌ (email only)
17otpWorkflow()232❌ (sms + email)
18withdrawalMethodVerifiedWorkflow()1258❌ (email only)
19withdrawalTransactionWorkflow()1180❌ (email only)
20sendCourseCompletionNotificationWorkflow()1150❌ (email only)
21multistepWorkflow()294βœ… Push + email + inApp + sms
22adminSystemAlertWorkflow()459❌ (inApp only)
23chatNotificationWorkflow()1381βœ… Chat push with digest

3. Push Notification Workflow (Single Channel)

File: server/src/notification/workflows.service.ts:132-163

Invocation Path

pushNotificationWorkflow().trigger({
to: subscriberId,
payload: {
userId: string,
template: PushNotification, // enum key
data: { TITLE?, MESSAGE? }
}
})

Step Flow

step.push('send-push-notification')
β”œβ”€β”€ userService.getUser(payload.userId)
β”œβ”€β”€ templateService.processTemplate(
β”‚ payload.template, // PushNotification enum
β”‚ NotificationChannel.PUSH, // 'push'
β”‚ payload.data, // interpolation variables
β”‚ user // user context for template
β”‚ )
β”œβ”€β”€ Validates: subject && body !== null
└── Returns: { subject, body, data }

Typical Caller Pattern

Triggered mostly via triggerWithCircuitBreaker() which wraps the trigger in:

  • CircuitBreakerStrategy: 60% error threshold, 8 req volume, 15s sleep, 90s window
  • RetryStrategy: 3 max retries, 15s max delay, exponential backoff (1.5s base)

4. Chat Message Push Notification Flow

Message Creation & Queuing

ModerationService (moderation.service.ts:270-303)
└── After moderation pass:
└── notificationService.addNotificationJob({
messageId, roomId, senderId, senderName,
messageType, content, timestamp, roomName,
roomType, entityType
})
└── notificationQueue.add('PROCESS_NOTIFICATION', data)
(queue: NOTIFICATION)

BullMQ Notification Processor

File: server/src/notification/processors/notification.processor.ts

NotificationProcessor.process(job)
└── job.name === 'PROCESS_NOTIFICATION'
└── job.data.messageId exists?
β”œβ”€β”€ YES β†’ processChatNotification(job)
└── NO β†’ handle other types
processChatNotification(job):
β”œβ”€β”€ 1. getMessageDetails(messageId)
β”‚ β†’ instantdbService.getMessage()
β”œβ”€β”€ 2. Check message age threshold
β”‚ β†’ settingsService.getSettingValue('CHAT_NOTIFICATION_MAX_AGE_MINUTES')
β”‚ β†’ Default: 2 minutes
β”‚ β†’ If too old: markMessageNotificationProcessed() + return
β”œβ”€β”€ 3. Route by room type:
β”‚
β”‚ PRIVATE:
β”‚ β”œβ”€β”€ getNotificationRecipients(roomId, senderId, timestamp)
β”‚ β”‚ β”œβ”€β”€ getRoomById() + getRoomParticipants()
β”‚ β”‚ β”œβ”€β”€ Filter: skip sender
β”‚ β”‚ └── Filter: shouldNotifyUser() per recipient
β”‚ β”‚ β”œβ”€β”€ isUserInRoomView(userId, roomId) β†’ ephemeral presence
β”‚ β”‚ └── getUserLastSeen(userId, roomId) β†’ persistent read receipt
β”‚ └── triggerChatNotificationWorkflow(recipientId, messageData)
β”‚ β†’ triggerTarget = { subscriberId: recipientId }
β”‚
β”‚ GROUP:
β”‚ β”œβ”€β”€ notificationService.ensureRoomTopic(roomId, roomName)
β”‚ β”‚ β†’ deriveRoomTopicKey(roomId) β†’ `room-${roomId}`
β”‚ β”‚ β†’ Check if topic exists via novu.topics.get()
β”‚ β”‚ β†’ If not: novu.topics.create({ key, name: `Room: ${roomName}` })
β”‚ └── triggerChatNotificationWorkflow(null, messageData)
β”‚ β†’ triggerTarget = { type: "Topic", topicKey: `room-${roomId}` }
β”‚
└── 4. Mark message as notification processed
β†’ instantdbService.markMessageNotificationProcessed(messageId)

Chat Notification Workflow Definition

File: server/src/notification/workflows.service.ts:1381-1420

chatNotificationWorkflow():
β”œβ”€β”€ step.digest('batch-chat-messages', () => {
β”‚ amount: digestWindow || 5 seconds
β”‚ digestKey: subscriberId || `room-${roomId}`
β”‚ })
└── step.push('send-chat-notification', () => {
subject: payload.sender // sender name for private, room name for group
body: payload.content // formatted + truncated content
data: { messageId, roomId, messageType, timestamp }
})

Message Formatting

File: server/src/notification/services/message-formatter.service.ts

Message TypeNotification Content
TEXTTruncated to CHAT_NOTIFICATION_CONTENT_MAX_LENGTH (default: 100) + ellipsis
FILE / IMAGE / VIDEO / AUDIO / DOCUMENT"You received a file"

Digest windows:

  • PRIVATE rooms: CHAT_NOTIFICATION_DIGEST_WINDOW_DM (1s)
  • GROUP rooms: CHAT_NOTIFICATION_DIGEST_WINDOW_GROUP (10s)
  • CATEGORY rooms: CHAT_NOTIFICATION_DIGEST_WINDOW_CATEGORY (10s)

5. Event-Driven Push Notification Flow

File: server/src/notification/listeners/notification.listener.ts

Trigger Pattern

All event handlers follow:

@OnEvent(AppEvents.XXX)
async handleXxxEvent(payload) {
await this.workflowsService.triggerWithCircuitBreaker(() =>
this.workflowsService.someWorkflow().trigger({ to, payload })
);
}

Events β†’ Workflow Mapping (with push)

EventWorkflowPush?
SPACE_SESSION_LIVEmultistepWorkflow()βœ… push: { data: { TITLE, MESSAGE } }
SPACE_SESSION_REMINDER_24HRmultistepWorkflow()❌ (email + inApp)
SPACE_SESSION_REMINDER_1HRmultistepWorkflow()❌ (email + inApp)
SPACE_REGISTRATION_COMPLETEDmultistepWorkflow()❌ (email + inApp)
SPACE_REGISTRATION_CANCELLEDmultistepWorkflow()❌ (email + inApp)
COURSE_APPROVEDproductUpdateWorkflow()❌ (email only)
COURSE_REJECTIONproductUpdateWorkflow()❌ (email only)
CONSULTATION_APPROVEDproductUpdateWorkflow()❌ (email only)
CONSULTATION_REJECTEDproductUpdateWorkflow()❌ (email only)
PRODUCT_ENROLLMENTproductEnrollmentWorkflow()❌ (email only)

The SPACE_SESSION_LIVE handler (notification.listener.ts:419-450) is the only event-driven path that triggers push notifications:

await this.workflowsService.multistepWorkflow().trigger({
to: payload.recipientUserId,
payload: {
userId: payload.recipientUserId,
push: { data: { TITLE: 'Space is live now', MESSAGE: 'The session has started. Join now.' } },
inapp: { data: { ... } },
data: { spaceId, sessionId },
priority: 'high',
},
})

6. Multistep Workflow (Multi-Channel)

File: server/src/notification/workflows.service.ts:294-457

Single trigger β†’ up to 4 channel steps, each with conditional skip:

multistepWorkflow():
β”œβ”€β”€ step.email('send-multistep-email')
β”‚ β”œβ”€β”€ IF email.key β†’ processTemplate() with Enum key
β”‚ β”œβ”€β”€ IF email.data β†’ use TITLE/MESSAGE directly
β”‚ └── skip: !payload.email
β”‚
β”œβ”€β”€ step.push('send-multistep-push')
β”‚ β”œβ”€β”€ IF push.key β†’ processTemplate() with PushNotification enum
β”‚ β”œβ”€β”€ IF push.data β†’ use TITLE/MESSAGE directly
β”‚ └── skip: !payload.push
β”‚
β”œβ”€β”€ step.inApp('send-multistep-inapp')
β”‚ β”œβ”€β”€ IF inapp.key β†’ processTemplate() with InAppNotification enum
β”‚ β”œβ”€β”€ IF inapp.data β†’ use TITLE/MESSAGE directly
β”‚ └── skip: !payload.inapp
β”‚
└── step.sms('send-multistep-sms')
β”œβ”€β”€ IF sms.key β†’ processTemplate() with SmsNotification enum
β”œβ”€β”€ IF sms.data β†’ use MESSAGE directly
└── skip: !payload.sms

Payload schema:

{
userId: string,
eventId?: AppEvents,
email?: { key?: EmailNotification, data?: { TITLE, MESSAGE } },
push?: { key?: PushNotification, data?: { TITLE, MESSAGE } },
inapp?: { key?: InAppNotification, data?: { TITLE, MESSAGE } },
sms?: { key?: SmsNotification, data?: { MESSAGE } },
data?: Record<string, any>,
priority?: 'low' | 'medium' | 'high' | 'urgent'
}

7. Consultation Reminder Push

Scheduling (BullMQ β†’ ReminderProcessor)

File: server/src/bullmq/processors/reminder.processor.ts

ReminderProcessor.process(job)
β”œβ”€β”€ job.name === 'CONSULTATION_REMINDER_24HR'
β”‚ β†’ reminderType = TWENTY_FOUR_HOURS
β”œβ”€β”€ job.name === 'CONSULTATION_REMINDER_30MIN'
β”‚ β†’ reminderType = THIRTY_MINUTES
β”‚
β”œβ”€β”€ Validate session exists & status is SCHEDULED/UPCOMING
β”œβ”€β”€ Fetch student + consultant
β”œβ”€β”€ Trigger for student: workflow.trigger({ to: student.id, ... })
└── Trigger for consultant: workflow.trigger({ to: consultant.id, ... })

Reminder Workflow Definition

File: server/src/notification/workflows.service.ts:490-590

consultationReminderWorkflow():
β”œβ”€β”€ Data retrieval helper: getConsultationReminderData()
β”‚ β”œβ”€β”€ sessionsService.findOne(sessionId, [relations...])
β”‚ β”œβ”€β”€ Load: consultation β†’ consultant β†’ user
β”‚ └── Determine userForEmail + otherParty
β”‚
β”œβ”€β”€ step.email('send-consultation-reminder-email') ← ALWAYS
β”‚ └── template: EmailNotification.UPCOMING_CONSULTATION_REMINDER
β”‚ with MJML template CONSULTATION
β”‚
└── Conditional: reminderType === THIRTY_MINUTES
β”œβ”€β”€ step.push('send-30min-push-reminder')
β”‚ β†’ subject: 'Session Starting Soon'
β”‚ β†’ body: `Your consultation with ${otherParty.firstName} is in 30 minutes.`
β”‚ β†’ data: { sessionId, meetingLink }
└── step.inApp('send-30min-in-app-reminder')
β†’ subject: 'Session Starting Soon'
β†’ body: same as push
β†’ data: { sessionId, meetingLink }

8. Device Token Registration & Novu Sync

File: server/src/notification/notification.service.ts

Client β†’ Server Flow

Client sends: POST /notification/device-token (or similar)
β†’ NotificationController (unimplemented in controller β€” called from other services)
saveDeviceToken(data: DeviceTokenDto, user?: User) β†’ notification.service.ts:166
β”‚
β”œβ”€β”€ 1. Check existing: deviceTokenRepo.findOne({ deviceToken, fingerprint })
β”‚
β”œβ”€β”€ 2. Token already exists:
β”‚ β”œβ”€β”€ Update lastActive
β”‚ β”œβ”€β”€ If no user attached but user provided: attach user + sync credentials
β”‚ └── Return 'success'
β”‚
β”œβ”€β”€ 3. New token:
β”‚ β”œβ”€β”€ deviceTokenRepo.save({ deviceToken, fingerprint, user, lastActive })
β”‚ └── If user provided: fetch all user tokens β†’ sync to Novu
β”‚ └── setCredential(user.id, allDeviceTokens)
β”‚
└── setCredential(subscriberId, deviceTokens) β†’ notification.service.ts:297
β”œβ”€β”€ Resolve provider ID:
β”‚ - Config: ONESIGNAL_PROVIDER_ID
β”‚ - Normalize: 'onesignal' β†’ 'one-signal'
β”‚ - Fallback: NovuProviderId.OneSignal = 'one-signal'
└── novu.subscribers.credentials.update({
providerId,
credentials: { deviceTokens }
}, subscriberId)

Device Token Entity

File: server/src/notification/entities/device-tokens.entity.ts

ColumnTypeNotes
idUUID (PK)Auto-generated
deviceTokenstringPush device token
fingerprintstringDevice fingerprint
userRelation β†’ UserNullable (attached on first login)
lastActivetimestampUpdated on token reuse
createdAt / updatedAttimestampsAuto

9. Topic-Based Group Push

Topic Lifecycle

Room Creation (instantdb.service.ts:365-419):

getOrCreateRoom() or createRoom()
└── IF type === GROUP:
└── notificationService.ensureRoomTopic(roomId, roomName)

Topic Initialization (notification.service.ts:338-359):

ensureRoomTopic(roomId, roomName):
β”œβ”€β”€ deriveRoomTopicKey(roomId) β†’ `room-${roomId}`
β”œβ”€β”€ Try: novu.topics.get(topicKey)
β”‚ β”œβ”€β”€ Found: log + return
β”‚ └── Not found: novu.topics.create({ key, name: `Room: ${roomName}` })
└── Return topicKey

Participant Subscription (instantdb.service.ts:137-188):

createRoomParticipant({ entityType, entityId, userId, role })
β”œβ”€β”€ Create participant in InstantDB
└── IF room.type === GROUP:
└── notificationService.addSubscriberToTopic([userId], topicKey)

Participant Unsubscription (instantdb.service.ts:310-332):

leaveRoom(roomId, userId)
β”œβ”€β”€ Delete participant from InstantDB
└── IF room.type === GROUP:
└── notificationService.removeSubscriberFromTopic(userId, topicKey)

Topic Notification Delivery

From Chat Processor (notification.processor.ts:287-296):

// GROUP room β†’ topic trigger
triggerTarget = {
type: "Topic",
topicKey: `room-${messageData.roomId}`
}
await workflowsService.chatNotificationWorkflow().trigger({
to: triggerTarget,
payload: { ... }
})

Generic Topic Notification (notification.service.ts:271-286):

notifyTopic(topicKey: Topics | string, payload, workflowId?):
β”œβ”€β”€ resolveTopicWorkflowId(topicKey)
β”‚ β”œβ”€β”€ Topics.ADMIN β†’ Workflows.ADMIN_SYSTEM_ALERT
β”‚ └── default β†’ undefined (warns + returns null)
└── novu.trigger({ workflowId, to: { type: "Topic", topicKey }, payload })

10. Resilience & Error Handling

File: server/src/notification/workflows.service.ts:1293-1379

Circuit Breaker + Retry

@UseResilience(
CircuitBreakerStrategy({
errorThresholdPercentage: 60,
requestVolumeThreshold: 8,
sleepWindowInMilliseconds: 15000,
rollingWindowInMilliseconds: 90000,
timeoutInMilliseconds: 5000,
}),
RetryStrategy({
maxRetries: 3,
maxDelay: 15000,
backoff: new ExponentialBackoff({ baseDelay: 1500 }),
})
)
async triggerWithCircuitBreaker<T>(workflowTrigger: () => Promise<T>)

isRetryableError() classifies errors: timeouts, network errors, template processing errors, user lookup errors, Novu validation errors, rate limiting (429), service unavailable (502/503/504).

Chat Notification Failure Isolation

In NotificationProcessor (notification.processor.ts:136-142):

  • Errors are logged but NOT thrown (prevents job retry)
  • Chat message creation is decoupled from notification delivery
  • β€œChat notification failed, but chat message creation succeeded”

11. Test Trigger Endpoint

File: server/src/notification/notification.controller.ts:99-138

POST /notification/test-trigger with { channel: 'push' | 'email' | 'inapp' | 'sms' | 'multistep' }:

ChannelWorkflowTrigger Payload
pushpushNotificationWorkflow(){ template: PushNotification.WELCOME, data: { TITLE, MESSAGE } }
emailemailWorkflow(){ template: EmailNotification.WELCOME, data: { TITLE, MESSAGE } }
inappinAppNotificationWorkflow(){ template: InAppNotification.CUSTOM, data: { TITLE, MESSAGE } }
smsotpWorkflow(){ template: SmsNotification.OTP_CODE, channel: SMS, data: { CODE, TITLE, MESSAGE } }
multistepmultistepWorkflow(){ email, push, inapp, sms all with data }

12. Novu Subscriber Management

File: server/src/notification/notification.service.ts

MethodLineDescription
createSubscriber(user)156Creates Novu subscriber with id, email, name
updateSubscriber(user)219Patches subscriber email + name
deactivateSubscriber(subscriberId)230Deletes subscriber from Novu
getSubscribers() / listSubscribers()195/203Lists subscribers (limit 20)
generateSubscriberHash(subscriberId)311HMAC-SHA256 hash for secure inbox
resubscribeUserToGroupRooms(userId)373Re-subscribes user to all GROUP room topics
resubscribeUserToRoomTopic(userId, roomId)409Re-subscribes user to single room topic

Topic Management

MethodLineDescription
addSubscriberToTopic(subscriberIds, topicKey)208Subscribe users to a topic
removeSubscriberFromTopic(subscriberId, topicKey)234Unsubscribe user from topic
createTopic(key, name)263Create a Novu topic
getTopic(key)258Get topic details
syncListToTopic(roles, topic)248Sync user roles to a topic
ensureRoomTopic(roomId, roomName)338Get-or-create room topic
deriveRoomTopicKey(roomId)325room-${roomId}
getNotificationTriggerTarget(roomType, recipientId, roomId)435Returns { subscriberId } or { type: "Topic", topicKey }

13. Key Enums & Constants

File: server/src/notification/enums/

EnumFilePurpose
PushNotificationpush.enum.ts42 push template keys
Workflowsworkflows.enum.ts40+ workflow ID strings
NotificationChannelnotification-channel.enum.tsEMAIL, PUSH, IN_APP, SMS
Topicstopics.enum.tsADMIN, USERS, SYSTEM
NovuProviderIdnovu.enums.tsOneSignal = 'one-signal'
NovuRecipientTypenovu.enums.tsTopic
AppEventscommon/enums/app-events.enum.ts170+ domain events

14. Data Flow Diagrams

Push Notification (Single Channel)

[Service/Controller]
β†’ workflowsService.pushNotificationWorkflow().trigger({
to: subscriberId,
payload: { userId, template, data }
})
β†’ Novu Framework β†’ step.push('send-push-notification')
β†’ userService.getUser(userId)
β†’ templateService.processTemplate(template, 'push', data, user)
β†’ Returns { subject, body, data }
β†’ Novu dispatches to OneSignal
β†’ Device receives push

Chat Message Push

[User sends message]
β†’ ModerationService processes message
β†’ notificationService.addNotificationJob({ messageId, ... })
β†’ BullMQ Queue (NOTIFICATION)
β†’ NotificationProcessor.processChatNotification()
β†’ getMessageDetails()
β†’ Check message age (CHAT_NOTIFICATION_MAX_AGE_MINUTES)
β†’ Route: PRIVATE β†’ per-recipient with shouldNotifyUser() filtering
GROUP β†’ topic-based single trigger
β†’ MessageFormatterService.formatNotificationContent()
β†’ workflowsService.chatNotificationWorkflow().trigger()
β†’ step.digest() for batching
β†’ step.push('send-chat-notification') β†’ { subject, body, data }
β†’ markMessageNotificationProcessed()

Device Token Registration

[Client registers device]
β†’ notificationService.saveDeviceToken({ token, fingerprint }, user)
β†’ Upsert in device-tokens table
β†’ Fetch all tokens for user
β†’ setCredential(userId, tokens)
β†’ novu.subscribers.credentials.update({
providerId: 'one-signal',
credentials: { deviceTokens: [...] }
})

Event-Driven Push (Space Session Live)

[Space goes live]
β†’ EventEmitter emits AppEvents.SPACE_SESSION_LIVE
β†’ NotificationListener.handleSpaceSessionLiveEvent()
β†’ triggerWithCircuitBreaker()
β†’ CircuitBreakerStrategy + RetryStrategy
β†’ multistepWorkflow().trigger({
to: recipientUserId,
payload: { push: { data: { TITLE, MESSAGE } }, inapp: {...}, ... }
})
β†’ Novu Framework
β†’ step.push('send-multistep-push') β†’ subject, body, data
β†’ step.inApp('send-multistep-inapp') β†’ subject, body, data

15. File Index

FilePathKey Lines
Workflows Serviceserver/src/notification/workflows.service.ts132, 294, 490, 1059, 1309, 1381, 1422
Notification Serviceserver/src/notification/notification.service.ts166, 208, 271, 297, 338, 373, 435
Notification Processorserver/src/notification/processors/notification.processor.ts70, 149, 183, 270, 350
Notification Listenerserver/src/notification/listeners/notification.listener.ts59, 201, 329, 374, 419, 452
Notification Controllerserver/src/notification/notification.controller.ts99
Message Formatterserver/src/notification/services/message-formatter.service.ts22, 92
Reminder Processorserver/src/bullmq/processors/reminder.processor.ts32
Push Enumserver/src/notification/enums/push.enum.ts1-43
Workflows Enumserver/src/notification/enums/workflows.enum.ts1-102
Notification Channel Enumserver/src/notification/enums/notification-channel.enum.ts1-6
Topics Enumserver/src/notification/enums/topics.enum.ts1-5
Novu Enumsserver/src/notification/enums/novu.enums.ts1-7
Device Token DTOserver/src/notification/dto/device-token.dto.ts1-11
Device Token Entityserver/src/notification/entities/device-tokens.entity.ts1-26
InstantDB Serviceserver/src/instantdb/instantdb.service.ts137, 247, 285, 365, 758, 905, 927
Instant Listenerserver/src/instantdb/listeners/instant.listener.ts51, 98, 219
Moderation Serviceserver/src/moderation/moderation.service.ts287
App Eventsserver/src/common/enums/app-events.enum.ts1-171
Template Serviceserver/src/templates/template.service.ts(template processing)