Happy House - Ecommerce Docs
Developer ResourcesNotification

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

  1. An auth flow (password reset, email verification, account-recovery OTP) calls AuthEmailService (apps/api/src/modules/auth/services/auth-email.service.ts).
  2. AuthEmailService calls one of three render functions in apps/api/src/modules/auth/emails/ (buildPasswordResetEmail, buildVerifyEmailEmail, buildVerifyEmailOtpEmail), each of which composes the shared buildEmailLayout() helper with escapeHtml()-wrapped dynamic values and returns a RenderedEmail ({ subject, html, text, previewText? }).
  3. AuthEmailService enqueues a NotificationJob.SEND_EMAIL job (payload type SendEmailPayload from @happy-shop/jobs) on the NOTIFICATIONS queue, or a NotificationJob.SEND_OTP job (payload type SendOtpPayload) for SMS-delivered OTPs.
  4. NotificationsProcessor (apps/api/src/modules/notifications/notifications.processor.ts) dispatches by job name to exactly two handlers: processSendEmail and processSendOtp.
  5. processSendEmail calls EmailNotificationService.send(), which sends through @happy-shop/email's Resend-backed EmailClient.
  6. processSendOtp calls OtpSmsService.sendOtp() (SMS delivery, unrelated to email).

What Was Removed

RemovedReplaced 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 notificationsN/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.tsbuildEmailLayout() (shared HTML wrapper with header/footer), escapeHtml() (XSS-safe interpolation), resolveSupportEmail().
  • branding.ts — brand name, colors, support email defaults.
  • types.ts — the RenderedEmail interface.

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.