TypeScript 97.4%
SQL 1%
JavaScript 0.9%
CSS 0.6%
1import "server-only";2import { Resend } from "resend";3import { APP_URL } from "@/lib/env";4import { log } from "@/lib/log";5import * as T from "./templates";67export interface SendResult {8 id: string | null;9 skipped?: boolean;10}1112class EmailService {13 private resend: Resend | null;14 private from: string;15 private dryRun: boolean;16 private siteUrl = APP_URL;1718 constructor() {19 const apiKey = process.env.RESEND_API_KEY;20 this.from = process.env.RESEND_FROM_EMAIL ?? "PolyLLM <polyllm@mail.spboucher.ai>";21 this.dryRun = !apiKey || process.env.EMAIL_DRY_RUN === "1";22 this.resend = apiKey ? new Resend(apiKey) : null;23 }2425 private async send(to: string, content: T.EmailContent, kind: string): Promise<SendResult> {26 if (this.dryRun || !this.resend) {27 log.info("email dry-run", { kind, to: to.replace(/(.).+(@.*)/, "$1***$2") });28 if (process.env.EMAIL_DRY_RUN_PRINT === "1") console.log(content.text);29 return { id: null, skipped: true };30 }31 const { data, error } = await this.resend.emails.send({32 from: this.from,33 to: [to],34 subject: content.subject,35 html: content.html,36 text: content.text,37 });38 if (error) {39 log.error("email send failed", { kind, error: error.message, name: error.name });40 throw new Error(`Email delivery failed: ${error.message}`);41 }42 log.info("email sent", { kind, id: data?.id });43 return { id: data?.id ?? null };44 }4546 sendVerification(to: string, url: string) {47 return this.send(to, T.verificationEmail(this.siteUrl, url), "verification");48 }49 sendWelcome(to: string, name: string) {50 return this.send(to, T.welcomeEmail(this.siteUrl, name), "welcome");51 }52 sendPasswordReset(to: string, url: string) {53 return this.send(to, T.passwordResetEmail(this.siteUrl, url), "password-reset");54 }55 sendPasswordChanged(to: string, ip?: string | null) {56 return this.send(to, T.passwordChangedEmail(this.siteUrl, new Date().toUTCString(), ip), "password-changed");57 }58 sendEmailChangeApproval(to: string, url: string, newEmail: string) {59 return this.send(to, T.emailChangeApprovalEmail(this.siteUrl, url, newEmail), "email-change-approval");60 }61 sendEmailChanged(to: string, newEmail: string) {62 return this.send(to, T.emailChangedEmail(this.siteUrl, newEmail), "email-changed");63 }64 sendAccountDeletion(to: string, url: string) {65 return this.send(to, T.accountDeletionEmail(this.siteUrl, url), "account-deletion");66 }67 sendNewLogin(to: string, ip?: string | null, userAgent?: string | null) {68 return this.send(to, T.newLoginEmail(this.siteUrl, new Date().toUTCString(), ip, userAgent), "new-login");69 }70}7172let service: EmailService | null = null;73export function getEmailService(): EmailService {74 return (service ??= new EmailService());75}76