import { BadRequestException, Body, Controller, Headers, Post } from '@nestjs/common'; import { SessionVerifier } from '../platform/session.verifier'; import type { MessagePrincipal } from '../identity/actor.resolver'; import { MailService } from './mail.service'; import { MailInternalDto, MailSendDto } from './mail.dto'; import type { TemplateSource } from '../templates/template.model'; @Controller('v1/mail') export class MailController { constructor( private readonly mail: MailService, private readonly session: SessionVerifier, ) {} /** App-to-app mail — renders a template and posts it into the recipient's in-app inbox (no SMTP). */ @Post('internal') async internal(@Body() body: MailInternalDto, @Headers('authorization') authorization?: string) { const principal = this.principal(authorization); return this.mail.postInternal(principal, { source: this.source(body), recipientUserId: body.recipientUserId, vars: body.vars ?? {}, ...(body.locale ? { locale: body.locale } : {}), idempotencyKey: body.idempotencyKey, }); } /** External email (SMTP) + an in-app mirror when a registered recipient is named. */ @Post('send') async send(@Body() body: MailSendDto, @Headers('authorization') authorization?: string) { const principal = this.principal(authorization); return this.mail.sendExternalWithMirror(principal, { source: this.source(body), target: body.target, vars: body.vars ?? {}, ...(body.locale ? { locale: body.locale } : {}), idempotencyKey: body.idempotencyKey, ...(body.purpose ? { purpose: body.purpose } : {}), ...(body.attachments && body.attachments.length > 0 ? { attachments: body.attachments } : {}), ...(body.mirrorToUserId ? { mirrorToUserId: body.mirrorToUserId } : {}), }); } private source(body: { key?: string; version?: number; inline?: { subject?: string; html?: string; text?: string; variables?: string[] } }): TemplateSource { if (body.inline && body.key) throw new BadRequestException('provide either "key" or "inline", not both'); if (body.inline) return { inline: body.inline }; if (body.key) return body.version != null ? { key: body.key, version: body.version } : { key: body.key }; throw new BadRequestException('one of "key" or "inline" is required'); } private principal(authorization?: string): MessagePrincipal { const token = (authorization ?? '').replace(/^Bearer\s+/i, ''); if (!token) throw new BadRequestException('Authorization bearer token is required'); return this.session.verify(token); } }