import { Body, Controller, HttpCode, HttpStatus, Inject, Post } from "@nestjs/common"; import { and, eq } from "drizzle-orm"; import { createHash } from "node:crypto"; import { schema, type Database } from "@courtmitra/db"; import { parseReplyAddress, verifyReplyToken } from "@courtmitra/domain"; import { DB } from "../db/db.module.js"; import { WorkflowService } from "../workflow/inngest.module.js"; import { Public } from "../auth/public.decorator.js"; @Controller() export class EmailController { constructor( @Inject(DB) private readonly db: Database, @Inject(WorkflowService) private readonly workflow: WorkflowService, ) {} @Public() @Post("webhooks/email") @HttpCode(HttpStatus.OK) async handleInbound(@Body() body: unknown) { const payload = body as Record; const provider = "resend_email"; const to = (payload["to"] as string) ?? ""; const from = (payload["from"] as string) ?? ""; const subject = (payload["subject"] as string) ?? ""; const textBody = (payload["text"] as string) ?? (payload["html"] as string) ?? ""; const providerEventId = (payload["messageId"] as string | undefined) ?? `email_${createHash("sha256") .update(JSON.stringify({ to, from, subject, textBody })) .digest("hex")}`; const [existing] = await this.db .select({ id: schema.webhookEvents.id }) .from(schema.webhookEvents) .where(and( eq(schema.webhookEvents.provider, provider), eq(schema.webhookEvents.providerEventId, providerEventId), )) .limit(1); if (existing) { return { ok: true, deduped: true }; } const replyInfo = parseReplyAddress(to); const verifiedReply = replyInfo ? verifyReplyToken(replyInfo.token) : null; await this.db.insert(schema.webhookEvents).values({ provider, providerEventId, payload: payload as Record, processedAt: verifiedReply ? new Date() : null, }); if (!verifiedReply) { return { ok: true, queued: false, reason: "unresolved_reply_token" }; } await this.workflow.send("email.reply_received", { missionId: verifiedReply.missionId, actionId: verifiedReply.actionId, from, subject, body: textBody.slice(0, 50000), messageId: providerEventId, replyToken: replyInfo?.token ?? "", toAddress: to, }); return { ok: true }; } }