> For the complete documentation index, see [llms.txt](https://docs.opendatadiscovery.org/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.opendatadiscovery.org/developer-guides/architecture-decision-log/adr-0041-notification-per-channel-presence-activation.md).

# ADR-0041: Notification channels activate by the presence of their keys

## Status

**Accepted.** Reconstructed from the codebase on 2026-05-30; the decision is live in the source today.

## Context

Once the Notifications subsystem is enabled (ADR-0040), an operator still has to choose *where* alerts go: Slack, a generic webhook, email, or some combination. The platform needs a way to activate exactly the channels an operator wants — without a separate enable flag per channel that could drift out of step with whether the channel is actually configured.

## Decision

**Each channel activates by the presence of its own configuration key.** The three sender beans each carry `@ConditionalOnProperty` on the key that channel needs — `notifications.receivers.slack.url`, `notifications.receivers.webhook.url`, `notifications.receivers.email.sender`. Setting the key creates the bean (the channel is on); leaving it unset means no bean is created and that channel is silently absent. There is no separate `slack.enabled` / `email.enabled` flag — **the configuration key is the toggle.**

This composes with ADR-0040 into a **two-stage opt-in**: the subsystem must be enabled (`notifications.enabled: true`), and then each channel is activated by populating its key. An operator who enables notifications but sets no channel keys gets a running subsystem that delivers nowhere.

The presence test is for the key being set; a key set to an *empty* value is caught separately by the fail-fast empty-value check inside the sender's bean factory (ADR-0018), which aborts startup rather than building a broken sender.

## Consequences

* Operators configure only the channels they want, by populating only those keys — the common case is "set one key, get one channel."
* Adding a new channel is an add-a-bean-method change carrying its own `@ConditionalOnProperty`, not a change to a central channel registry.
* The trade-off is implicitness: an operator who *omits* a channel's key gets no warning (it is indistinguishable from deliberately not wanting that channel). A *malformed* (empty) key is the case the fail-fast check catches; an *absent* key is by-design silent.
* Channel activation is independent — one configured channel works regardless of whether the others are set.

## Evidence

* `odd-platform-api/.../notification/config/NotificationConfiguration.java:37` — `@ConditionalOnProperty(name = "notifications.receivers.email.sender")` on the `mailSender` bean; `:102` — the same key on the `emailNotificationSender` bean (the email channel needs two beans, both presence-gated on one key).
* `odd-platform-api/.../notification/config/NotificationConfiguration.java:75` — `@ConditionalOnProperty(name = "notifications.receivers.slack.url")` on `slackNotificationSender`; `:89` — `@ConditionalOnProperty(name = "notifications.receivers.webhook.url")` on `webhookNotificationSender`.
* `odd-platform-api/src/main/resources/application.yml:180-186` — the `notifications.receivers.{slack.url|webhook.url|email.sender}` keys ship empty, so no channel is active by default.

## See also

* [ADR-0040 — Notifications ship disabled by default behind one condition](/developer-guides/architecture-decision-log/adr-0040-notifications-disabled-by-default.md) — the subsystem gate; this record is the per-channel second stage.
* [ADR-0018 — Outbound-integration config is fail-fast at boot](/developer-guides/architecture-decision-log/adr-0018-fail-fast-outbound-config-at-boot.md) — the empty-value check that turns a malformed channel key into a startup failure.


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.opendatadiscovery.org/developer-guides/architecture-decision-log/adr-0041-notification-per-channel-presence-activation.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
