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)¶
- Reminders are email-only in v1 (no SMS/push in this slice).
- Up to 5 rows per event: template + offset before start (stored as minutes), unique offsets, server-validated (
V2EventsController). - Data lives on Cosmos
EventDocasEmailReminders(EventEmailReminderRow:TemplateId,OffsetMinutesBeforeStart). - Recurring series: reminder UI and persistence were intentionally not wired to recurring save paths in v1 (strip
emailRemindersonsaveRecurringEvent). Revisit in a follow-up if product wants it. - 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). - Visibility:
- Must appear on Messages → Templates (same table as other TemplateV2 rows; loaded with
includeSystemTemplates: true). - Must not appear in regular message compose template pickers that use
getTemplateEmails(..., includeSystemTemplates: false)/ “without system” list. - 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 /
EmailMessagespattern used elsewhere). - Add
HtmlCreationServicemethod for body: event merge tokens already used for receipts (GetEmailHtmlPartForEventReceiptarea) 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-EventEmailReminderTemplateId→EmailTemplateKeys.EventEmailReminderinModels/Dtos/Emails/Enums.cs.
3. New org provisioning¶
NewOrganisationGenericService.AddDefaultEmailTemplates: after other defaults,AddDefaultEmailTemplate(...), thenSetOptionwith new key (mirror event receipt block).AddDefaultEmailTemplatesForOldOrganizations: if key missing or empty, create template + set key (same pattern asEvent-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-EventEmailReminderTemplateIdto that same exclusion list (bothUseNewEmailModuleand 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:
- Queue type: Add e.g.
add-event-email-reminder-template(or extend existingadd-default-email-templates-for-old-organizations-partwith a new message type) inServices/Backbone/QueueType.csif you need a dedicated fan-out. - Worker: Either:
- Light: Reuse
EmailTemplatesFunctions.AddDefaultEmailTemplatesForOldOrganizationsPartstyle: message per org id, call intoNewOrganisationGenericServiceextension that only creates missingEvent-EventEmailReminderTemplateId, or - 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). CreateDefaultEmailTemplatesV2/CreateTemplateV2: Add case for"Event-EventEmailReminderTemplateId"next to"Event-EventRegistrationTemplateId"inRaklet.WebJobs/Functions.cs(and anyCreateTemplateIfMissingswitch that lists event keys) so new pipelines get the template when the v2 job runs.- 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-EventRegistrationTemplateIdin API + Angular).
7. Scheduler / send path (future slice)¶
- WebJob or timer: read
EventDocwithEmailReminders, compute send time fromStartDateTime− 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
StartDateTimeeach tick (start − offset). QA postpone vs move-earlier vs already-sent + idempotency (seeevent-email-reminders-handoff.md). Recurring: per-occurrenceEventDoc— 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¶
git fetchand branch from currentmasterif this branch is stale.- Implement sections 1–5 in order (content → keys → provisioning → API visibility → WebJob).
- Run
build-fast.ps1for touched projects; Grunt if JS changes. - Bump
Cdn-Versionand 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).