import config from 'config'; import { Resend } from 'resend'; import { emailTemplates } from './email-templates.js'; import initLogger from './logger.js'; const logger = initLogger(); const resend = (() => { if (config.email.enabled) { try { return new Resend(config.email.apiKey); } catch (error) { logger.error(`Failed to initialize Resend: ${error.message}`); } } return null; })(); export async function renderEmail(template, { to, subject, props }) { const { html, text } = await emailTemplates.render(`${template}.vue`, { props: { ...props, address: config.web.address, }, }); return { from: config.email.from, to, subject, html, text, }; } // always batches, even for a single item: Resend's batch endpoint accepts a batch of one, // and passing batchValidation: 'permissive' means one bad recipient among many doesn't // sink the rest of the batch (the default 'strict' mode fails the whole call instead) export async function sendEmails(items) { if (!config.email.enabled) { return null; } if (!resend) { logger.warn('E-mails are enabled, but Resend is not initialized'); return null; } const rendered = await Promise.all(items.map(async (item) => { try { return await renderEmail(item.template, item); } catch (error) { logger.error(`Failed to render '${item.template}' e-mail for '${item.to}': ${error.message} — ${JSON.stringify(item.props)}`); return null; } })); const sentItems = items.filter((item, index) => rendered[index]); const emails = rendered.filter(Boolean); if (emails.length === 0) { return null; } const { data, error } = await resend.batch.send(emails, { batchValidation: 'permissive' }); if (error) { logger.error(`Failed to send ${emails.length} e-mail(s) (${error.statusCode} ${error.name}): ${error.message}`); return null; } data.errors?.forEach(({ index, message }) => { const item = sentItems[index]; logger.error(`Failed to send '${item.template}' e-mail to '${item.to}': ${message} — ${JSON.stringify(item.props)}`); }); logger.info(`Sent ${emails.length - (data.errors?.length || 0)}/${items.length} e-mail(s)`); return data; }