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.ts—buildPasswordResetEmail(). Subject:"Reset your Happy House password".verify-email.email.ts—buildVerifyEmailEmail(). Subject:"Verify your email address".verify-email-otp.email.ts—buildVerifyEmailOtpEmail(). 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— exportsbuildEmailLayout(),escapeHtml(),resolveSupportEmail().branding.ts— brand constants (name, colors, support email).types.ts— theRenderedEmailtype.
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 aRenderedEmailvia the render functions above, then enqueue{ email, subject, html, text, previewText }as aSEND_EMAILjob viaBullService.addJob()against the injectedNOTIFICATIONSqueue. enqueueOtpDelivery()enqueues aSEND_OTPjob for SMS-delivered OTPs.
Processor
Implemented in apps/api/src/modules/notifications/notifications.processor.ts (NotificationsProcessor, @Processor(QueueName.NOTIFICATIONS)):
processSendEmail(job)— callsEmailNotificationService.send({ to, subject, html, text, previewText }).processSendOtp(job)— callsOtpSmsService.sendOtp(phoneNumber, otp, purpose).- Unknown job names are logged via
this.logger.warnand 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, assertingRenderedEmailshape and thatescapeHtml()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— assertsSEND_EMAIL/SEND_OTPdispatch 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.