import { Resend } from 'resend' import { state } from './state.js' import { queuePodcastChecklistWrite, queueCalendarEventsWrite, queueStudyUsersWrite } from './data.js' import { DEFAULT_RESEND_FROM, DEFAULT_RESEND_TO } from './config.js' import { celebrationDateForYear } from './helpers.js' function toDateKey(date) { return date.toISOString().slice(0, 10) } export function startReminderScheduler() { checkReminders() setInterval(checkReminders, 60 * 60 * 1000) } async function checkReminders() { const todayKey = toDateKey(new Date()) // Episodes const episodes = state.podcastChecklist?.episodes if (Array.isArray(episodes)) { let changed = false for (const ep of episodes) { if (!ep.datePublished || !(ep.reminderDays > 0) || ep.reminderSentAt) continue const publish = new Date(ep.datePublished + 'T12:00:00Z') if (isNaN(publish.getTime())) continue const reminderDate = new Date(publish) reminderDate.setUTCDate(reminderDate.getUTCDate() - ep.reminderDays) if (todayKey === toDateKey(reminderDate)) { const sent = await sendReminderEmail({ label: ep.episodeNumber ? `Episode ${ep.episodeNumber}` : 'Episode', title: ep.title || 'Untitled', series: ep.series, date: ep.datePublished, reminderDays: ep.reminderDays, type: 'episode', }) if (sent) { ep.reminderSentAt = new Date().toISOString(); changed = true } } } if (changed) queuePodcastChecklistWrite() } // Calendar events const events = state.calendarEvents if (Array.isArray(events)) { let changed = false for (const ev of events) { if (!ev.date || !(ev.reminderDays > 0) || ev.reminderSentAt) continue const publish = new Date(ev.date + 'T12:00:00Z') if (isNaN(publish.getTime())) continue const reminderDate = new Date(publish) reminderDate.setUTCDate(reminderDate.getUTCDate() - ev.reminderDays) if (todayKey === toDateKey(reminderDate)) { const sent = await sendReminderEmail({ label: ev.type.charAt(0).toUpperCase() + ev.type.slice(1), title: ev.title, series: null, date: ev.date, reminderDays: ev.reminderDays, type: ev.type, }) if (sent) { ev.reminderSentAt = new Date().toISOString(); changed = true } } } if (changed) queueCalendarEventsWrite() } // Birthdays — fire on the student's celebration day, once per calendar year const users = state.studyUsers ?? [] let birthdayUsersChanged = false const todayYear = new Date().getUTCFullYear() const birthdayKids = [] for (const user of users) { if (!user.birthdayMonth || !user.birthdayDay) continue const celebDate = celebrationDateForYear(user, todayYear) if (!celebDate) continue if (todayKey !== celebDate) continue if (user.birthdayEmailSentYear === todayYear) continue birthdayKids.push({ name: user.displayName || user.username, birthdayMonth: user.birthdayMonth, birthdayDay: user.birthdayDay, hasAlternate: Boolean(user.birthdayAlternateMonth) }) user.birthdayEmailSentYear = todayYear birthdayUsersChanged = true } if (birthdayKids.length > 0) { await sendBirthdayNotificationEmail(birthdayKids) if (birthdayUsersChanged) queueStudyUsersWrite() } } async function sendBirthdayNotificationEmail(kids) { if (!process.env.RESEND_API_KEY) return try { const resend = new Resend(process.env.RESEND_API_KEY) const from = process.env.RESEND_FROM ?? DEFAULT_RESEND_FROM const to = process.env.RESEND_TO ?? DEFAULT_RESEND_TO const MONTHS = ['January','February','March','April','May','June','July','August','September','October','November','December'] const lines = kids.map(k => { const bdayStr = `${MONTHS[k.birthdayMonth - 1]} ${k.birthdayDay}` const alt = k.hasAlternate ? ' (celebrating on alternate date)' : '' return `
The following student${kids.length > 1 ? 's have' : ' has'} a birthday celebration today — consider sending a special message or acknowledging them in your study community!
To edit birthday info, open the Study Users panel in your admin settings.
${label}: ${title}${seriesStr}
Scheduled for ${date} — ${daysText}
This reminder was set ${reminderDays} day${reminderDays === 1 ? '' : 's'} before the date. To change or remove it, open the Calendar in your admin panel.