Skip to content

Add a notifier

A notifier delivers one alert on one channel. Like collectors, adding one does not touch the loop.

interface Notifier {
readonly channel: ChannelType
notify(notification: Notification): Promise<void>
}

A Notification carries the watch id and name, the owner, the channel, an optional destination override, the title, the summary, the evidence (quote, URL, date), the confidence and the tier.

  1. Add the channel type to the channel list in the domain models. It is a closed set, so existing watches keep validating.

  2. Write the adapter in src/adapters/notifiers/:

    export class MyNotifier implements Notifier {
    readonly channel: ChannelType = 'mychannel'
    constructor(private readonly options: MyNotifierOptions) {}
    async notify(notification: Notification): Promise<void> {
    const response = await this.options.fetch(this.endpoint, {
    method: 'POST',
    headers: { 'content-type': 'application/json' },
    body: JSON.stringify(formatMyMessage(notification)),
    })
    if (!response.ok) {
    // Status only — never the token, the URL or the payload.
    throw new Error(`MyChannel delivery failed (${response.status})`)
    }
    }
    }
  3. Export a pure formatting function (formatMyMessage) separately from the transport. It makes the interesting part testable without mocking anything.

  4. Honour the tier. A weak alert must be visibly different from a strong one — the existing channels prefix “Possible update (unconfirmed)”. A reader must never mistake an unconfirmed alert for a confirmed one.

  5. Honour chatId, the per-watch destination override, if your channel has any concept of a destination.

  6. Register it in entrypoints/wiring.ts, with a console fallback when its configuration is absent — that is the established pattern, and it keeps development working with no credentials.

  7. Add configuration through the central config module. Never read the environment directly from an adapter.

  8. Document it in Channels and the watch schema.

Test Why
Formats a strong alert correctly The message is the product
Formats a weak alert differently The tier must be unmistakable
Includes the evidence quote and URL when present, omits them when not Optional fields
Uses chatId when given, the default otherwise Routing
Throws on a non-2xx response Retry depends on it
Never includes the token in the error Credential safety

Inject a fake transport. No test may touch the network.

  • Credentials arrive through configuration, never from a watch definition.
  • Never log a token, and never include one in an error message or a URL that might be logged.
  • Alert content includes collected page text. If your channel posts somewhere shared, say so in the documentation so people know what they are exposing.