Problem/Motivation

Alerting is currently hard-wired to email. AlertService::checkAndAlert() both decides whether an alert should fire (the threshold-crossing logic) and how it is delivered (it calls MailManagerInterface::mail() directly and loops over alerts.emails). The two responsibilities are entangled in one class.

This has real limitations:

  • Teams that manage AI budgets in Slack, Microsoft Teams, PagerDuty, or a custom monitoring system cannot receive alerts without patching the module. Webhook/Slack delivery is on the roadmap but has no code - it only exists as an example in ai_metering.api.php.
  • Adding a new delivery mechanism means editing AlertService itself, so every channel a site needs has to live in this module rather than in the site's or a contrib module's own code.
  • The delivery logic is not independently testable - a channel cannot be unit-tested without exercising the threshold math around it.

Delivery is a good fit for Drupal's plugin system: the service should own the decision, and each channel should own its own delivery.

Proposed Resolution

Split alert delivery into a plugin type so AlertService is reduced to detection only.

  • Define an AlertChannel plugin type - an AlertChannelInterface, an AlertChannelBase, an attribute/annotation, and a plugin manager (ai_metering.alert_channel). Plugins are discovered from src/Plugin/AlertChannel/.
  • Introduce a small value object (for example AlertEvent) carrying the alert payload that checkAndAlert() already assembles: uid, username, threshold, pct_used, tokens_used, budget. AlertChannelInterface::send(AlertEvent $event): void receives it.
  • Refactor AlertService to keep only the once-per-threshold-crossing detection logic. After a crossing is detected it builds the AlertEvent and dispatches it to every enabled channel via the plugin manager, instead of calling MailManagerInterface itself.
  • Move the existing email behaviour into an email AlertChannel plugin. It wraps the current MailManagerInterface call, the alerts.emails recipient loop, the per-address FILTER_VALIDATE_EMAIL check, and the failure logging. Behaviour is preserved so existing sites see no change.
  • Add a webhook AlertChannel plugin that POSTs a JSON body built from the AlertEvent to a configured endpoint via the http_client service, with a Slack-compatible payload shape (a text summary plus the structured fields) so it works with Slack and Teams incoming webhooks out of the box. Failures are logged and never interrupt the metering request.
  • Extend ai_metering.settings and its schema with per-channel enable flags and channel config - for the webhook plugin at minimum a url, an optional secret/header, and a timeout. The settings form gains a section per channel.
  • Tests: a unit test per channel (email delivery, webhook payload shape and enabled/disabled behaviour) plus an AlertService test asserting it dispatches to enabled channels exactly once per crossing and does no delivery itself.

Result: AlertService owns when, plugins own how, and sites can add channels without touching this module.

AI Assistance

Yes, AI was used to explore the issue.

Command icon Show commands

Start within a Git clone of the project using the version control instructions.

Or, if you do not have SSH keys set up on git.drupalcode.org:

Comments

marcus_johansson created an issue. See original summary.

codeitwisely’s picture

Assigned: Unassigned » codeitwisely

  • 765f58a6 committed on 1.0.x
    Issue #3608699 by codeitwisely, marcus_johansson: Add alert channel...
codeitwisely’s picture

Status: Active » Fixed

Now that this issue is closed, review the contribution record.

As a contributor, attribute any organization that helped you, or if you volunteered your own time.

Maintainers, credit people who helped resolve this issue.