feat: notifier - use NS8 Redis SMTP relay as primary source via smtp_config.resolve_smtp_config()

This commit is contained in:
2026-05-18 15:28:57 +00:00
parent 5890142ce6
commit a296ab127d
+25 -12
View File
@@ -6,6 +6,11 @@ Formats a human-readable HTML + text email based on:
- correlation outcome (SUCCESS / PARTIAL / REPO_FAILURE)
- per-module statuses
- repository check results (if run)
SMTP configuration is resolved via smtp_config.resolve_smtp_config():
1. NS8 cluster Redis (cluster/mail_settings) - same relay used by NS8 itself
2. config.yml [smtp] section
3. localhost:25 fallback
"""
import logging
@@ -15,9 +20,11 @@ from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
from typing import Optional
from .smtp_config import resolve_smtp_config
log = logging.getLogger(__name__)
OUTCOME_EMOJI = {
OUTCOME_LABEL = {
"SUCCESS": "OK",
"PARTIAL": "WARNING",
"REPO_FAILURE": "CRITICAL",
@@ -33,7 +40,7 @@ OUTCOME_COLOR = {
def _build_text(correlation: dict, repo_status: Optional[dict]) -> str:
outcome = correlation["outcome"]
lines = [
f"NS8 Backup Monitor - {OUTCOME_EMOJI[outcome]}: {outcome}",
f"NS8 Backup Monitor - {OUTCOME_LABEL[outcome]}: {outcome}",
f"Time: {datetime.now(timezone.utc).isoformat()}",
f"Plans checked: {', '.join(correlation.get('backup_ids', []))}",
f"Modules: {correlation['succeeded']} OK / {correlation['failed']} FAILED / {correlation['total']} total",
@@ -60,10 +67,10 @@ def _build_text(correlation: dict, repo_status: Optional[dict]) -> str:
return "\n".join(lines)
def _build_html(correlation: dict, repo_status: Optional[dict]) -> str:
def _build_html(correlation: dict, repo_status: Optional[dict], smtp_source: str) -> str:
outcome = correlation["outcome"]
color = OUTCOME_COLOR[outcome]
label = OUTCOME_EMOJI[outcome]
label = OUTCOME_LABEL[outcome]
ts = datetime.now(timezone.utc).strftime("%Y-%m-%d %H:%M UTC")
plan_ids = ", ".join(correlation.get("backup_ids", [])) or "N/A"
@@ -131,6 +138,7 @@ def _build_html(correlation: dict, repo_status: Optional[dict]) -> str:
<tbody>{rows}</tbody>
</table>
{repo_section}
<p style="color:#aaa;font-size:11px;margin-top:24px">Sent via {smtp_source}</p>
</div>
</body></html>
"""
@@ -145,15 +153,20 @@ def send_notification(
):
outcome = correlation["outcome"]
mail_cfg = config.get("mail", {})
smtp_cfg = config.get("smtp", {})
mail_from = mail_cfg.get("from", "ns8-backup-monitor@localhost")
mail_to = mail_cfg.get("to", [])
subject_prefix = mail_cfg.get("subject_prefix", "[NS8 Backup]")
subject = f"{subject_prefix} {OUTCOME_EMOJI[outcome]}: {outcome} - {datetime.now(timezone.utc).strftime('%Y-%m-%d')}"
subject = f"{subject_prefix} {OUTCOME_LABEL[outcome]}: {outcome} - {datetime.now(timezone.utc).strftime('%Y-%m-%d')}"
mail_to = mail_cfg.get("to", [])
if not mail_to:
log.error("No mail.to recipients configured in config.yml - cannot send notification")
return
# Resolve SMTP: NS8 Redis relay -> config.yml -> localhost:25
smtp_cfg, mail_from = resolve_smtp_config(config)
smtp_source = f"{smtp_cfg['host']}:{smtp_cfg['port']}"
text_body = _build_text(correlation, repo_status)
html_body = _build_html(correlation, repo_status)
html_body = _build_html(correlation, repo_status, smtp_source)
msg = MIMEMultipart("alternative")
msg["Subject"] = subject
@@ -180,6 +193,6 @@ def send_notification(
smtp.login(username, password)
smtp.sendmail(mail_from, mail_to, msg.as_string())
smtp.quit()
log.info(f"Notification sent: {subject} -> {mail_to}")
log.info(f"Notification sent: {subject} -> {mail_to} (via {smtp_source})")
except Exception as e:
log.error(f"Failed to send notification: {e}")
log.error(f"Failed to send notification via {smtp_source}: {e}")