85 lines
2.1 KiB
JavaScript
85 lines
2.1 KiB
JavaScript
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;
|
|
}
|