"""Send an email via the Gmail API using the same domain-wide delegation that powers capture and draft creation. The DWD grant on this deployment includes the `gmail.compose` scope (verified 2026-06-15: token mint + a live messages.send both succeed), and `gmail.compose` authorizes `users.messages.send`. So CRM-originated mail (the daily digest) can send through the existing service account — no SMTP account, no app password, no admin change. Sends impersonating `sender_email`, which must be a Workspace user in the delegated domain. Mirrors the REST pattern in compose.py; stdlib only. """ import base64 import email.message import json import os import urllib.error import urllib.parse import urllib.request from . import config as _cfg from . import credentials as _creds def gmail_available(): """True when DWD send is usable: integration enabled, DWD auth, key present.""" cfg = _cfg.CONFIG if not cfg.enabled or cfg.primary_auth != "dwd": return False return bool(cfg.dwd_key_path) and os.path.exists(cfg.dwd_key_path) def _build_raw(from_addr, to_addrs, subject, body): msg = email.message.EmailMessage() msg["From"] = from_addr msg["To"] = ", ".join(to_addrs) msg["Subject"] = subject or "(no subject)" msg.set_content(body or "") return base64.urlsafe_b64encode(msg.as_bytes()).decode("ascii") def send_via_gmail(sender_email, to_addrs, subject, body, conn=None): """Send one message as `sender_email` to `to_addrs` via the Gmail API (DWD). Returns {'sent_to', 'from', 'message_id'}; raises on failure.""" if isinstance(to_addrs, str): to_addrs = [to_addrs] to_addrs = [a for a in (str(x).strip() for x in to_addrs) if a] if not to_addrs: raise ValueError("no recipients") if not sender_email: raise ValueError("no sender_email (DWD impersonation needs a domain user)") provider = _creds.build_provider(lambda: conn) token = provider.access_token_for(sender_email, _creds.GMAIL_COMPOSE_SCOPE).token raw = _build_raw(sender_email, to_addrs, subject, body) url = ("https://gmail.googleapis.com/gmail/v1/users/" f"{urllib.parse.quote(sender_email)}/messages/send") req = urllib.request.Request( url, data=json.dumps({"raw": raw}).encode("utf-8"), method="POST", headers={"Authorization": f"Bearer {token}", "Content-Type": "application/json"}) try: with urllib.request.urlopen(req, timeout=20) as resp: result = json.loads(resp.read()) except urllib.error.HTTPError as e: detail = e.read().decode("utf-8", "replace")[:300] raise RuntimeError(f"Gmail API send failed (HTTP {e.code}): {detail}") return {"sent_to": to_addrs, "from": sender_email, "message_id": result.get("id")}