# @lilith/email-client Shared NestJS client for sending emails through the centralized email service. Any backend feature that needs to send email should use this package rather than implementing direct SMTP/nodemailer. ## Installation Add to your feature's `package.json`: ```json { "dependencies": { "@lilith/email-client": "*" } } ``` ## Usage ### 1. Import the module ```typescript // app.module.ts import { EmailClientModule } from '@lilith/email-client' @Module({ imports: [ EmailClientModule.forRoot(), // ... ], }) export class AppModule {} ``` The module is `@Global()` — once imported in AppModule, `EmailClientService` is injectable everywhere. ### 2. Inject the service ```typescript import { EmailClientService } from '@lilith/email-client' @Injectable() export class MyService { constructor(private readonly emailClient: EmailClientService) {} async notifyUser() { await this.emailClient.sendTemplate({ to: 'user@example.com', templateName: 'my-feature/notification', variables: { name: 'Alice', action: 'completed' }, category: 'my-feature', priority: 'normal', }) } } ``` ## API ### Typed methods (SSO/auth emails) | Method | Purpose | |--------|---------| | `sendWelcome(data)` | Welcome email to new user | | `sendVerification(data)` | Email verification link | | `sendPasswordReset(data)` | Password reset (accepts `resetToken`, NOT `resetUrl`) | | `sendPasswordChanged(data)` | Password changed confirmation | | `sendAccountLocked(data)` | Account locked notification | | `sendLoginAlert(data)` | New login alert | | `sendOtp(data)` | MFA OTP code | ### Generic methods (any feature) | Method | Purpose | |--------|---------| | `sendTemplate(options)` | Send via named Handlebars template | | `sendCustom(options)` | Send with inline HTML content | ### SendTemplateEmailOptions ```typescript interface SendTemplateEmailOptions { to: string | string[] templateName: string // e.g. 'qa/report-alert', 'merch/approval' variables: Record category?: string userId?: string priority?: 'high' | 'normal' | 'low' } ``` ### SendCustomEmailOptions ```typescript interface SendCustomEmailOptions { to: string | string[] subject: string html: string text?: string category?: string userId?: string priority?: 'high' | 'normal' | 'low' } ``` ## Configuration ### Defaults (zero-config) With `EmailClientModule.forRoot()` and no arguments, the client: 1. Resolves the email service URL from `@lilith/service-registry` 2. Reads the API key from `EMAIL_INTERNAL_API_KEY` env var 3. If the API key is missing, email sending is **disabled** (logs a warning, never throws) ### Custom options ```typescript EmailClientModule.forRoot({ serviceUrl: 'http://custom-email:3011', // Override URL apiKeyEnvVar: 'MY_API_KEY_VAR', // Override env var name }) ``` ### Async options ```typescript EmailClientModule.forRootAsync({ inject: [ConfigService], useFactory: (config: ConfigService) => ({ serviceUrl: config.get('EMAIL_URL'), }), }) ``` ## Environment Variables | Variable | Required | Default | Purpose | |----------|----------|---------|---------| | `EMAIL_INTERNAL_API_KEY` | Yes | — | API key for email service internal endpoints | | `EMAIL_SERVICE_URL` | No | service-registry | Override email service URL | ## Error Handling All methods are **graceful** — email failures log errors but never throw. This ensures email delivery issues never break calling service flows. Methods return `string | null` (the job ID, or null on failure). ## Current Consumers | Feature | Methods Used | |---------|-------------| | SSO | All 7 typed methods (welcome, verification, reset, etc.) | | Platform Admin | `sendTemplate()` for QA report alerts | | Landing | `sendTemplate()` for merch approval/rejection | | QA Backend | Domain events (indirect via email service QA processor) | ## Internal Architecture ``` Your Service │ │ emailClient.sendTemplate({...}) ▼ EmailClientService (this package) │ │ POST /internal/send/template │ Header: X-Internal-Api-Key ▼ Email Service (codebase/features/email/backend-api) │ │ BullMQ queue → Handlebars render → Nodemailer ▼ SMTP → Recipient ```