fix(notifications): a line break in a rule name no longer stops the alert email (#5064)
CI / lint (push) Successful in 2s
CI / integration (push) Successful in 58s
CI / unit (push) Successful in 1m9s
CI / publish (push) Successful in 1m16s

The rule name goes into the Subject header, and EmailMessage refuses a
header holding a line break, so the whole email failed. The web form can't
enter one but MCP takes any string. The subject now folds whitespace.
Found by the new From-fallback test in CI run 8349.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-06 13:58:59 -04:00
co-authored by Claude Opus 5.5
parent 35fca57397
commit 88d34a3827
2 changed files with 7 additions and 4 deletions
+2 -1
View File
@@ -57,7 +57,8 @@ def email_body(alert: dict) -> str:
async def _send_email(cfg: dict, alert: dict) -> dict:
try:
msg = EmailMessage()
msg["Subject"] = f"[Steward] {alert['state']} — {alert['rule_name']}"
# 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))
+5 -3
View File
@@ -106,15 +106,17 @@ def test_smtp_send_connects_by_mode_with_a_timeout(monkeypatch, port, tls, expec
assert smtp.calls == expected + [("login", "u", "p"), ("send", "steward@example.com")]
async def test_email_from_address_falls_back_to_username_then_localhost(monkeypatch):
async def test_email_from_falls_back_and_a_line_break_in_the_name_still_sends(monkeypatch):
sent = []
monkeypatch.setattr(n, "_smtp_send_sync", lambda cfg, msg: sent.append(msg["From"]))
monkeypatch.setattr(n, "_smtp_send_sync", lambda cfg, msg: sent.append((msg["From"], msg["Subject"])))
base = {"host": "mx", "port": 587, "recipients": ["a@example.com"]}
for cfg in ({**base, "from": "alerts@example.com", "username": "u@example.com"},
{**base, "from": "", "username": "u@example.com"},
{**base, "from": "", "username": ""}):
assert (await n._send_email(cfg, ALERT))["sent"]
assert sent == ["alerts@example.com", "u@example.com", "steward@localhost"]
assert [f for f, _ in sent] == ["alerts@example.com", "u@example.com", "steward@localhost"]
# The rule name has a line break; the subject folds it to a space rather than failing.
assert sent[0][1] == '[Steward] FIRING — Disk "root" on web01 \\ backslash'
def test_email_body_names_no_data_and_the_note():