Files
hrynco-notification-service/docs/itemtracker-outbox-email-consumer.md
agrynco 1ceb71b9a2 fix: correct notification template filter links
Use explicit Razor interpolation for create and edit URLs and cover the rendered link syntax with a regression test.

Ref: IT-1039
2026-08-04 21:01:46 +03:00

7.7 KiB

ItemTracker Outbox Email Consumer

Contract and routing

The Worker consumes durable messages from queue notification.send-email. ItemTracker publishes through RabbitMQ's default exchange, so the queue name is also the routing key. No custom exchange binding is required.

HrynCo.RabbitMq owns the transport lifecycle: connection, queue declaration, deserialization, prefetch, structured delivery context, retry, terminal-failure notification, and manual ACK/NACK. Notification Service only supplies its queue, contract validation, email handler, and result publisher through the package's generic extension points. This integration requires package version 1.0.17 or newer.

Accepted messages must have all of the following:

  • AMQP Type: Notification.SendEmail.v1
  • AMQP MessageId: the non-empty stable ItemTracker Outbox ID
  • JSON content using the PascalCase SendEmailMessage envelope
  • non-empty CorrelationContext.CorrelationId
  • non-empty ServiceName, TemplateKey, RecipientEmail, RecipientName, and LanguageCode
  • a valid recipient address and a non-null Variables object

The service resolves a template by the exact ServiceName, TemplateKey, and normalized lower-case LanguageCode. It does not silently fall back to another language. Every variable marked Required by the selected template must have a non-empty value before rendering.

Delivery and acknowledgement policy

The consumer uses manual acknowledgements and prefetches one message at a time.

  1. Unsupported contract metadata, malformed JSON, or invalid required fields are logged and nacked without requeue.
  2. Template, provider, quota, SMTP, or usage-accounting failures are retried in-process three times with a five-second delay.
  3. The message is acknowledged only after SMTP accepts the email and provider usage is incremented.
  4. If CorrelationContext.ReplyTo is present, result publication is attempted after delivery. It is best-effort: a reply-queue failure is logged but does not make the SMTP delivery fail, because retrying after SMTP success could send a duplicate email.
  5. After the final processing retry fails, the shared terminal-failure hook publishes one negative result to the same ReplyTo. It retains the original correlation and contains a normalized diagnostic string limited to 2000 characters. Common SMTP DNS, connection-refused, timeout, and recipient-rejection failures receive safe explanatory context while retaining the low-level provider reason. Hostnames, recipient addresses, and credentials are not added. Client outboxes store this as a neutral delivery error rather than interpreting SMTP-specific exception types.

Success and terminal failure result publication remain best-effort. If the result queue cannot be reached, the internal exception is logged and the original delivery follows its normal ACK/NACK policy. Clients therefore must not interpret the absence of a result as a confirmed failure.

Delivery remains at least once. There is no inbox/deduplication store in this MVP, so a broker redelivery or a process failure after SMTP acceptance but before acknowledgement can produce a duplicate. MessageId, CorrelationId, and MessageType are added to the structured log scope for correlation. Recipient addresses are masked, and template variable names/values and rendered links are not logged.

No database migration is required. Existing channel selection, SMTP settings, quota checks, and usage counters remain in place.

Local end-to-end setup

With Docker Desktop running, install and start the complete Notification Service development stack from the repository root:

.\docker\environments\install-development.cmd

The script uses the tracked docker/environments/.env.Development defaults. The following manual setup is only needed when overriding those values locally.

Create a local environment file outside source control, for example docker/environments/.env.local, with these values:

DB_NAME=notification_service
DB_USER=postgres
DB_PASS=postgres
VOLUME_PREFIX=ns-dev
RABBITMQ_USER=guest
RABBITMQ_PASSWORD=guest
RABBITMQ_VIRTUAL_HOST=/
RABBITMQ_AMQP_PORT=5672
RABBITMQ_MANAGEMENT_PORT=15672
DEVELOPMENT_EMAIL_SERVICE_NAME=TestService

Start the Notification Service stack from the repository root:

docker compose --env-file docker/environments/.env.local `
  -f docker/environments/docker-compose.yml `
  -f docker/environments/docker-compose.Development.yml `
  up --build -d

Development compose exposes:

  • Notification Service admin: http://localhost:5200/admin/channels and /admin/templates
  • RabbitMQ management: http://localhost:15672
  • Mailpit inbox: http://localhost:8025
  • Seq: http://localhost:5342

The development migrator idempotently creates an active SMTP channel for DEVELOPMENT_EMAIL_SERVICE_NAME using host mailpit, port 1025, SSL disabled, and blank credentials. It also creates a neutral English TestEmail template with RecipientName and Message variables. Existing channels and templates are left unchanged. Use the admin UI to inspect or customize them and to add client-specific service names, template keys, or languages.

Configure ItemTracker to use the same RabbitMQ host, port, credentials, virtual host, queue notification.send-email, and service name. When ItemTracker runs outside Docker against the development stack, the broker is localhost:5672; from a Docker container it is the reachable host or shared-network name. Start the ItemTracker Outbox Worker in RabbitMq mode, trigger a production-safe notification to an owned test address, then verify:

  1. the ItemTracker Outbox row becomes published;
  2. RabbitMQ delivers and removes the message from notification.send-email;
  3. Notification Service logs show the same MessageId and CorrelationId without the full address;
  4. Mailpit shows exactly one rendered email;
  5. the Notification Service channel usage counter increments;
  6. the client result queue is consumed and the matching outbox row records successful delivery.

Production configuration and smoke test

Production requires the same App__RabbitMq__Host, Port, User, Password, and VirtualHost values as ItemTracker's publisher, plus the Notification Service database connection. The production compose file obtains these from deployment environment variables; secrets must stay in the deployment secret store. The queue is declared durable by both producer and consumer.

This Notification Service revision restores the published immutable HrynCo.RabbitMq version 1.0.17 from NuGet.org. Build and deployment environments therefore require NuGet.org access or a trusted package mirror containing that exact version.

Before enabling the Worker, verify that the target service has an active SMTP channel and exact-language templates for every queued ItemTracker template key. Inspect any delayed ItemTracker backlog for expired password-reset, verification, or invitation messages before draining it.

The template administration list preserves optional Service Name and Key filters when an administrator opens the create or edit screen. These links use explicit Razor expression boundaries so the filter query is appended as query parameters rather than rendered as a literal @filterQuery path segment. With no active filters, the create route is exactly /admin/templates/create.

For the smoke test, use an owned test account and a non-sensitive notification template. Record the Outbox Id and CorrelationId, trigger only one message, follow those identifiers through Outbox publication and Notification Service logs, and confirm receipt with the configured SMTP provider. Do not copy payloads, tokens, credentials, full recipient addresses, or rendered URLs into tickets or logs.