import { Module } from '@nestjs/common'; import { ConfigService } from '@nestjs/config'; import { MailerModule } from '@nestjs-modules/mailer'; import { SettingsModule } from '../settings/settings.module'; import { SettingsService } from '../settings/settings.service'; import { MailService } from './mail.service'; /** * MailModule — system email delivery (password reset, welcome emails). * * D-06: SMTP transport is now sourced from the DB SmtpConfig row (priority 1) * with an env-var fallback (priority 2) when no DB row exists. * * Transport priority: * 1. DB SmtpConfig (first row — single-tenant default; set via /settings/smtp) * 2. Env vars: MAIL_HOST / MAIL_PORT / MAIL_USER / MAIL_PASS * 3. Legacy env vars: TESSERA_SMTP_HOST / TESSERA_SMTP_PORT / TESSERA_SMTP_USER / TESSERA_SMTP_PASSWORD * 4. Final hardcoded fallback: localhost:1025 (Mailhog / dev default) * * The factory is async because getStartupSmtpConfig() reads from the DB. * No circular import risk: MailModule → SettingsModule → CalendarModule (no reverse edges). */ @Module({ imports: [ SettingsModule, MailerModule.forRootAsync({ imports: [SettingsModule], useFactory: async (settingsService: SettingsService, configService: ConfigService) => { // Priority 1: DB SmtpConfig (getStartupSmtpConfig uses findFirst — single-tenant default) const db = await settingsService.getStartupSmtpConfig(); if (db) { // T-07-11: DB password used only to build transport; never logged return { transport: { host: db.host, port: db.port, secure: db.secure, requireTLS: db.requireTLS, auth: db.username ? { user: db.username, pass: db.password ?? '' } : undefined, }, defaults: { from: db.fromAddress, }, }; } // Priority 2: Env vars (new names first, legacy TESSERA_SMTP_* as secondary fallback) const host = configService.get('MAIL_HOST') ?? configService.get('TESSERA_SMTP_HOST') ?? 'localhost'; const port = configService.get('MAIL_PORT') ?? configService.get('TESSERA_SMTP_PORT') ?? 1025; const user = configService.get('MAIL_USER') ?? configService.get('TESSERA_SMTP_USER') ?? ''; const pass = configService.get('MAIL_PASS') ?? configService.get('TESSERA_SMTP_PASSWORD') ?? ''; const from = configService.get('TESSERA_SMTP_FROM') ?? 'Tessera '; const secure = configService.get('TESSERA_SMTP_SECURE', 'false') === 'true'; return { transport: { host, port, secure, auth: { user, pass }, }, defaults: { from }, }; }, inject: [SettingsService, ConfigService], }), ], providers: [MailService], exports: [MailService], }) export class MailModule {}