'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 = `
`; 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, }); }, });