Skip to content

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 hooksCosmosDbEventService.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).

WebJobEventEmailReminderSchedulerService loads pending instances whose ScheduledSendEpochSeconds falls in the current UTC minute window, then TryClaimPendingToSentAsync, then sends via EventEmailService.

Manager API + UIGET .../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.

ToolingRaklet.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

  1. Build: .\scripts\dev\build-fast.ps1 Debug -Project Models, -Project Services, -Project Raklet.Api, -Project Raklet.UnitTests. Stop Raklet.WebJobs.Secondary if DLL copy is locked, then build WebJob project if needed.
  2. Event + reminders: Create/edit event with at least one valid reminder row; save (triggers rebuild).
  3. Minutes: Choose Minutes and pick a value from the dropdown; save — API should accept only 5-minute multiples (EventEmailReminderValidation).
  4. Registration: Complete an event ticket purchase; open event Details → RemindersPer registrant should list rows from Cosmos with correct scheduled time (event timezone in UI).
  5. Sent preserved: After a reminder has been sent (or a Sent instance exists), change event title/start and save — Sent instance rows should remain in Cosmos / per-registrant list; pending rows can still be rebuilt.
  6. Unpublish: Click Unpublish — confirm modal mentions pending reminders; confirm — event unpublishes; pending materialization should not apply while unpublished.
  7. API: GET v2/organisations/{orgId}/events/{eventId}/email-reminder-instances returns non-cancelled rows matching Cosmos.

Deploy notes

  • Deploy API, Services (same app), WebJob that runs TriggerScheduleEmail / EventEmailReminderSchedulerService, and admin static assets (build Grunt bundles in Raklet.Backend, locales, Cdn-Version).
  • Cosmos: same container as other docs; new DocType eventreminderinstance. No new container.
  • Legacy eventremindersent docs 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 (includes event-email-reminder-helpers.spec.js).