Notification Architecture
Architecture of the auth-only transactional email system built on BullMQ, Resend, and a shared HTML email-rendering layer.
Notification Architecture
Scope
The notification system in this codebase is auth-only transactional email. Firebase/FCM push, the in-app notification feed (a MongoDB-backed inbox with realtime fanout), the e-commerce/LMS transactional order-notification layer, and the admin-notifications polling API have all been removed. The only surviving notification surface is password-reset, verify-email, and verify-email-OTP email, sent through Resend.
Component Map
Flow
- An auth flow (password reset, email verification, account-recovery OTP) calls
AuthEmailService(apps/api/src/modules/auth/services/auth-email.service.ts). AuthEmailServicecalls one of three render functions inapps/api/src/modules/auth/emails/(buildPasswordResetEmail,buildVerifyEmailEmail,buildVerifyEmailOtpEmail), each of which composes the sharedbuildEmailLayout()helper withescapeHtml()-wrapped dynamic values and returns aRenderedEmail({ subject, html, text, previewText? }).AuthEmailServiceenqueues aNotificationJob.SEND_EMAILjob (payload typeSendEmailPayloadfrom@happy-shop/jobs) on theNOTIFICATIONSqueue, or aNotificationJob.SEND_OTPjob (payload typeSendOtpPayload) for SMS-delivered OTPs.NotificationsProcessor(apps/api/src/modules/notifications/notifications.processor.ts) dispatches by job name to exactly two handlers:processSendEmailandprocessSendOtp.processSendEmailcallsEmailNotificationService.send(), which sends through@happy-shop/email's Resend-backedEmailClient.processSendOtpcallsOtpSmsService.sendOtp()(SMS delivery, unrelated to email).
What Was Removed
| Removed | Replaced by |
|---|---|
Firebase Admin SDK / FCM push (packages/firebase, apps/api/src/services/firebase) | Nothing — push notifications are not part of this product |
In-app notification feed + push-token sync (apps/api/src/modules/notification, singular) | Nothing — no in-app inbox exists |
E-commerce/LMS transactional order-notification layer (notifications/transactional/*: router, catalog, recipient resolver, React email templates) | Auth's own 3 render functions in apps/api/src/modules/auth/emails/ |
Admin-notifications polling REST API (apps/api/src/modules/admin-notifications) | Nothing — no admin notification inbox exists |
MongoDB Notification/NotificationSetting schemas (packages/mongodb) | Nothing — no persistence layer for notifications; email delivery is fire-and-forget through Resend |
| Realtime/WebSocket fanout for notifications | N/A — never existed as a real transport in this codebase; was informal naming for the admin-notifications polling API |
Data Model
There is no notification persistence layer. Sent emails are not recorded in any database table or collection; delivery state is whatever Resend's API returns synchronously to EmailClient.send(). NotificationsProcessor's BullMQ job history (retained per the queue's removeOnComplete/removeOnFail policy) is the only record of a send attempt.
Shared Email-Rendering Layer
apps/api/src/modules/auth/emails/shared/:
layout.ts—buildEmailLayout()(shared HTML wrapper with header/footer),escapeHtml()(XSS-safe interpolation),resolveSupportEmail().branding.ts— brand name, colors, support email defaults.types.ts— theRenderedEmailinterface.
This layer was migrated from the (now-deleted) e-commerce order-notification templates before that layer was removed, so auth inherits the same shared-layout/plaintext-fallback/preheader/branding pattern the order templates used, rather than the older bare-string-returning functions auth used previously.