feat(email): alert and weekly-report emails read as designed messages (#5335)
CI / lint (push) Successful in 4s
CI / integration (push) Successful in 1m22s
CI / unit (push) Successful in 1m49s
CI / publish (push) Successful in 1m13s

#5335 "Emails Steward sends read as designed messages: an HTML alert email
and weekly report, with a plain-text twin".

- core/mail.py renders each email as multipart/alternative from
  templates/email/: a table-and-inline-style HTML part and a plain-text
  twin carrying the same facts. Colours are the design system's light
  theme, resolved from theme.css; nothing is fetched when a mail opens.
- Alert email: state badge, a headline sentence ("Disk used on web01 is
  94%."), rule, resource, reading, the condition in words, and the time in
  the zone Notifications was saved in with UTC beside it; "View in
  Steward" when Steward's address is set. Subject "[Steward] Firing: <rule>
  — <resource> 94%". Alerts now carry their operator.
- Weekly report: monitors down, active alerts, the week's alert events,
  uptime and busiest Traefik routers as small tables with links. Sent from
  the configured From address; the "Generated … UTC" line and the stale
  repo footer are gone.
- condition_text() states a condition from a rule's parts.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-08 14:08:05 -04:00
co-authored by Claude Opus 5.5
parent 24700ee608
commit ec55faf737
18 changed files with 539 additions and 113 deletions
+9 -3
View File
@@ -134,15 +134,21 @@ _BINARY = ("is_up", "is_healthy")
def rule_condition(rule: AlertRule) -> str:
"""What a rule fires on, in words: "CPU > 90%", "Fullest disk >= 85%",
or "is down" for an up/down metric rather than "is_up < 0.5"."""
return condition_text(rule.metric_name, rule.operator, rule.threshold)
def condition_text(metric: str, operator: AlertOperator | str, threshold: float) -> str:
"""rule_condition from a rule's parts, for an alert carried as a dict (an
email); the operator may be its value, ">=", rather than the enum."""
from steward.core.alerts import _is_breached
from steward.core.formatting import metric_label, metric_value
metric = rule.metric_name
operator = AlertOperator(operator)
if metric in _BINARY:
fires_on = [metric_value(metric, v) for v in (1.0, 0.0)
if _is_breached(v, rule.operator, rule.threshold)]
if _is_breached(v, operator, threshold)]
if len(fires_on) == 1:
return f"is {fires_on[0]}"
return f"{metric_label(metric)} {rule.operator.value} {metric_value(metric, rule.threshold)}"
return f"{metric_label(metric)} {operator.value} {metric_value(metric, threshold)}"
def alert_sentence(rule: AlertRule, label: str, value: float | None,