Skip to content

Plan: Event email reminders + default template (consolidated)

Branch: feature/event-email-reminders (or successor)
Last updated: 2026-04-04
Purpose: Single place to resume work after this chat. Implements automated event email reminders, a per-org default template, backfill for existing accounts, and correct visibility (Templates list vs compose).


Product decisions (locked)

  1. Reminders are email-only in v1 (no SMS/push in this slice).
  2. Up to 5 rows per event: template + offset before start (stored as minutes), unique offsets, server-validated (V2EventsController).
  3. Data lives on Cosmos EventDoc as EmailReminders (EventEmailReminderRow: TemplateId, OffsetMinutesBeforeStart).
  4. Recurring series: reminder UI and persistence were intentionally not wired to recurring save paths in v1 (strip emailReminders on saveRecurringEvent). Revisit in a follow-up if product wants it.
  5. Default “Event reminder” template: materialized as a real Email (TemplateV2) per organisation, with Cosmos key for lookup. No virtual-only row (matches Event Registration / receipt pattern).
  6. Visibility:
  7. Must appear on Messages → Templates (same table as other TemplateV2 rows; loaded with includeSystemTemplates: true).
  8. Must not appear in regular message compose template pickers that use getTemplateEmails(..., includeSystemTemplates: false) / “without system” list.
  9. Must appear in Event create/edit → Email reminders template dropdown.

Already implemented (verify on branch)

Area What
Models EventEmailReminderRow, EventDoc.EmailReminders, EventDetailsEditViewModel.EmailReminders
API V2EventsController: validate/normalize on create/update, clone copies reminders
Manager UI event-upsert + template: rows, amount/unit, EmailService templates, refetch when returning from manager.messages.*, hidden for recurring v1
Admin Cdn-Version bump after Grunt (Raklet.Admin/Web.config)
Cursor Optional rule: branch from master before plan-eng-review (committed separately)
Cosmos + WebJob EventReminderInstanceDoc materialized rows; sync on event + ticket payment save; scheduler consumes pending instances in schedule window (EventEmailReminderSchedulerService)
Manager API + UI GET .../email-reminder-instances; event details Reminders tab Per registrant table
Git Raklet.Backend/Content/dist/ gitignored; Grunt output not committed

Follow-up after any JS/HTML change: npx grunt ngtemplates concat:development in Raklet.Backend, bump Cdn-Version, recycle admin app pool.


Remaining work (ordered)

1. Default Event Reminder template content

  • Add subject string (Resources / EmailMessages pattern used elsewhere).
  • Add HtmlCreationService method for body: event merge tokens already used for receipts (GetEmailHtmlPartForEventReceipt area) such as |*EVENTNAME*|, |*EVENT_START_DATE_TIME*|, etc. Goal: copy suitable for “reminder before event”, distinct from registration/receipt if needed.

2. Cosmos key + EmailTemplateKeys

  • Add constant, e.g. Event-EventEmailReminderTemplateIdEmailTemplateKeys.EventEmailReminder in Models/Dtos/Emails/Enums.cs.

3. New org provisioning

  • NewOrganisationGenericService.AddDefaultEmailTemplates: after other defaults, AddDefaultEmailTemplate(...), then SetOption with new key (mirror event receipt block).
  • AddDefaultEmailTemplatesForOldOrganizations: if key missing or empty, create template + set key (same pattern as Event-EventRegistrationTemplateId).

4. Exclude from regular compose, keep on Templates list + event picker

Mechanism today: V2EmailsController.GetTemplates with includeSystemTemplates == false removes rows whose ids appear in the org-settings exclusion list (see Event-EventRegistrationTemplateId, invoice reminders, incomplete application, etc.).

Action:

  • Add Event-EventEmailReminderTemplateId to that same exclusion list (both UseNewEmailModule and legacy _ suffix branches) so the template does not show in generic “without system” compose flows.

Conflict: event-upsert currently calls EmailService.getTemplateEmails("", 0, 100, **false**) for reminder rows. Once the new template id is on the exclusion list, it will be stripped from that response.

Pick one approach (implement one):

Option Description
A (recommended) Add API query param on GetTemplates, e.g. forEventReminders=true, that returns the same base query as includeSystemTemplates: false but merges back in the template id from Event-EventEmailReminderTemplateId if set. Update EmailService.getTemplateEmails or add a small dedicated method used only from event-upsert.
B Event page uses includeSystemTemplates: true and filters client-side to TemplateV2 only, excluding known system ids except event-reminder + user templates (fragile if ids change).
C Dedicated endpoint GET .../templates/for-event-reminders returning allowed ids only.

Templates list keeps using includeSystemTemplates: true (see messages-templates.controller.js) so the row still shows there.

5. WebJob: populate existing accounts

Pattern: Same family as AddDefaultEmailTemplatesForOldOrganizations and create-default-email-templates-v2.

Concrete steps:

  1. Queue type: Add e.g. add-event-email-reminder-template (or extend existing add-default-email-templates-for-old-organizations-part with a new message type) in Services/Backbone/QueueType.cs if you need a dedicated fan-out.
  2. Worker: Either:
  3. Light: Reuse EmailTemplatesFunctions.AddDefaultEmailTemplatesForOldOrganizationsPart style: message per org id, call into NewOrganisationGenericService extension that only creates missing Event-EventEmailReminderTemplateId, or
  4. Heavy: One-time admin enqueue that lists org ids (batched) and queues messages (mirror existing “old organizations” migration jobs in Raklet.WebJobs.Secondary / Raklet.WebJobs).
  5. CreateDefaultEmailTemplatesV2 / CreateTemplateV2: Add case for "Event-EventEmailReminderTemplateId" next to "Event-EventRegistrationTemplateId" in Raklet.WebJobs/Functions.cs (and any CreateTemplateIfMissing switch that lists event keys) so new pipelines get the template when the v2 job runs.
  6. Idempotency: Creation path must no-op if Cosmos key already set and email row exists (same as other defaults).

6. Email settings / automated UI (optional but nice)

  • If other event templates appear in Settings → Email or automated.html, add a row for Event Reminder default with preview/edit links using the Cosmos key (grep Event-EventRegistrationTemplateId in API + Angular).

7. Scheduler / send path (future slice)

  • WebJob or timer: read EventDoc with EmailReminders, compute send time from StartDateTime − offset, enqueue send using existing email pipeline and template id. Not required to close “template + backfill + UI” slice.

8. Tests + QA

  • Unit tests for validation helper / controller if you extract it.
  • Manual QA (see also docs/events/event-email-reminders-handoff.md):
  • Templates: default row visible; subject/body OK after deploy (V2 needs blob MJML; old orgs may need backfill/repair).
  • Compose: newsletter / “without system” does not list event-reminder template; event edit picker does.
  • Event: non-recurring; distinct offsets only (duplicate offset → validation error by design); save, reload, clone copies reminders.
  • Event start / timezone updates: Reminder send instant is recomputed from current StartDateTime each tick (start − offset). QA postpone vs move-earlier vs already-sent + idempotency (see event-email-reminders-handoff.md). Recurring: per-occurrence EventDoc — date edits with scope update the right occurrences.
  • Scheduler: fires in window; completed ticket payments only; Cosmos blocks duplicate sends.
  • Recycle app pool after admin deploy if UI looks stale.

Files to touch (checklist)

File / area Why
Models/Dtos/Emails/Enums.cs New EmailTemplateKeys constant
Resources / EmailMessages Subject + strings
Services/HtmlCreationService.cs (or equivalent) Default HTML body
Services/NewOrganisationGenericService.cs New + old org provisioning
Raklet.Api/Controllers/V2/V2EmailsController.cs Exclusion list + optional forEventReminders (or chosen option)
Raklet.Backend/.../email.service.js + event-upsert.controller.js Load templates for reminders per API change
Raklet.WebJobs/Functions.cs CreateTemplateV2 for new key
Raklet.WebJobs.Secondary/Email/EmailTemplatesFunctions.cs (or similar) Backfill job
Services/Backbone/QueueType.cs New queue if needed
Raklet.Api/.../V2EmailsController email settings DTOs If exposing in settings UI

PR description snippet

Event email reminders (Cosmos + V2 API + manager event form). Default Event Reminder template per org with Cosmos key; excluded from compose “without system” list; visible on Templates and event reminder picker via [API approach chosen]. WebJob backfill for existing orgs. Recurring v1: reminders UI/save not applied to recurring endpoints.


When you resume

  1. git fetch and branch from current master if this branch is stale.
  2. Implement sections 1–5 in order (content → keys → provisioning → API visibility → WebJob).
  3. Run build-fast.ps1 for touched projects; Grunt if JS changes.
  4. Bump Cdn-Version and recycle IIS for admin.

Open questions (resolve when implementing)

  • Exact subject line and whether body is shared heavily with receipt or separate.
  • Whether delete of the default template should be blocked (many orgs block deleting “system” templates; grep You cannot delete default templates).