feat(api): status-driven registration emails (receipt + waiting list)#1656
Conversation
There was a problem hiding this comment.
Pull request overview
Implements status-driven registration emails in the API so that participants receive the correct message based on the registration’s actual status (e.g., WaitingList vs Verified), addressing the bug where admin waitlisting still triggered a “you’re registered” confirmation.
Changes:
- Introduces
RegistrationNotificationService+RegistrationEmailRendererto render and send receipt (Verified) and waiting-list (WaitingList) emails using the existing DocComposer (Fluid/Liquid) pipeline. - Wires notifications into
CreateRegistrationAsync(create) andUpdateRegistrationAsync(status transitions), and removes the old welcome-letter auto-send path andRegistrationOptions.SendWelcomeLetter. - Adds embedded Liquid templates (nb/en), expands DocComposer model-type registration for nested models, and adds targeted unit tests.
Reviewed changes
Copilot reviewed 19 out of 19 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| docs/spec/registration-notifications.md | Design/spec documenting the new status-driven notification behavior and rationale. |
| apps/api/tests/Eventuras.Services.Tests/Registrations/RegistrationNotificationServiceTest.cs | Tests for transition-to-email mapping and idempotency behavior. |
| apps/api/tests/Eventuras.Services.Tests/Registrations/RegistrationManagementServiceTests.cs | Updates tests for removed welcome-letter option and adds waiting-list defaulting test. |
| apps/api/tests/Eventuras.Services.Tests/Registrations/RegistrationEmailRendererTest.cs | Tests template rendering (receipt/waitlist), nested line rendering, and locale fallback. |
| apps/api/src/Eventuras.WebApi/Controllers/v3/Registrations/RegistrationsController.cs | Logs deprecated sendWelcomeLetter=false usage and stops passing it into service options. |
| apps/api/src/Eventuras.WebApi/Controllers/v3/Registrations/NewRegistrationDto.cs | Deprecates SendWelcomeLetter field as ignored for backwards compatibility. |
| apps/api/src/Eventuras.Services/Registrations/Templates/registration-waitlist.nb.liquid | New Bokmål waiting-list HTML email template. |
| apps/api/src/Eventuras.Services/Registrations/Templates/registration-waitlist.en.liquid | New English waiting-list HTML email template. |
| apps/api/src/Eventuras.Services/Registrations/Templates/registration-receipt.nb.liquid | New Bokmål receipt HTML email template (includes orders). |
| apps/api/src/Eventuras.Services/Registrations/Templates/registration-receipt.en.liquid | New English receipt HTML email template (includes orders). |
| apps/api/src/Eventuras.Services/Registrations/RegistrationServiceCollectionExtensions.cs | Registers renderer + notification service in DI. |
| apps/api/src/Eventuras.Services/Registrations/RegistrationOptions.cs | Removes SendWelcomeLetter option from internal service options. |
| apps/api/src/Eventuras.Services/Registrations/RegistrationManagementService.cs | Replaces welcome-letter send with status-driven notifications on create/update. |
| apps/api/src/Eventuras.Services/Registrations/Notifications/RegistrationNotificationService.cs | New status-transition email sender assembling receipt/waitlist models and sending via IEmailSender. |
| apps/api/src/Eventuras.Services/Registrations/Notifications/RegistrationEmailRenderer.cs | New renderer using DocComposer to produce subject + HTML/text body from embedded templates. |
| apps/api/src/Eventuras.Services/Registrations/Notifications/RegistrationEmailModels.cs | Email model records used as template input (including nested order lines). |
| apps/api/src/Eventuras.Services/Eventuras.Services.csproj | Embeds the Liquid templates and adds DocComposer project reference. |
| apps/api/src/Eventuras.Libs.DocComposer/FluidDocumentComposer.cs | Registers nested model types recursively to allow template access to nested structures. |
| .changeset/registration-status-emails.md | Changeset documenting the API behavior change and deprecation. |
| private static string NormalizeLocale(string? locale) | ||
| { | ||
| var lang = (locale ?? DefaultLocale).Split('-')[0].ToLowerInvariant(); | ||
| return lang switch | ||
| { | ||
| "nb" or "no" => "nb", | ||
| "nn" => "nn", | ||
| "en" => "en", | ||
| _ => DefaultLocale | ||
| }; | ||
| } |
There was a problem hiding this comment.
Good call — fixed. NormalizeLocale now passes unknown languages through (only mapping no→nb), so the composer tries that locale's template and falls back to nb if missing. Matches the certificate renderer and lets future *.sv.liquid etc. be picked up.
| services.AddTransient<IRegistrationAccessControlService, RegistrationAccessControlService>(); | ||
| services.AddTransient<IRegistrationManagementService, RegistrationManagementService>(); | ||
|
|
||
| // Email renderer is stateless and caches parsed templates -> singleton. |
There was a problem hiding this comment.
Correct — FluidDocumentComposer re-parses per call; it only caches registered model types. Fixed the comment (singleton still reuses the composer + its registered-type cache). A real parsed-template cache can come later if it matters.
6f684ae to
4259874
Compare
Replace the single "welcome letter" with an email chosen by the registration's status, rendered from Liquid templates via the existing DocComposer engine (HTML, no PDF): - Verified -> receipt with a copy of the registration and its orders - WaitingList -> "you're on the waiting list" email - other statuses -> no email Sent on status transitions (create + status change) and idempotent (only on an actual change), so putting a participant on the waiting list sends the waiting-list email instead of a confirmation (fixes the reported bug). - DocComposer: register nested model types recursively so templates can render structured data (order lines). - RegistrationEmailRenderer + models + nb/en templates (fallback to nb). - RegistrationNotificationService (status -> email) + DI. - Remove the internal SendWelcomeLetter option and the EventInfo.WelcomeLetter auto-send. NewRegistrationDto.SendWelcomeLetter is kept as an [Obsolete] no-op (logs a warning when a client still sends it); to be removed in the next API version. - Tests: renderer, notification service (incl. waiting-list regression) and admin-add on a waiting-list event defaults to WaitingList. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
4259874 to
eb5f6c2
Compare
|
| - Templates as embedded resources per locale (`nb`, `nn`, `en`): | ||
| - `registration-receipt.<locale>.liquid` | ||
| - `registration-waitlist.<locale>.liquid` |



Why
Reported bug: an admin putting a participant on the waiting list still sent a "you're registered" confirmation. Root cause — the email was a single per-event "welcome letter" fired on create, gated only by the event's status, not the registration's actual status.
What
Registration emails are now status-driven — the email is chosen by the registration's status and sent on status transitions (create + status change), idempotent (only on an actual change):
VerifiedWaitingListSo placing someone on the waiting list now sends the waiting-list email, not a confirmation.
Rendered with the existing DocComposer (Fluid/Liquid) engine used by certificate emails — HTML only, no PDF. Templates are neutral in tone ("Påmelding", not over-confirming), omit the "Eventuras" signature, and mention that you can log in to see up-to-date info (no links).
Changes
RegistrationEmailRenderer+ models + embedded.liquidtemplates (nb/en, fallback to nb):registration-receipt,registration-waitlist.RegistrationNotificationService(status → email) + DI; wired intoCreateRegistrationAsyncandUpdateRegistrationAsync(on status change).SendWelcomeLetteroption and theEventInfo.WelcomeLetterauto-send.NewRegistrationDto.SendWelcomeLetteris kept as an[Obsolete]no-op (logs a warning if a client still sendsfalse); to be removed in the next API version.WaitingList(so a single waiting-list email; no double send).Tests
dotnet buildclean, 67/67 inEventuras.Services.Tests:RegistrationEmailRendererTest— receipt renders subject/body/nested order lines, waiting-list message, locale fallback.RegistrationNotificationServiceTest— WaitingList → waiting-list email (not confirmation), Verified → receipt, no-op on unchanged/non-notifiable status.CreateRegistrationAsync_SetsWaitingList_WhenEventIsWaitingList.Deferred
The one remaining double-email case: an event not on the waiting list where an admin manually waitlists a single participant via the status dropdown (receipt at create + waiting-list at patch). The clean fix is event-driven notifications / an atomic "add to waiting list" — intentionally out of scope here (see
docs/spec/registration-notifications.md).🤖 Generated with Claude Code