import "server-only"; import { Resend } from "resend"; import { APP_URL } from "@/lib/env"; import { log } from "@/lib/log"; import * as T from "./templates"; export interface SendResult { id: string | null; skipped?: boolean; } class EmailService { private resend: Resend | null; private from: string; private dryRun: boolean; private siteUrl = APP_URL; constructor() { const apiKey = process.env.RESEND_API_KEY; this.from = process.env.RESEND_FROM_EMAIL ?? "PolyLLM "; this.dryRun = !apiKey || process.env.EMAIL_DRY_RUN === "1"; this.resend = apiKey ? new Resend(apiKey) : null; } private async send(to: string, content: T.EmailContent, kind: string): Promise { if (this.dryRun || !this.resend) { log.info("email dry-run", { kind, to: to.replace(/(.).+(@.*)/, "$1***$2") }); if (process.env.EMAIL_DRY_RUN_PRINT === "1") console.log(content.text); return { id: null, skipped: true }; } const { data, error } = await this.resend.emails.send({ from: this.from, to: [to], subject: content.subject, html: content.html, text: content.text, }); if (error) { log.error("email send failed", { kind, error: error.message, name: error.name }); throw new Error(`Email delivery failed: ${error.message}`); } log.info("email sent", { kind, id: data?.id }); return { id: data?.id ?? null }; } sendVerification(to: string, url: string) { return this.send(to, T.verificationEmail(this.siteUrl, url), "verification"); } sendWelcome(to: string, name: string) { return this.send(to, T.welcomeEmail(this.siteUrl, name), "welcome"); } sendPasswordReset(to: string, url: string) { return this.send(to, T.passwordResetEmail(this.siteUrl, url), "password-reset"); } sendPasswordChanged(to: string, ip?: string | null) { return this.send(to, T.passwordChangedEmail(this.siteUrl, new Date().toUTCString(), ip), "password-changed"); } sendEmailChangeApproval(to: string, url: string, newEmail: string) { return this.send(to, T.emailChangeApprovalEmail(this.siteUrl, url, newEmail), "email-change-approval"); } sendEmailChanged(to: string, newEmail: string) { return this.send(to, T.emailChangedEmail(this.siteUrl, newEmail), "email-changed"); } sendAccountDeletion(to: string, url: string) { return this.send(to, T.accountDeletionEmail(this.siteUrl, url), "account-deletion"); } sendNewLogin(to: string, ip?: string | null, userAgent?: string | null) { return this.send(to, T.newLoginEmail(this.siteUrl, new Date().toUTCString(), ip, userAgent), "new-login"); } } let service: EmailService | null = null; export function getEmailService(): EmailService { return (service ??= new EmailService()); }