Skip to content

Email Sending Workflow

The primary responsibility of the Notifications module is to dispatch emails asynchronously without blocking the core business logic of other modules.

How It Works

  1. Integration Event Emitted: A module (e.g., identity) emits an integration event like UserRegisteredIntegrationEvent.
  2. Consumer Reacts: The corresponding consumer (e.g., UserRegisteredConsumer) in the Notifications module catches the event.
  3. Command Dispatched: The consumer extracts the relevant data and dispatches a SendEmailNotificationCommand.
  4. Template Resolution: The SendEmailNotificationHandler looks up the requested template from the TemplateRegistry.
  5. Rendering: The template renders the HTML content using the provided properties (props).
  6. Dispatch: The NodemailerEmailAdapter sends the email via the configured SMTP server.
  7. App Log Emission: The command handler emits structured AppLoggerService events for missing templates, render failures, SMTP delivery failures, and notification-log persistence failures.
  8. Logging: For render/send outcomes, a NotificationLog is created and saved to the database to track the attempt. Missing-template failures return early after emitting notifications.email.template_missing, so no notification row is created in that specific path.

Current Email Templates

  • user-registration-verification
  • client-registration-verification
  • new-device-login
  • password-changed

In non-production environments, the SMTP adapter prefixes the outgoing subject with [DEVELOPMENT] or [TEST] so test deliveries are obvious in the recipient inbox.

Adding a New Email Template

To add a new email template to the system:

  1. Create a new .tsx file in apps/api/src/modules/notifications/infrastructure/email/templates/.
  2. Implement the template logic using react-email components.
  3. Register the template in apps/api/src/modules/notifications/infrastructure/adapters/template-registry.adapter.ts.
  4. Create or update a consumer to dispatch the SendEmailNotificationCommand using the new template key.

Observability

When email delivery enters an error path, the Notifications module now emits structured OTEL log events in addition to the database audit row:

  • notifications.email.template_missing: emitted when a consumer requests a template key that is not registered.
  • notifications.email.render_failed: emitted when subject generation or HTML rendering throws before the provider call is attempted.
  • notifications.email.delivery_failed: emitted when SMTP delivery fails.
  • notifications.email.log_persist_failed: emitted when the NotificationLog row itself cannot be persisted.

These events include the notification context (templateKey, recipient, channel, and any available userId, clientId, or agencyId). When the command runs inside an integration-event consumer span, the emitted log also carries the active trace.id and span.id, so the failure can be correlated in Grafana Loki and Tempo.

Error Handling

If the email fails to send (for example, the template subject/render step throws or the SMTP provider is down), the SendEmailNotificationHandler catches the error, emits a structured app log, and still writes a failed NotificationLog row whenever persistence remains available. The error does not crash the application and the command still resolves successfully so upstream identity flows remain non-blocking.