""" core/mailer.py — E-Mail via Microsoft Graph (client-credentials) ================================================================= Sendet HTML-Mails über die Graph-App-Registrierung in `[graph]` (tenant_id/client_id/client_secret). Absender = `[graph] sender` oder Default mailagent@hocks.eu. Ersetzt den PowerShell-Umweg (send_daily_report.ps1) durch reines Python — so kann die Engine den Tagesreport selbst verschicken. """ from __future__ import annotations from core.logger import get_logger log = get_logger("mail") _TOKEN_URL = "https://login.microsoftonline.com/{tenant}/oauth2/v2.0/token" _SENDMAIL = "https://graph.microsoft.com/v1.0/users/{sender}/sendMail" _DEFAULT_SENDER = "mailagent@hocks.eu" def send_graph_mail(graph_cfg, subject: str, html: str, to_addr: str) -> bool: """True bei Erfolg. `to_addr` darf mehrere Adressen kommagetrennt enthalten. Fail-safe: loggt + gibt False zurück, wirft nie.""" import requests g = graph_cfg or {} tenant = (g.get("tenant_id") or "").strip() cid = (g.get("client_id") or "").strip() secret = (g.get("client_secret") or "").strip() sender = (g.get("sender") or _DEFAULT_SENDER).strip() recipients = [{"emailAddress": {"address": a.strip()}} for a in (to_addr or "").split(",") if a.strip()] if not (tenant and cid and secret) or not recipients: log.warning("Graph-Config/Empfänger unvollständig — keine E-Mail") return False try: tok = requests.post( _TOKEN_URL.format(tenant=tenant), data={"client_id": cid, "client_secret": secret, "scope": "https://graph.microsoft.com/.default", "grant_type": "client_credentials"}, timeout=30) tok.raise_for_status() access = tok.json().get("access_token") if not access: log.warning("Graph: kein access_token"); return False r = requests.post( _SENDMAIL.format(sender=sender), headers={"Authorization": "Bearer " + access}, json={"message": { "subject": subject, "body": {"contentType": "HTML", "content": html}, "toRecipients": recipients, "from": {"emailAddress": {"address": sender}}}, "saveToSentItems": True}, timeout=30) if r.status_code in (200, 202): log.info(f"E-Mail an {to_addr}: {subject}") return True log.warning(f"Graph sendMail {r.status_code}: {r.text[:200]}") return False except Exception as e: log.warning(f"send_graph_mail: {e}") return False