import type { PostgresJsDatabase } from 'drizzle-orm/postgres-js' import { SettingsService } from './settings.service.js' interface SendOpts { to: string subject: string html: string text?: string } interface EmailProvider { send(opts: SendOpts): Promise } class ResendProvider implements EmailProvider { constructor(private db: PostgresJsDatabase) {} async send(opts: SendOpts): Promise { const apiKey = await SettingsService.get(this.db, 'email.resend_api_key') ?? process.env.RESEND_API_KEY const from = await SettingsService.get(this.db, 'email.from_address') ?? process.env.MAIL_FROM if (!apiKey) throw new Error('Resend API key not configured') if (!from) throw new Error('email.from_address not configured') const res = await fetch('https://api.resend.com/emails', { method: 'POST', headers: { Authorization: `Bearer ${apiKey}`, 'Content-Type': 'application/json', }, body: JSON.stringify({ from, to: opts.to, subject: opts.subject, html: opts.html, ...(opts.text ? { text: opts.text } : {}), }), }) if (!res.ok) { const body = await res.text() throw new Error(`Resend API error ${res.status}: ${body}`) } } } class SendGridProvider implements EmailProvider { constructor(private db: PostgresJsDatabase) {} async send(opts: SendOpts): Promise { const apiKey = await SettingsService.get(this.db, 'email.sendgrid_api_key') const from = await SettingsService.get(this.db, 'email.from_address') ?? process.env.MAIL_FROM if (!apiKey) throw new Error('SendGrid API key not configured') if (!from) throw new Error('email.from_address not configured') const res = await fetch('https://api.sendgrid.com/v3/mail/send', { method: 'POST', headers: { Authorization: `Bearer ${apiKey}`, 'Content-Type': 'application/json', }, body: JSON.stringify({ personalizations: [{ to: [{ email: opts.to }] }], from: { email: from }, subject: opts.subject, content: [ { type: 'text/html', value: opts.html }, ...(opts.text ? [{ type: 'text/plain', value: opts.text }] : []), ], }), }) if (!res.ok) { const body = await res.text() throw new Error(`SendGrid API error ${res.status}: ${body}`) } } } class SmtpProvider implements EmailProvider { async send(_opts: SendOpts): Promise { throw new Error('SMTP email provider is not yet implemented') } } class TestProvider implements EmailProvider { static lastEmail: SendOpts | null = null static emails: SendOpts[] = [] async send(opts: SendOpts): Promise { TestProvider.lastEmail = opts TestProvider.emails.push(opts) } static reset() { TestProvider.lastEmail = null TestProvider.emails = [] } } export const EmailService = { async send(db: PostgresJsDatabase, opts: SendOpts): Promise { const provider = await SettingsService.get(db, 'email.provider') ?? process.env.MAIL_PROVIDER switch (provider) { case 'resend': return new ResendProvider(db).send(opts) case 'sendgrid': return new SendGridProvider(db).send(opts) case 'smtp': return new SmtpProvider().send(opts) case 'test': return new TestProvider().send(opts) default: throw new Error('Email provider not configured. Set email.provider in app_settings.') } }, getTestEmails: () => TestProvider.emails, getLastTestEmail: () => TestProvider.lastEmail, resetTestEmails: () => TestProvider.reset(), }