Skip to main content

Notification API Tenant Context

Summary

Tenant identity on the native surface is a caller-supplied string parameter, never a validated claim. It is applied by some endpoints and ignored by others. There is no global query filter and no ambient tenant accessor.

Audience

Security reviewers, engineers, architects, QA and support.

How tenant enters a request

There are exactly three verified paths.

  1. Query parameter — the inbox endpoint and the template list accept an optional tenantId query value.
  2. Request body field — template create and both test hooks carry a tenant identifier in the JSON body.
  3. Message metadata — for event-driven processing, tenant arrives on the message header rather than through HTTP.

No path validates the supplied value against an authenticated principal, a token claim, a registered tenant list or a request header. Any caller may name any tenant.

Per-endpoint tenant behavior

EndpointTenant behavior
GET /notifications/myApplied — matched directly against the recipient row's tenant
POST /notifications/{id}/readNot applied — lookup by identifier alone
GET /notification-templatesApplied only when supplied and non-blank; otherwise unfiltered across tenants
POST /notification-templatesApplied — a blank value is stored as null, creating a global template
PUT /notification-templates/{id}Not applied — lookup by identifier alone
POST /notification-templates/{id}/testInherited from the template row
GET /notifications/{id}Not applied
GET /notifications/{id}/attemptsNot applied
POST /notifications/{id}/retryNot applied
GET /notifications/failedNot applied — spans all tenants
GET /notifications/processed/{eventId}Not applied
GET /notifications/auditNot applied — spans all tenants
GET /notifications/statsNot applied — platform-wide counts
POST /notifications/test/dispatchCarried onto the envelope
POST /notifications/test/publishCarried into message metadata
GET / and GET /healthNot applicable

Six of the fifteen resource operations carry tenant in some form — applied, inherited or forwarded onto the event. The remaining nine apply no tenant predicate at all. The two operational endpoints are not applicable.

Null tenant semantics

A null tenant is meaningful rather than absent. On the template model it denotes a global default template available to every tenant, and the resolution order prefers a tenant-specific row over the global one. On the recipient model, a request that omits tenantId matches recipient rows whose tenant is null, so omitting the parameter does not broaden the search — it narrows it to the global partition.

Callers that omit the parameter expecting "all tenants" will instead receive only null-tenant rows.

No global filter

The persistence context declares no global query filter for tenant. Scoping is applied by each query individually, which is why the behavior varies by endpoint. Adding an endpoint without a tenant predicate produces a cross-tenant read silently, with no compile-time or runtime signal.

Compatibility surface

The monolith derives tenant implicitly. The caller is resolved from the email claim to a user record, and notification rows are matched on that user, so cross-tenant reads are prevented as a side effect of caller scoping rather than by an explicit tenant predicate. Rows additionally carry a nullable client tenant identifier which is projected into the response.

Classification

Partial.

Requires confirmation

Whether a validated tenant claim or header is planned for the native surface, and whether the unscoped administration endpoints are intended to remain cross-tenant, require confirmation.

See Also

Keywords

  • Notification API
  • Tenant Context
  • Draft API documentation

Source References

  • microservices/src/notification-service/Api/NotificationEndpoints.cs
  • microservices/src/notification-service/Infrastructure/NotificationDbContext.cs
  • microservices/src/notification-service/Domain/NotificationEntities.cs
  • Controllers/NotificationsController.cs

Revision Information

  • Status: Draft
  • Last reviewed: 2026-07-21
  • Review cycle: Quarterly