Apply review polish to the digest send path (post-v0.1.0:76)
Non-blocking items from the v76 reviewer pass. No redeploy needed — the box runs
v76 and its happy path is unaffected; these ride the next build:
- digest_mailer.send_digest: when Gmail is enabled but no sender resolves
(CRM_DIGEST_SENDER unset and no admin email), raise NoTransport so the caller
returns a clear 400 instead of a generic 502.
- gmail_send.send_via_gmail: wrap OSError/URLError (timeout/DNS) as a RuntimeError
("Gmail API unreachable: ...") to match the HTTPError handling; include the
sender in the HTTPError message for debuggability.
- credentials.py: correct the now-stale GMAIL_COMPOSE_SCOPE comment (the digest
mailer sends with this scope; only outreach drafts never send).
- test_gmail_send.py: add the HTTPError->RuntimeError branch, default_sender DB
fallback (+None case + env override), and the send_digest SMTP-tag path.
19/19 backend tests green.
This commit is contained in:
@@ -32,8 +32,10 @@ from . import errors
|
||||
|
||||
|
||||
GMAIL_READONLY_SCOPE = "https://www.googleapis.com/auth/gmail.readonly"
|
||||
# Drafts scope (authorized in Workspace DWD). We only ever CREATE drafts with it; the
|
||||
# human sends from Gmail. (Google bundles send into this scope, but our code never sends.)
|
||||
# Compose scope (authorized in Workspace DWD). Two consumers: outreach (compose.py)
|
||||
# only CREATES drafts — the human sends from Gmail; the daily-digest mailer
|
||||
# (gmail_send.py) uses this same scope to SEND, since gmail.compose authorizes
|
||||
# users.messages.send. (The narrow gmail.send scope is NOT on the DWD grant.)
|
||||
GMAIL_COMPOSE_SCOPE = "https://www.googleapis.com/auth/gmail.compose"
|
||||
GOOGLE_TOKEN_URL = "https://oauth2.googleapis.com/token"
|
||||
|
||||
|
||||
@@ -48,6 +48,8 @@ def send_via_gmail(sender_email, to_addrs, subject, body, conn=None):
|
||||
if not sender_email:
|
||||
raise ValueError("no sender_email (DWD impersonation needs a domain user)")
|
||||
|
||||
# conn is only consulted by the OAuth provider path; the DWD provider (the one
|
||||
# used here) reads the service-account key from disk and ignores it.
|
||||
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)
|
||||
@@ -61,5 +63,7 @@ def send_via_gmail(sender_email, to_addrs, subject, body, conn=None):
|
||||
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}")
|
||||
raise RuntimeError(f"Gmail API send failed for {sender_email} (HTTP {e.code}): {detail}")
|
||||
except OSError as e: # URLError/timeout/DNS (URLError subclasses OSError)
|
||||
raise RuntimeError(f"Gmail API unreachable: {e}")
|
||||
return {"sent_to": to_addrs, "from": sender_email, "message_id": result.get("id")}
|
||||
|
||||
Reference in New Issue
Block a user