Happy House - Ecommerce Docs
Developer ResourcesNotification

Notification Implementation

Concrete implementation details for the auth-only email notification path — render functions, BullMQ payloads, and the processor.

Notification Implementation

Email Render Functions

Implemented in apps/api/src/modules/auth/emails/:

  • password-reset.email.tsbuildPasswordResetEmail(). Subject: "Reset your Happy House password".
  • verify-email.email.tsbuildVerifyEmailEmail(). Subject: "Verify your email address".
  • verify-email-otp.email.tsbuildVerifyEmailOtpEmail(). Subject is a required caller-supplied parameter, since it serves two distinct callers with two distinct subjects: "Verify your email address" for the email-verification OTP flow, "Account recovery code" for account recovery.

Each function returns a RenderedEmail:

interface RenderedEmail {
  subject: string;
  html: string;
  text: string;
  previewText?: string;
}

All dynamic values (name, reset URL, OTP code, expiry) are passed through escapeHtml() before interpolation into HTML.

Shared Layout

Implemented in apps/api/src/modules/auth/emails/shared/:

  • layout.ts — exports buildEmailLayout(), escapeHtml(), resolveSupportEmail().
  • branding.ts — brand constants (name, colors, support email).
  • types.ts — the RenderedEmail type.

BullMQ Payload

packages/jobs (@happy-shop/jobs) defines:

export enum NotificationJob {
  SEND_OTP = "notification.send_otp",
  SEND_EMAIL = "notification.send_email",
}

export interface SendEmailPayload {
  email: string;
  subject: string;
  html: string;
  text: string;
  previewText?: string;
}

export interface SendOtpPayload {
  phoneNumber: string;
  otp: string;
  userId: string;
  purpose?: OtpPurpose;
}

Both job types run on the NOTIFICATIONS queue (QueueName.NOTIFICATIONS).

Producer

apps/api/src/modules/auth/services/auth-email.service.ts (AuthEmailService):

  • Three send*Safe() methods build a RenderedEmail via the render functions above, then enqueue { email, subject, html, text, previewText } as a SEND_EMAIL job via BullService.addJob() against the injected NOTIFICATIONS queue.
  • enqueueOtpDelivery() enqueues a SEND_OTP job for SMS-delivered OTPs.

Processor

Implemented in apps/api/src/modules/notifications/notifications.processor.ts (NotificationsProcessor, @Processor(QueueName.NOTIFICATIONS)):

  • processSendEmail(job) — calls EmailNotificationService.send({ to, subject, html, text, previewText }).
  • processSendOtp(job) — calls OtpSmsService.sendOtp(phoneNumber, otp, purpose).
  • Unknown job names are logged via this.logger.warn and skipped.

These are the only two job-name branches this processor handles — SEND_PUSH, SEND_PROMO_PUSH, SEND_PROMO_EMAIL, SEND_EMAIL_BATCH, and EXECUTE_PROMO_NOTIFICATION_SCHEDULE (all Firebase/push- or e-commerce-order-specific) have been removed along with their producers.

Email Delivery

apps/api/src/modules/notifications/email.service.ts (EmailNotificationService) wraps @happy-shop/email's EmailClient, which is Resend-backed. The EmailClient is provided via the EMAIL_CLIENT DI token in apps/api/src/modules/notifications/notifications.module.ts, configured from RESEND_API_KEY/RESEND_DEFAULT_FROM (read via RuntimeConfigService, falling back to ConfigService).

Testing

  • apps/api/src/modules/auth/emails/*.spec.ts — one spec per render function, asserting RenderedEmail shape and that escapeHtml() is applied to interpolated values.
  • apps/api/src/modules/auth/services/auth-email.service.spec.ts — asserts the enqueued payload shape.
  • apps/api/src/modules/notifications/notifications.processor.spec.ts — asserts SEND_EMAIL/SEND_OTP dispatch and the "unknown job name" warn-and-skip path.

What This Replaced

This auth-only path replaced a much larger e-commerce/LMS transactional notification layer (order/checkout/buy-now/lifecycle/refund/digital-key-delivery events, a Redis-deduplicated router, a recipient resolver, React-based email templates, and MongoDB-persisted in-app/push delivery via Firebase) — none of which is part of this Happy House product, and all of which has been fully removed.