Sprint 2. Shared context and the definition of done are in docs/sprints/sprint-2.md.
Description
NotificationService.send claims the dedupe key before it sends. If the send fails the key is already burned, so that email never goes out, ever. MailService.send makes it worse by turning a non-2xx from Resend into a console.error and returning as though it worked.
Separately, sendReminders calls NotificationService.org() and UserDAO.findById inside its innermost loop.
Acceptance Criteria
Other Notes
NotificationLogDAO.claim and NotificationService.send. A status field on the log document is probably simpler than claim-then-release; explain whichever you pick.
UserDAO.findSummaries already does the batched lookup, but its SUMMARY_FIELDS projection omits notificationPreferences, and Recipient makes that field optional, so dropping it in as is type-checks and silently defeats the reminders opt-out. Widen the projection first.
- Getting
sendReminders to send anything takes two steps, not one. The virtual coding event is seeded 6 days out and the reminder windows are 24 and 72 hours, so first change the day(6, ...) near the bottom of scripts/seed.ts to day(2, ...) and re-seed. The seed returns early with "Demo events already exist" when the organizer already has events, so drop the database before re-seeding. Second, the seed creates no sign-ups at all, so sign up for that shift by hand as maya@example.com, who the seed already clears — that event requires clearance. Her sign-up lands confirmed straight away: ensureUser sets waiverVersionAccepted: 1 on every seeded account and the event is requiresApproval: false. Reminders only go to confirmed.
- No retry queue and no backoff. The runner fires hourly; that is retry enough.
- Other tickets touch
JobService.runAll too. If a job you do not recognise appears in the map on rebase, convert it to the new return shape the same way as the rest; it is a one-line change per job.
Sprint 2. Shared context and the definition of done are in
docs/sprints/sprint-2.md.Description
NotificationService.sendclaims the dedupe key before it sends. If the send fails the key is already burned, so that email never goes out, ever.MailService.sendmakes it worse by turning a non-2xx from Resend into aconsole.errorand returning as though it worked.Separately,
sendReminderscallsNotificationService.org()andUserDAO.findByIdinside its innermost loop.Acceptance Criteria
MailService.sendreports failure to its caller, and a bad address is distinguishable from Resend being downsendRemindersandAttendanceService.approveRosterbatch their lookups and fetch org settings once per runjobs/runreturns sent, skipped and failed per job. Jobs that send nothing (expireHolds,expireLapsed,closeStale) use the same shape; say in the PR how you mapped themOther Notes
NotificationLogDAO.claimandNotificationService.send. A status field on the log document is probably simpler than claim-then-release; explain whichever you pick.UserDAO.findSummariesalready does the batched lookup, but itsSUMMARY_FIELDSprojection omitsnotificationPreferences, andRecipientmakes that field optional, so dropping it in as is type-checks and silently defeats the reminders opt-out. Widen the projection first.sendRemindersto send anything takes two steps, not one. The virtual coding event is seeded 6 days out and the reminder windows are 24 and 72 hours, so first change theday(6, ...)near the bottom ofscripts/seed.tstoday(2, ...)and re-seed. The seed returns early with "Demo events already exist" when the organizer already has events, so drop the database before re-seeding. Second, the seed creates no sign-ups at all, so sign up for that shift by hand asmaya@example.com, who the seed already clears — that event requires clearance. Her sign-up landsconfirmedstraight away:ensureUsersetswaiverVersionAccepted: 1on every seeded account and the event isrequiresApproval: false. Reminders only go toconfirmed.JobService.runAlltoo. If a job you do not recognise appears in the map on rebase, convert it to the new return shape the same way as the rest; it is a one-line change per job.