Files

62 lines
1.7 KiB
TypeScript

'use node';
import { v } from 'convex/values';
import { UseSend } from 'usesend-js';
import { internal } from './_generated/api';
import { internalAction } from './_generated/server';
/**
* Best-effort email dispatch for a notification. Runs the getForEmail query,
* sends via UseSend, and marks the notification emailed on success. Missing
* UseSend env is logged and swallowed — email is never allowed to throw.
*/
export const sendNotificationEmail = internalAction({
args: { notificationId: v.id('notifications') },
handler: async (ctx, { notificationId }) => {
const { to, title, body, link } = await ctx.runQuery(
internal.notifications.getForEmail,
{ notificationId },
);
if (!to) return;
const apiKey = process.env.USESEND_API_KEY;
const useSendUrl = process.env.USESEND_URL;
const from = process.env.USESEND_FROM_EMAIL;
if (!apiKey || !useSendUrl || !from) {
console.warn(
'Skipping notification email: USESEND_API_KEY, USESEND_URL, and USESEND_FROM_EMAIL must be set.',
);
return;
}
const text = link ? `${body}\n\n${link}` : body;
const html = `
<div style="max-width: 600px; margin: 0 auto; font-family: Arial, sans-serif;">
<h2>${title}</h2>
<p>${body}</p>
${link ? `<p><a href="${link}">${link}</a></p>` : ''}
</div>
`;
const useSend = new UseSend(apiKey, useSendUrl);
const result = await useSend.emails.send({
from,
to: [to],
subject: title,
text,
html,
});
if (result.error) {
console.error('UseSend error: ' + JSON.stringify(result.error));
return;
}
await ctx.runMutation(internal.notifications.markEmailed, {
notificationId,
});
},
});