PR: Event email reminders — materialized Cosmos instances + per-registrant UI¶
Summary¶
Materialized reminder rows (Cosmos) — Each eligible (event × reminder offset × ticket payment) is stored as EventReminderInstanceDoc (DocType: eventreminderinstance, partition {organisationId}_eventreminderinstance). Same deterministic id as legacy idempotency: eventId|offsetMinutes|ticketPaymentId. Status: Pending, Sent, or Cancelled. Scheduled send time is stored as ScheduledSendEpochSeconds; SentAtEpochSeconds is set when the WebJob claims the row.
Sync hooks — CosmosDbEventService.UpdateAsync runs EventEmailReminderInstanceSyncService.RebuildForEventAsync after event save (rebuilds pending/cancelled instances for the event, then merges legacy EventReminderSentDoc markers). CosmosDbTicketPaymentService create/update calls SyncForPaymentAsync for event-ticket payments.
Rebuild semantics — Full event rebuild deletes pending and cancelled instance rows only; Sent rows are kept so updates do not remove audit/history of emails already delivered. Upsert skips creating a new row when a Sent document already exists for the same deterministic id. Unpublished events are treated as ineligible for new pending rows (reminders are not materialized while the event is unpublished).
WebJob — EventEmailReminderSchedulerService loads pending instances whose ScheduledSendEpochSeconds falls in the current UTC minute window, then TryClaimPendingToSentAsync, then sends via EventEmailService.
Manager API + UI — GET .../events/{eventId}/email-reminder-instances reads Cosmos instance docs (filters out Cancelled). If no rows exist but reminders are configured, runs a one-time self-heal rebuild. Reminders tab on event details: Per registrant table (recipient, template, offset, scheduled send, sent at, status, payment link).
Reminder configuration UX — Minute-based offsets use a dropdown in 5-minute steps (5–720 minutes), aligned with validation and scheduler granularity. API validation rejects offsets not divisible by 5. event-email-reminder-helpers rounds minute amounts to the grid; unit change handling avoids misinterpreting amount when switching days/hours/minutes.
Unpublish — Unpublishing from event details opens a confirmation modal warning that pending scheduled reminders will be cancelled; sent reminders remain on record.
Tooling — Raklet.Backend/Content/dist/ is gitignored; run npx grunt ngtemplates concat:development in Raklet.Backend locally or in CI to produce bundles. Bump Cdn-Version in Raklet.Admin/Web.config when JS/locales change.
How to test¶
- Build:
.\scripts\dev\build-fast.ps1 Debug -Project Models,-Project Services,-Project Raklet.Api,-Project Raklet.UnitTests. StopRaklet.WebJobs.Secondaryif DLL copy is locked, then build WebJob project if needed. - Event + reminders: Create/edit event with at least one valid reminder row; save (triggers rebuild).
- Minutes: Choose Minutes and pick a value from the dropdown; save — API should accept only 5-minute multiples (
EventEmailReminderValidation). - Registration: Complete an event ticket purchase; open event Details → Reminders — Per registrant should list rows from Cosmos with correct scheduled time (event timezone in UI).
- Sent preserved: After a reminder has been sent (or a
Sentinstance exists), change event title/start and save — Sent instance rows should remain in Cosmos / per-registrant list; pending rows can still be rebuilt. - Unpublish: Click Unpublish — confirm modal mentions pending reminders; confirm — event unpublishes; pending materialization should not apply while unpublished.
- API:
GET v2/organisations/{orgId}/events/{eventId}/email-reminder-instancesreturns non-cancelled rows matching Cosmos.
Deploy notes¶
- Deploy API, Services (same app), WebJob that runs
TriggerScheduleEmail/EventEmailReminderSchedulerService, and admin static assets (build Grunt bundles inRaklet.Backend, locales,Cdn-Version). - Cosmos: same container as other docs; new
DocTypeeventreminderinstance. No new container. - Legacy
eventremindersentdocs are still merged on full event rebuild for historical sends; new sends are claimed on instance rows.
Automated tests¶
Raklet.UnitTests:EventEmailReminderScheduleMathTests,EventEmailReminderValidationTests(including 5-minute offset cases).Raklet.Backend:npm run test:js(includesevent-email-reminder-helpers.spec.js).