[Tech debt] Migrate remaining IEmailService callers to SendEmailCommand (follow-up to #225) #226

Closed
opened 2026-06-15 22:56:45 +00:00 by spikerj · 1 comment
Owner

Background

#225 fixed the registration verification email by having InitiatePreRegistrationCommandHandler publish SendEmailCommand : IRemoteCommand to the Notifications worker instead of resolving the API-edge NoOpEmailService (which drops outbound SMTP). The other in-process IEmailService callers listed in the NoOpEmailService XML doc still hit the stub, so their emails are silently dropped in Production too.

Remaining callers to migrate

Caller Email method Migration
RegisterUserCommandHandler SendUserConfirmationEmailAsync new SendUserConfirmationEmailCommand (template stays worker-side)
CreateOrganizationCommandHandler SendOrganizationConfirmationEmailAsync new SendOrganizationConfirmationEmailCommand (template stays worker-side)
ResendPreRegistrationCodesCommandHandler SendEmailAsync (body built in caller) reuse SendEmailCommand
ParentalEmailService (behind IParentalEmailService) SendEmailAsync (body built in caller) reuse SendEmailCommand

Plan

  1. Add SendUserConfirmationEmailCommand and SendOrganizationConfirmationEmailCommand (IRemoteCommand, notifications exchange) to SpikerSoft.Contracts.Workers. These carry only data (recipient, name, confirmation URL); the HTML templates remain in the worker's EmailService (no template duplication on the API edge).
  2. Add matching worker handlers in SpikerSoft.Workers.Notifications that call the existing EmailService.SendUserConfirmationEmailAsync / SendOrganizationConfirmationEmailAsync.
  3. Register both fire-and-forget routes in NotificationsRpcConsumerHostedService.
  4. Refactor the four callers to publish the appropriate command via MediatR and drop their direct IEmailService dependency.
  5. Update affected unit tests.

Acceptance criteria

  • User email confirmation, organization confirmation, pre-registration code resend, and parental approval emails are all delivered by the Notifications worker.
  • No NoOpEmailService "Skipping outbound SMTP" warnings remain on these paths.
  • Existing tests pass; new coverage for the added worker handlers.

Follow-up to #225.

## Background #225 fixed the registration verification email by having `InitiatePreRegistrationCommandHandler` publish `SendEmailCommand : IRemoteCommand` to the Notifications worker instead of resolving the API-edge `NoOpEmailService` (which drops outbound SMTP). The other in-process `IEmailService` callers listed in the `NoOpEmailService` XML doc still hit the stub, so their emails are silently dropped in Production too. ## Remaining callers to migrate | Caller | Email method | Migration | |---|---|---| | `RegisterUserCommandHandler` | `SendUserConfirmationEmailAsync` | new `SendUserConfirmationEmailCommand` (template stays worker-side) | | `CreateOrganizationCommandHandler` | `SendOrganizationConfirmationEmailAsync` | new `SendOrganizationConfirmationEmailCommand` (template stays worker-side) | | `ResendPreRegistrationCodesCommandHandler` | `SendEmailAsync` (body built in caller) | reuse `SendEmailCommand` | | `ParentalEmailService` (behind `IParentalEmailService`) | `SendEmailAsync` (body built in caller) | reuse `SendEmailCommand` | ## Plan 1. Add `SendUserConfirmationEmailCommand` and `SendOrganizationConfirmationEmailCommand` (`IRemoteCommand`, `notifications` exchange) to `SpikerSoft.Contracts.Workers`. These carry only data (recipient, name, confirmation URL); the HTML templates remain in the worker's `EmailService` (no template duplication on the API edge). 2. Add matching worker handlers in `SpikerSoft.Workers.Notifications` that call the existing `EmailService.SendUserConfirmationEmailAsync` / `SendOrganizationConfirmationEmailAsync`. 3. Register both fire-and-forget routes in `NotificationsRpcConsumerHostedService`. 4. Refactor the four callers to publish the appropriate command via MediatR and drop their direct `IEmailService` dependency. 5. Update affected unit tests. ## Acceptance criteria - [ ] User email confirmation, organization confirmation, pre-registration code resend, and parental approval emails are all delivered by the Notifications worker. - [ ] No `NoOpEmailService` "Skipping outbound SMTP" warnings remain on these paths. - [ ] Existing tests pass; new coverage for the added worker handlers. Follow-up to #225.
Author
Owner

Resolved in spikersoft-backend PR #9 (merged to master at 2026-06-15T23:15:02Z).

The remaining in-process IEmailService callers that still hit the NoOpEmailService stub now publish remote commands handled by SpikerSoft.EventHandlers.Notifications:

  • RegisterUserCommandHandler -> new SendUserConfirmationEmailCommand
  • CreateOrganizationCommandHandler -> new SendOrganizationConfirmationEmailCommand
  • ResendPreRegistrationCodesCommandHandler -> SendEmailCommand
  • ParentalEmailService -> SendEmailCommand

The two new confirmation commands carry only template data; the HTML templates stay in the worker's EmailService (no MailKit/markup on the API edge). NoOpEmailService remains registered defensively as a DI fallback that warns if any future caller bypasses the worker. New worker-handler unit tests added; existing tests updated.

Closing.

Resolved in spikersoft-backend PR #9 (merged to `master` at 2026-06-15T23:15:02Z). The remaining in-process `IEmailService` callers that still hit the `NoOpEmailService` stub now publish remote commands handled by `SpikerSoft.EventHandlers.Notifications`: - `RegisterUserCommandHandler` -> new `SendUserConfirmationEmailCommand` - `CreateOrganizationCommandHandler` -> new `SendOrganizationConfirmationEmailCommand` - `ResendPreRegistrationCodesCommandHandler` -> `SendEmailCommand` - `ParentalEmailService` -> `SendEmailCommand` The two new confirmation commands carry only template data; the HTML templates stay in the worker's `EmailService` (no MailKit/markup on the API edge). `NoOpEmailService` remains registered defensively as a DI fallback that warns if any future caller bypasses the worker. New worker-handler unit tests added; existing tests updated. Closing.
Sign in to join this conversation.