CH-UICH-UI

Alerts

Email alerting for governance and scheduled jobs — SMTP, Resend, and Brevo channels, rules with severity thresholds and cooldowns, delivery retries, and an event history

Alerts turn governance and scheduler events into email notifications: a policy blocks a query, a scheduled job fails or runs slow, and the right people hear about it — through your own SMTP server or a transactional provider.

Alerts are a Pro feature. Manage them under Governance → Alerts; the API lives at /api/governance/alerts/* (returns 402 on the free edition, and the background dispatcher only runs with a Pro license).

The pipeline is: events (emitted by CH-UI) → rules (which events matter, at what severity) → channels (how email leaves the building).

What can trigger an alert

Three event types, emitted automatically:

Event typeSeverityEmitted when
policy.violationpolicy severityA governance policy is violated — a block policy rejects a query pre-execution, or a warn policy detects a violation post-execution during query-log sync. Also emitted at warn when a pre-execution guardrail check can't be trusted (access state stale or missing) and the query is allowed through
schedule.failederrorA scheduled query run errors
schedule.slowwarnA scheduled run succeeds but takes ≥ 80% of its timeout (minimum threshold 5 seconds)

So yes — schedules alert out of the box: every failed run emits a schedule.failed event with the schedule name, run ID, elapsed time, and error in the payload. You only need a rule and a channel to receive it.

Severities rank info < warn < error < critical.

Channels

A channel is one way to deliver email. Three provider types:

ProviderRequired configOptional config
SMTPhost, from_email (password required if username is set)port, username, password, from_name, use_tls, starttls, insecure_skip_verify
Resendapi_key, from_emailfrom_name
Brevoapi_key, from_emailfrom_name

Channel configs are encrypted at rest with the app secret key. Secrets are never echoed back: the API returns the password / API key blanked, with a has_secret flag so the UI knows one is stored — updating a channel without re-entering the secret keeps the existing one.

SMTP options

FieldDefaultMeaning
hostSMTP server hostname (required)
port587SMTP server port
usernameAuth username; leave empty for unauthenticated relays
passwordAuth password (required when username is set)
from_emailEnvelope and header From address (required)
from_nameDisplay name, rendered as Name <from_email>
use_tlsfalseImplicit TLS: the whole connection is TLS from the first byte
starttlstrue when use_tls is offConnect in plaintext, then upgrade via STARTTLS
insecure_skip_verifyfalseSkip TLS certificate verification (self-signed certs; avoid in production)

Common setups:

  • Port 465 (implicit TLS)port: 465, use_tls: true. Gmail, most managed SMTP.
  • Port 587 (STARTTLS)port: 587, leave the TLS flags alone: starttls defaults to on whenever use_tls is off. The most common submission setup.
  • Port 25, no TLS (internal relay)port: 25, use_tls: false, starttls: false.

One caveat worth knowing: STARTTLS is opportunistic — if the server doesn't advertise the STARTTLS extension, the mail is sent in plaintext rather than failing. For a guaranteed-encrypted connection, use implicit TLS (use_tls: true).

Testing a channel

Every channel has a Test button (POST /api/governance/alerts/channels/{id}/test with recipients and optional subject / message). The test sends immediately, bypassing the rule engine and the dispatch queue — so it verifies exactly one thing: can this config deliver mail. Test before you depend on it.

Rules

Rules decide which events become email, and where it goes:

FieldDefaultDescription
NameRule identifier (required)
event_typepolicy.violation, schedule.failed, schedule.slow, or * (wildcard, any also accepted)
severity_minOnly match events at or above this severity
enabledtrueToggle
cooldown_seconds300Deduplication window: the same event fingerprint won't be re-sent to the same channel within this window
max_attempts5Delivery attempts before a job is marked failed
subject_templatebuilt-inCustom subject, supports {{placeholders}}
body_templatebuilt-inCustom plain-text body, supports {{placeholders}}
channels[]One or more channel bindings, each with channel_id, recipients (email addresses), and is_active

Available template variables: {{event_type}}, {{severity}}, {{title}}, {{message}}, {{payload_json}}, {{channel_name}}, {{channel_type}}, {{created_at}}, {{event_id}}, {{rule_name}}. Leave the templates empty for a sensible default ([CH-UI][ERROR][schedule.failed] … subject, plain-text body with the event details and payload).

A good starter rule: event_type: *, severity_min: error, one channel, your on-call address. Widen from there.

Dispatch

A background dispatcher ticks every 8 seconds:

  1. Materialize — new events are matched against enabled rules (event type + severity floor); each matching active channel binding becomes a dispatch job (up to 100 events per tick).
  2. Deliver — due jobs are sent (up to 30 per tick).

Reliability behavior:

  • Cooldown dedupe — events carry a fingerprint (e.g. schedule:{id}:error, or policy + user + query hash). Within a rule's cooldown window, the same fingerprint is not re-dispatched to the same channel — a job failing every minute produces one email per cooldown, not sixty.
  • Retries — failed deliveries retry with exponential backoff: 10s × 2^(attempt−1), capped at 30 minutes, until max_attempts is reached, then the job is marked failed with its last error.

Events history

Governance → Alerts → Events shows the raw event stream — every alert-worthy thing that happened, whether or not a rule matched it. Each event records its type, severity, title, message, JSON payload, fingerprint, and processing status.

GET /api/governance/alerts/events supports limit (default 100), event_type, and status filters. If an email never arrived, check here first: an event with no matching rule means a routing gap, no event at all means the trigger never fired.

API

EndpointPurpose
GET / POST /api/governance/alerts/channelsList / create channels
PUT / DELETE /api/governance/alerts/channels/{id}Update / delete a channel
POST /api/governance/alerts/channels/{id}/testSend a test notification
GET / POST /api/governance/alerts/rulesList / create rules (rules come back with their channel bindings)
PUT / DELETE /api/governance/alerts/rules/{id}Update / delete a rule
GET /api/governance/alerts/eventsEvent history

Channel and rule changes are recorded in the Audit Log (alerts.channel.*, alerts.rule.* actions).

Alerts are the notification layer for Governance policies and Schedules: those pages define what gets watched, this one defines who finds out.

On this page