[Bug] Registration verification email never delivered — notifications email worker is not built/deployed (+ Email/Smtp config-key mismatch) #258

Closed
opened 2026-06-25 16:48:45 +00:00 by spikerj · 1 comment
Owner

Severity

High — no new user can complete signup: the frontend reports the verification email was sent, but it never arrives.

Area

Registration email verification ("Verificar" stage) — backend email delivery pipeline.

Symptom

During the email-confirmation stage the frontend shows "email sent", but the email never arrives. A manual/test message from SpikerSoft does go through (so the SMTP server itself is up).

Why the frontend says "sent" but nothing arrives

After #225/#226/#227, all email sending was moved out of the API into the notifications worker and is dispatched fire-and-forget over RabbitMQ:

  1. POST /api/authentication/initiate-pre-registrationInitiatePreRegistrationCommandHandler.SendEmailVerification(...) publishes a SendEmailCommand (IRemoteCommand, exchange notifications, routing key notifications.email.send.requested).
    • SpikerSoft.Business/Domain/Authentication/Commands/InitiatePreRegistration/InitiatePreRegistrationCommandHandler.cs (~L354-368)
  2. RemoteDispatchBehavior publishes to RabbitMQ and immediately returns JobAccepted — it does not wait for SMTP delivery.
    • SpikerSoft.Business.Edge/Behaviors/RemoteDispatchBehavior.cs (~L51-67)
  3. The API returns success; the frontend optimistically shows the success banner (it never inspects an emailSent flag).
    • registration-stepper.component.ts handleRegistrationSuccess(...) (~L743-759)

So "sent" only means "published to the queue", not "delivered".

Root cause #1 (primary): the notifications email worker is not built or deployed

SpikerSoft.EventHandlers.Notifications is the only consumer of SendEmailCommand (NotificationsRpcConsumerHostedServiceSendEmailCommandHandlerEmailService MailKit SMTP send). But it is missing from every deployment layer:

  • No Dockerfile — every other event handler has one (SpikerSoft.EventHandlers.*/Dockerfile); SpikerSoft.EventHandlers.Notifications does not.
  • No CI workflow — there is no .gitea/workflows/spikersoft-notifications.yml (every other worker has one).
  • No stack servicedeploy/docker-stack.yml has no notifications service (it deploys the API + ~16 workers, but not this one).

Result: SendEmailCommand messages are published to RabbitMQ with no consumer running, so registration verification emails are never sent (they sit on the queue / eventually dead-letter). This is a regression from the #225/#226/#227 refactor — email-sending was moved into a worker that was never containerized or deployed.

The API's SMTP health check is TCP-only (SpikerSoft.Api/Infrastructure/HealthChecks/SMTP_HealthCheck.cs), so it (and a manual/test send) can pass while the actual app pipeline is dead.

Root cause #2 (latent, will bite once deployed): Email/Smtp config-key mismatch

EmailService reads Email:Host / Email:Port / Email:Username / Email:Password / Email:SkipSslValidation / Email:TimeoutSeconds (and throws ArgumentException if Host/Username/Password are missing):

  • SpikerSoft.Workers.Notifications/Services/EmailService.cs (~L16-21)

But the notifications host config defines an Smtp block instead of Email:

  • SpikerSoft.EventHandlers.Notifications/appsettings.json (~L41-49: "Smtp": { ... })

The canonical key elsewhere is Email (SpikerSoft.Api/appsettings.json"Email": { ... }). So even once the worker is deployed, unless Email__* env vars are supplied it will throw on construction and send nothing.

Suggested fix

  1. Deploy the worker: add SpikerSoft.EventHandlers.Notifications/Dockerfile (mirror CalendarReminders, EXPOSE 8085 per WithKestrel(8085)), a .gitea/workflows/spikersoft-notifications.yml, and a notifications service in deploy/docker-stack.yml (networks: seq-attachable, jaeger, rabbitmq, mongo, mailserver) plus the server-side per-service stack used by the deploy step.
  2. Fix config: rename the host's Smtp block to Email (Host/Port/Username/Password/...) so it matches EmailService, and wire Email__Host/Username/Password as production secrets/env for the worker.
  3. (Optional hardening) Have the frontend/initiate response distinguish "queued" from "delivered", and add a delivery alert/dead-letter alarm so a missing consumer is caught.

Security note (separate)

SpikerSoft.Api/appsettings.json (~L383-389) contains a plaintext SMTP password committed to the repo. It should be rotated and moved to a secret. (Worth its own ticket.)

Pointers

  • SpikerSoft.EventHandlers.Notifications/ (Program.cs, appsettings.json, missing Dockerfile)
  • SpikerSoft.Workers.Notifications/Services/EmailService.cs
  • SpikerSoft.EventHandlers.Notifications/Services/NotificationsRpcConsumerHostedService.cs
  • SpikerSoft.Business/Domain/Authentication/Commands/InitiatePreRegistration/InitiatePreRegistrationCommandHandler.cs
  • deploy/docker-stack.yml, .gitea/workflows/
## Severity High — no new user can complete signup: the frontend reports the verification email was sent, but it never arrives. ## Area Registration email verification ("Verificar" stage) — backend email delivery pipeline. ## Symptom During the email-confirmation stage the frontend shows "email sent", but the email never arrives. A manual/test message from SpikerSoft does go through (so the SMTP server itself is up). ## Why the frontend says "sent" but nothing arrives After #225/#226/#227, all email sending was moved out of the API into the notifications worker and is dispatched **fire-and-forget over RabbitMQ**: 1. `POST /api/authentication/initiate-pre-registration` → `InitiatePreRegistrationCommandHandler.SendEmailVerification(...)` publishes a `SendEmailCommand` (`IRemoteCommand`, exchange `notifications`, routing key `notifications.email.send.requested`). - `SpikerSoft.Business/Domain/Authentication/Commands/InitiatePreRegistration/InitiatePreRegistrationCommandHandler.cs` (~L354-368) 2. `RemoteDispatchBehavior` publishes to RabbitMQ and immediately returns `JobAccepted` — it does **not** wait for SMTP delivery. - `SpikerSoft.Business.Edge/Behaviors/RemoteDispatchBehavior.cs` (~L51-67) 3. The API returns success; the frontend optimistically shows the success banner (it never inspects an `emailSent` flag). - `registration-stepper.component.ts` `handleRegistrationSuccess(...)` (~L743-759) So "sent" only means "published to the queue", not "delivered". ## Root cause #1 (primary): the notifications email worker is not built or deployed `SpikerSoft.EventHandlers.Notifications` is the **only** consumer of `SendEmailCommand` (`NotificationsRpcConsumerHostedService` → `SendEmailCommandHandler` → `EmailService` MailKit SMTP send). But it is missing from every deployment layer: - **No Dockerfile** — every other event handler has one (`SpikerSoft.EventHandlers.*/Dockerfile`); `SpikerSoft.EventHandlers.Notifications` does not. - **No CI workflow** — there is no `.gitea/workflows/spikersoft-notifications.yml` (every other worker has one). - **No stack service** — `deploy/docker-stack.yml` has no `notifications` service (it deploys the API + ~16 workers, but not this one). Result: `SendEmailCommand` messages are published to RabbitMQ with **no consumer running**, so registration verification emails are never sent (they sit on the queue / eventually dead-letter). This is a regression from the #225/#226/#227 refactor — email-sending was moved into a worker that was never containerized or deployed. The API's SMTP health check is TCP-only (`SpikerSoft.Api/Infrastructure/HealthChecks/SMTP_HealthCheck.cs`), so it (and a manual/test send) can pass while the actual app pipeline is dead. ## Root cause #2 (latent, will bite once deployed): Email/Smtp config-key mismatch `EmailService` reads `Email:Host` / `Email:Port` / `Email:Username` / `Email:Password` / `Email:SkipSslValidation` / `Email:TimeoutSeconds` (and throws `ArgumentException` if Host/Username/Password are missing): - `SpikerSoft.Workers.Notifications/Services/EmailService.cs` (~L16-21) But the notifications host config defines an `Smtp` block instead of `Email`: - `SpikerSoft.EventHandlers.Notifications/appsettings.json` (~L41-49: `"Smtp": { ... }`) The canonical key elsewhere is `Email` (`SpikerSoft.Api/appsettings.json` → `"Email": { ... }`). So even once the worker is deployed, unless `Email__*` env vars are supplied it will throw on construction and send nothing. ## Suggested fix 1. Deploy the worker: add `SpikerSoft.EventHandlers.Notifications/Dockerfile` (mirror `CalendarReminders`, EXPOSE 8085 per `WithKestrel(8085)`), a `.gitea/workflows/spikersoft-notifications.yml`, and a `notifications` service in `deploy/docker-stack.yml` (networks: `seq-attachable`, `jaeger`, `rabbitmq`, `mongo`, `mailserver`) plus the server-side per-service stack used by the deploy step. 2. Fix config: rename the host's `Smtp` block to `Email` (Host/Port/Username/Password/...) so it matches `EmailService`, and wire `Email__Host/Username/Password` as production secrets/env for the worker. 3. (Optional hardening) Have the frontend/initiate response distinguish "queued" from "delivered", and add a delivery alert/dead-letter alarm so a missing consumer is caught. ## Security note (separate) `SpikerSoft.Api/appsettings.json` (~L383-389) contains a **plaintext SMTP password** committed to the repo. It should be rotated and moved to a secret. (Worth its own ticket.) ## Pointers - `SpikerSoft.EventHandlers.Notifications/` (Program.cs, appsettings.json, **missing** Dockerfile) - `SpikerSoft.Workers.Notifications/Services/EmailService.cs` - `SpikerSoft.EventHandlers.Notifications/Services/NotificationsRpcConsumerHostedService.cs` - `SpikerSoft.Business/Domain/Authentication/Commands/InitiatePreRegistration/InitiatePreRegistrationCommandHandler.cs` - `deploy/docker-stack.yml`, `.gitea/workflows/`
spikerj added the bug label 2026-06-25 16:48:45 +00:00
Author
Owner

Resolved in spikersoft-backend PR #18 (merged to master on 2026-06-25, merge commit 4903eb9).

Root cause: after #225/#226/#227 moved all email sending into the SpikerSoft.EventHandlers.Notifications worker (published fire-and-forget over RabbitMQ), that worker was never containerized or deployed, so SendEmailCommand messages had no consumer and registration verification emails were never delivered (while the API/frontend optimistically reported "sent").

Fix:

  • Added the worker's Dockerfile, a .gitea CI workflow, and a notifications service in deploy/docker-stack.yml.
  • Fixed the SmtpEmail config-key mismatch (and aligned the Twilio block) so EmailService binds its SMTP settings.
  • Standardized the Twilio Verify SID config key on Twilio:VerifyServiceSid (the registration SMS path had been reading an undefined Twilio:VerificationServiceSid).

The worker has since been deployed and is consuming notifications.email.send (and the SMS/verify/voice/confirmation queues). Closing.

Resolved in spikersoft-backend PR #18 (merged to `master` on 2026-06-25, merge commit `4903eb9`). Root cause: after #225/#226/#227 moved all email sending into the `SpikerSoft.EventHandlers.Notifications` worker (published fire-and-forget over RabbitMQ), that worker was never containerized or deployed, so `SendEmailCommand` messages had no consumer and registration verification emails were never delivered (while the API/frontend optimistically reported "sent"). Fix: - Added the worker's `Dockerfile`, a `.gitea` CI workflow, and a `notifications` service in `deploy/docker-stack.yml`. - Fixed the `Smtp` → `Email` config-key mismatch (and aligned the Twilio block) so `EmailService` binds its SMTP settings. - Standardized the Twilio Verify SID config key on `Twilio:VerifyServiceSid` (the registration SMS path had been reading an undefined `Twilio:VerificationServiceSid`). The worker has since been deployed and is consuming `notifications.email.send` (and the SMS/verify/voice/confirmation queues). Closing.
Sign in to join this conversation.