feat(notifications): wire the notification-digest scheduler (R2-H16)
The 'notification-digest' cron entry in scheduler.ts was registered but had no handler — admins configured a daily digest time/timezone at /admin/reminders and got fire-as-they-hit notifications instead. New runNotificationDigest() service: - Loads per-port reminder config; skips ports with digestEnabled=false - Compares the current hour in the port's configured timezone to the configured digest time; only fires when the hour matches (cron is hourly, so this gate ensures exactly one digest per port per day). - For every user with a port-role on that port, batches their unread notifications from the last 24h (capped at 20 inline + "and N more" link to the inbox) into a single digest email. - Marks the included rows as email_sent so tomorrow's digest doesn't resend them. New email template at notification-digest.ts renders the per-row type/title/description with deep-link to the in-app inbox. Email worker now routes case 'notification-digest' to the dispatcher. 1175/1175 vitest passing. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -81,6 +81,15 @@ export const emailWorker = new Worker(
|
||||
await sendEmail(to, subject, notification.html, undefined, notification.text, portId);
|
||||
break;
|
||||
}
|
||||
case 'notification-digest': {
|
||||
// Recurring scheduler entry (hourly). The dispatcher gates on
|
||||
// each port's configured digest time + timezone so this is a
|
||||
// cheap no-op for hours that don't match.
|
||||
const { runNotificationDigest } =
|
||||
await import('@/lib/services/notification-digest.service');
|
||||
await runNotificationDigest();
|
||||
break;
|
||||
}
|
||||
default:
|
||||
logger.warn({ jobName: job.name }, 'Unknown email job');
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user