Files
ten31-database/backend/digest_mailer.py
T
Keysat 47dfd110a0 Add Gmail-DWD send path for the digest mailer (v0.1.0:76)
The box's existing service-account domain-wide-delegation grant already includes
gmail.compose, which authorizes users.messages.send — verified 2026-06-15 by a
token-mint probe and a live messages.send to grant. So CRM-originated mail can
send through the account that already powers email capture: no SMTP account, no
app password, no admin change.

- backend/email_integration/gmail_send.py: send_via_gmail() impersonates a
  domain user and POSTs users.messages.send (reuses credentials.py + the compose
  scope; mirrors compose.py's REST pattern).
- backend/digest_mailer.py: send_digest() prefers Gmail DWD when enabled, falls
  back to smtp_send otherwise. Sender = CRM_DIGEST_SENDER else first active admin.
- server.py: the admin test endpoint now routes through digest_mailer (so the
  Settings button sends via DWD on the box with zero SMTP config). Recipient
  restriction to the admin set and no-leak error handling preserved.
- test_gmail_send.py: build/send + transport routing (provider + urlopen faked).
  19/19 backend green; s9pk typechecks.

SMTP (v75) stays as the fallback transport. Send-path decision + scope finding
recorded in ROADMAP.md and AGENTS.md.
2026-06-15 20:17:27 -05:00

65 lines
2.2 KiB
Python

"""Transport selection for CRM-originated email (daily digest, admin test sends).
Prefers Gmail-over-DWD — it reuses the service account that already powers email
capture (the grant includes gmail.compose, which can send), so there's no extra
credential to manage — and falls back to SMTP (`smtp_send`) when DWD isn't
available. One entry point so the digest and the admin test endpoint share the
same routing. Stdlib only.
"""
import os
class NoTransport(Exception):
"""Neither Gmail DWD nor SMTP is configured."""
def transport():
"""Return the active transport: 'gmail-dwd', 'smtp', or None."""
try:
from email_integration import gmail_send
if gmail_send.gmail_available():
return "gmail-dwd"
except Exception:
pass
try:
import smtp_send
if smtp_send.smtp_configured():
return "smtp"
except Exception:
pass
return None
def default_sender(conn):
"""Domain user to send as for the DWD path. `CRM_DIGEST_SENDER` if set, else
the first active admin's email."""
s = os.environ.get("CRM_DIGEST_SENDER", "").strip()
if s:
return s
if conn is None:
return None
row = conn.execute(
"SELECT email FROM users WHERE role='admin' AND is_active=1 "
"AND email IS NOT NULL AND TRIM(email)!='' ORDER BY created_at LIMIT 1"
).fetchone()
return row["email"].strip() if row and row["email"] else None
def send_digest(conn, to_addrs, subject, body, sender_email=None):
"""Send via the active transport. Returns the transport's result dict with a
'transport' key added; raises NoTransport if neither is configured."""
t = transport()
if t == "gmail-dwd":
from email_integration import gmail_send
sender = sender_email or default_sender(conn)
result = gmail_send.send_via_gmail(sender, to_addrs, subject, body, conn=conn)
result["transport"] = "gmail-dwd"
return result
if t == "smtp":
import smtp_send
result = smtp_send.send_email(to_addrs, subject, body)
result["transport"] = "smtp"
return result
raise NoTransport("No email transport configured: enable Gmail (DWD) or set "
"SMTP via the 'Configure Digest SMTP' action.")