1ceb71b9a2
Use explicit Razor interpolation for create and edit URLs and cover the rendered link syntax with a regression test. Ref: IT-1039
122 lines
7.7 KiB
Markdown
122 lines
7.7 KiB
Markdown
# 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:
|
|
|
|
```powershell
|
|
.\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:
|
|
|
|
```dotenv
|
|
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:
|
|
|
|
```powershell
|
|
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.
|