Templates, mentions & testing
Mentions
Section titled “Mentions”Tag specific users or groups in alert messages. Mentions are channel-agnostic: you write plain usernames in metric config, and each channel formats them in its native syntax.
Basic Setup
Section titled “Basic Setup”alerting: enabled: true channels: - mattermost_ops consecutive_anomalies: 3 mentions: - oncall_engineer - devops_teamThis appends @oncall_engineer @devops_team to alert messages in Mattermost.
Platform-Specific Formatting
Section titled “Platform-Specific Formatting”detectkit automatically formats mentions for each platform:
| Config Value | Mattermost | Slack | Telegram | Discord | Teams | Google Chat | ntfy | |
|---|---|---|---|---|---|---|---|---|
username | @username | @username (display only) | @username | CC: username | @username (display only) | plain text (never pings) | @username (display only) | plain text in body (never pings) |
here | @here | <!here> (broadcast) | @here | (ignored) | @here (real ping) | plain text (never pings) | <users/all> (real ping) | plain text in body (never pings) |
channel | @channel | <!channel> (broadcast) | @channel | (ignored) | @everyone (real ping) | plain text (never pings) | <users/all> (real ping) | plain text in body (never pings) |
all | @all | <!everyone> (broadcast) | @all | (ignored) | @everyone (real ping) | plain text (never pings) | <users/all> (real ping) | (ignored) |
U04ABCD1234 | @U04ABCD1234 | <@U04ABCD1234> (real ping) | @U04ABCD1234 | CC: U04ABCD1234 | @U04ABCD1234 (display only) | plain text (never pings) | @U04ABCD1234 (display only) | (ignored) |
Slack note: Slack webhooks do not actually ping users with
@username— it’s display-only. For real pings, use Slack User IDs (format:U+ alphanumeric, found in user profile > “Copy member ID”).
Discord note: a bare username never pings — only
all/everyone/channel/hereand a real<@user_id>/<@&role_id>token do. Put the real Discord mention id directly inmentions:for a ping.
Teams note:
@mentionsare always plain text on this channel — Teams needs a real Adaptive Card mention entity (an Azure AD user object id), which detectkit’s alert config doesn’t carry. Set up a real ping inside the Workflow itself if you need one.
Google Chat note: only the space-wide
<users/all>token (or a real<users/USER_ID>id passed through verbatim) actually pings; a bare username or<users/USER_ID>id renders as visible text.
ntfy note: ntfy is a push-notification service with no user-mention concept —
mentions:entries render as plain@nametext at the end of the notification body and never ping anyone.
Special Keywords
Section titled “Special Keywords”Use these keywords for broadcast mentions:
here— Notify active members (Mattermost:@here, Slack:<!here>, Discord:@here, Google Chat:<users/all>; Teams and ntfy render it as plain text)channel— Notify all channel members (Mattermost:@channel, Slack:<!channel>, Discord:@everyone, Google Chat:<users/all>; Teams and ntfy render it as plain text)all— Notify everyone (Mattermost:@all, Slack:<!everyone>, Discord:@everyone, Google Chat:<users/all>; Teams and ntfy render it as plain text)
Custom Template Placement
Section titled “Custom Template Placement”By default, mentions appear at the end of the message. Use template variables for custom placement:
{mentions}— Formatted mentions string (e.g.,@user1 @user2), empty string if none{mentions_line}— Same but with a leading newline, empty string if none
alerting: mentions: - oncall_engineer
# Place mentions at the top of the message template_consecutive: | {mentions} Alert: {metric_name} Time: {timestamp} Value: {value} | CI: {confidence_interval} Consecutive: {consecutive_count}Mentions with Recovery
Section titled “Mentions with Recovery”Mentions are included in both anomaly alerts and recovery notifications:
alerting: mentions: - oncall_engineer notify_on_recovery: true template_recovery: | {mentions} Resolved: {metric_name} at {timestamp} Value: {value}Configuration
Section titled “Configuration”| Field | Type | Default | Description |
|---|---|---|---|
mentions | List[str] | [] | Users/groups to mention. Plain usernames without @. |
No @ prefix needed — detectkit adds the appropriate prefix for each channel.
Timezone Display
Section titled “Timezone Display”Alerts display timestamps in UTC by default. Override per metric:
alerting: timezone: "Europe/Moscow" # MSK (UTC+3) timezone: "America/New_York" # EST/EDT timezone: "Asia/Tokyo" # JST (UTC+9)Note: This only affects alert display. All internal timestamps remain UTC.
Custom Alert Templates
Section titled “Custom Alert Templates”Override default alert message format.
Default Template
Section titled “Default Template”The default message foregrounds the alert — first how long it has been going on (the plain-language lead), then the rule that fired, with the anomaly as supporting evidence below. The order is the same on every channel and for both anomaly and recovery: description → Rule → Value/Expected.
🔴 {project_name_prefix}Alert: {metric_name}{description_line}{anomaly_lead}Rule: {rule_display}
Value: {value_display} | Expected: {expected_range}Quorum: {detector_count}/{min_detectors} · {direction}Severity: {severity:.2f}{window_line}Detectors: {detector_name}Parameters: {detector_params}{dashboard_line}{help_line}{mentions_line}The first line names the alert and the metric, led by the project name as a
[name] prefix (from detectkit_project.yml) so two projects posting to the
same channel stay distinguishable while keeping the default brand bot name +
avatar. See Channels for where each channel surfaces it.
{anomaly_lead} answers “when did this start, how long has it been running?”
— e.g. Anomalous for 2h 30m — 15 consecutive 10min intervals. (the metric
interval, the true streak length and the wall-clock duration). The Rule: line
sits right above the evidence it explains and restates the configured
thresholds via {rule_display} — for a config with no fraction-based alert
window this renders the
same min_detectors=… · direction=… · consecutive=… chip as before; a
share-configured metric also names anomaly_window / min_anomaly_share,
leading with whichever rule actually fired. {window_line} gives the
problematic span as Anomaly began: … | Latest reading: … (and
Anomaly began: … | Alert fired: … | Recovered: … on recovery — where
Alert fired is the on-grid moment the rule first tripped, distinct from the
onset).
{expected_range} renders one-sided detector bounds cleanly (e.g. >= 7.00
for a lower-only manual_bounds) instead of [7.00, nan].
How long is “true”? The streak length / onset are resolved at fire time by looking back over the detection history until the run breaks (bounded — a run older than the lookback window renders as
over …), so it reflects the real incident, not just theconsecutive_anomaliespoints the rule needs. The recovery message reports the same span the just-cleared incident covered (Incident lasted …).
Creating Custom Template
Section titled “Creating Custom Template”- Create template file in
templates/directory:
Alert: {{ metric_name }}
Current value: {{ value|round(2) }}Expected range: [{{ confidence_lower|round(2) }}, {{ confidence_upper|round(2) }}]
Severity: {{ severity|round(2) }} ({{ direction }})Detected by: {{ detector_name }}
Time: {{ timestamp }} {{ timezone }}
{% if consecutive_count > 1 %}Persisting for {{ consecutive_count }} consecutive points!{% endif %}- Reference in metric config:
alerting: template_consecutive: templates/custom_alert.j2Available Template Variables
Section titled “Available Template Variables”| Variable | Description | Available in |
|---|---|---|
metric_name | Metric name | all |
project_name | detectkit_project.yml name, empty if not set | all — populated for every alert by the pipeline |
project_name_prefix | "[<project_name>] " if set, else empty; leads every default title/headline/subject so multiple projects on one channel stay distinct | all |
timestamp | Timestamp (formatted in {timezone}) | all |
timezone | Timezone display name | all |
value | Current metric value (numeric, or string "no data" for no-data) | all |
value_display | NaN-safe string version — always renders, falls back to "no data" | all |
confidence_lower / confidence_upper | Bounds of confidence interval | anomaly, recovery |
confidence_interval | Formatted as [lower, upper] or "N/A" | all |
expected_range | One-sided aware expected band: >= lo, <= hi, [lo, hi], or "N/A". Renders one-sided detector bounds cleanly instead of [7.00, nan] | all |
detector_name | Detector that triggered (e.g., "MADDetector:threshold=3.0"); "N detectors" when several detectors formed the quorum | anomaly, recovery |
detector_params | Detector parameters as a JSON string (empty for no-data/error) | anomaly, recovery |
detector_count | Observed number of detectors that agreed (the quorum size that fired) | anomaly |
min_detectors | Configured quorum threshold the alert fired on (the rule) | anomaly, recovery |
severity | Severity score; max across the quorum for multi-detector alerts | anomaly |
direction | Observed/locked anomaly direction: "up" or "down"; also "mixed" for an any-policy quorum spanning both up and down, and "none" for no-data/recovery | all |
direction_policy | Configured direction rule: "same", "any", "up", "down" | anomaly, recovery |
consecutive_count | True consecutive streak length — resolved at fire time by looking back over the detection history, not capped at the rule threshold (recovery: the just-ended incident length) | anomaly, recovery |
consecutive_required | Configured consecutive threshold the alert fired on (the rule) | anomaly, recovery |
rule_display | The full alert-rule chip — the legacy min_detectors=… · direction=… · consecutive=… unless the metric also configures the fraction-based alert window, in which case it also names anomaly_window / min_anomaly_share, leading with whichever rule fired | anomaly, recovery (a share-configured metric’s recovery echoes the same combined chip, so fire and recovery name one rule) |
window_points / window_matched | Fraction-rule window size (in points) and the matched-quorum-point count within it; both empty unless the alert is share-configured (window_points) / share-fired (window_matched) | anomaly, recovery (window_points only) |
interval_display | Metric interval as a string (e.g. "10min") | all |
duration_display | How long the streak/incident has run (e.g. "2h 30m"; "over …" when older than the lookback window) | anomaly, recovery |
onset_display / started_display | First anomalous timestamp of the run — the onset, not the alert-fire time (formatted in {timezone}); started_display adds "or earlier" when the run is capped | anomaly, recovery |
fired_display | On-grid moment the alert first fired — onset + (consecutive_required − 1) × interval (formatted in {timezone}); empty when the run is capped or no interval is wired in | recovery |
anomaly_lead / recovery_lead | Ready-made plain-language lead — "Anomalous for …" / "… Incident lasted …" (falls back to "Latest X/Y consecutive points met the quorum." when no interval is wired in) | anomaly, recovery |
window_line | "Anomaly began: … | Latest reading: …\n" (anomaly) / "Anomaly began: … | Alert fired: … | Recovered: …\n" (recovery), or a single "Detected at: …" line when the onset is unknown | all |
status | "ANOMALY", "RECOVERED", "NO_DATA", or "ERROR" | all |
error_type / error_message | Exception details | error only |
description / description_line | Metric description | all |
synonyms / synonyms_line | Metric’s alternative names from ai_context.synonyms — "total revenue, gross sales" / "Also known as: …\n" (empty when unset). Opt-in: not in the default templates, so add {synonyms_line} to a custom template to surface an “Also known as: …” line | all |
mentions / mentions_line | Formatted mentions | all |
dashboard_url | Raw alerting.dashboard_url (empty string when unset); also surfaced natively as a clickable title on Slack/Mattermost/Discord, an inline link on Telegram, an “Open dashboard” button in email, and an action button on Teams/Google Chat/ntfy | all |
dashboard_line | "Dashboard: <url>\n" when set, else empty; appended to the default plain-text templates | all |
help_url | Raw “How to read this alert” URL (empty when hidden via alert_help_url: false); also surfaced natively as a clickable label in the webhook/Discord Links line, a links-line entry on Telegram, a footer link in email, and an action button on Teams/Google Chat/ntfy | all |
help_line | "How to read this alert: <url>\n" when set, else empty; appended to the default plain-text templates | all |
All variables are always substitutable in every alert kind — the
“Available in” column marks where a value is meaningful, not where the
placeholder is valid. Using a variable outside its listed kinds renders a
neutral fallback rather than raising a KeyError.
Format-spec safety: if a template uses
{value:.2f}(or any numeric format spec) on a no-data or error alert where there’s no real value, detectkit falls back to the kind-appropriate default template instead of crashing. Still cleaner to write kind-appropriate templates from the start.
Template Types
Section titled “Template Types”template_single- Used when the alert hasconsecutive_count≤ 1 (i.e.consecutive_anomalies: 1configs)template_consecutive- Used for streaks (consecutive_count> 1)template_singleandtemplate_consecutivefall back to each other when only one is settemplate_recovery- Used for recovery notificationstemplate_no_data- Used for no-data alertserror_alerting.template- Used for project-level pipeline errors (indetectkit_project.yml)
Testing Alerts
Section titled “Testing Alerts”Test alert configuration without waiting for real anomalies.
Test Alert Command
Section titled “Test Alert Command”cd my_projectdtk test-alert api_response_timeThis sends a mock alert through configured channels with fake data. The mock
uses the alert config’s own rule (min_detectors / direction /
consecutive_anomalies), so the preview matches what a real firing would look
like — here with the defaults (min_detectors: 1, direction: same,
consecutive_anomalies: 3):
🔴 [my_monitoring] Alert: api_response_timeAnomalous for 30m — 3 consecutive 10min intervals.Rule: min_detectors=1 · direction=same · consecutive=3
Value: 0.8532 | Expected: [0.45, 0.62]Quorum: 1/1 · upSeverity: 4.52Anomaly began: 2026-06-12 14:10:00 (UTC) | Latest reading: 2026-06-12 14:30:00 (UTC)Detectors: MADDetector:threshold=3.0Parameters: {"threshold": 3.0, "window_size": 8640}Use cases:
- Verify webhook URLs work
- Check alert formatting
- Test custom templates
- Validate channel permissions