"""Notification service — email (SMTP) and webhook dispatch.""" import json import smtplib from email.mime.text import MIMEText from datetime import datetime import httpx from sqlalchemy import select from sqlalchemy.ext.asyncio import AsyncSession from app.config import settings from app.models.models import Incident, NotificationLog, Subscriber async def send_email_notification( to_email: str, subject: str, body: str, ) -> bool: """Send an email notification via SMTP. Returns True if successful.""" if not settings.smtp_host: return False msg = MIMEText(body, "html") msg["Subject"] = subject msg["From"] = settings.smtp_from msg["To"] = to_email try: with smtplib.SMTP(settings.smtp_host, settings.smtp_port) as server: if settings.smtp_user: server.starttls() server.login(settings.smtp_user, settings.smtp_pass) server.send_message(msg) return True except Exception: return False async def send_webhook_notification( payload: dict, ) -> bool: """Send a webhook POST notification. Returns True if successful.""" if not settings.webhook_notify_url: return False try: async with httpx.AsyncClient() as client: response = await client.post( settings.webhook_notify_url, json=payload, timeout=10.0, ) return response.status_code < 400 except Exception: return False async def notify_subscribers( incident: Incident, db: AsyncSession, ) -> int: """Notify all confirmed subscribers about an incident update. Returns count notified.""" result = await db.execute( select(Subscriber).where(Subscriber.is_confirmed == True) # noqa: E712 ) subscribers = result.scalars().all() notified = 0 subject = f"[{incident.severity.upper()}] {incident.title}" for subscriber in subscribers: # Email notification email_sent = await send_email_notification( to_email=subscriber.email, subject=subject, body=f"
{incident.title}
Status: {incident.status}
", ) if email_sent: log = NotificationLog( incident_id=incident.id, subscriber_id=subscriber.id, channel="email", status="sent", ) db.add(log) notified += 1 # Webhook notification webhook_sent = await send_webhook_notification( payload={ "incident_id": incident.id, "title": incident.title, "status": incident.status, "severity": incident.severity, "started_at": incident.started_at.isoformat() if incident.started_at else None, } ) if webhook_sent: log = NotificationLog( incident_id=incident.id, subscriber_id=subscriber.id, channel="webhook", status="sent", ) db.add(log) await db.flush() return notified