feat(email): alert and weekly-report emails read as designed messages (#5335)
#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:
@@ -41,27 +41,11 @@ def smtp_mode(port, tls) -> str:
|
||||
return "starttls" if tls else "plain"
|
||||
|
||||
|
||||
def email_body(alert: dict) -> str:
|
||||
value = "no data" if alert.get("value") is None else alert["value"]
|
||||
return (
|
||||
f"Alert: {alert['state']}\n"
|
||||
f"Rule: {alert['rule_name']}\n"
|
||||
f"Resource: {alert['resource']}\n"
|
||||
f"Metric: {alert['metric']} = {value}\n"
|
||||
f"Threshold: {alert['threshold']}\n"
|
||||
+ (f"Note: {alert['note']}\n" if alert.get("note") else "")
|
||||
+ f"Time: {alert['timestamp']}\n"
|
||||
)
|
||||
|
||||
|
||||
async def _send_email(cfg: dict, alert: dict) -> dict:
|
||||
from steward.core.mail import alert_email, message
|
||||
try:
|
||||
msg = EmailMessage()
|
||||
# A header may not hold a line break; a rule name can (MCP takes any string).
|
||||
msg["Subject"] = " ".join(f"[Steward] {alert['state']} — {alert['rule_name']}".split())
|
||||
msg["From"] = cfg.get("from") or cfg.get("username") or "steward@localhost"
|
||||
msg["To"] = ", ".join(cfg["recipients"])
|
||||
msg.set_content(email_body(alert))
|
||||
subject, text, html = alert_email(cfg, alert)
|
||||
msg = message(cfg, subject, text, html)
|
||||
loop = asyncio.get_running_loop()
|
||||
await loop.run_in_executor(None, _smtp_send_sync, cfg, msg)
|
||||
return {"sent": True, "error": None}
|
||||
|
||||
Reference in New Issue
Block a user