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.
This commit is contained in:
+4
-2
@@ -87,11 +87,13 @@
|
||||
## Backlog (post-Phase-1 agentic)
|
||||
|
||||
### Daily activity digest (email to the team)
|
||||
*Requested 2026-06-15. **Phase A built in v0.1.0:75** (outbound SMTP send capability + admin test-email endpoint; not yet deployed). Phase B (digest content + Spark summarization + daily scheduler) remains.*
|
||||
*Requested 2026-06-15. **Phase A built + deployed** (v0.1.0:75 live on the box; send path then moved to **Gmail DWD** in v0.1.0:76, redeploy pending). Phase B (digest content + Spark summarization + daily scheduler) remains.*
|
||||
|
||||
**Decisions (locked 2026-06-15):** recipients = **all active admins**; summarization = **Spark-LLM narrative** (never Claude — un-anonymized substance stays local); granularity = **grouped by user** (→ per investor).
|
||||
|
||||
**Phase A — DONE (v0.1.0:75):** `configureDigestSmtp` Start9 action writes a per-package SMTP account to `/data/secrets/smtp/*`; `docker_entrypoint.sh` exports `SMTP_*`; `backend/smtp_send.py` (stdlib smtplib) + admin `POST /api/admin/digest/test-email` (recipient-restricted to the admin set — not an open relay). Tests: `test_smtp_send.py`, `test_smtp_endpoint.py`.
|
||||
**Send transport — DECIDED 2026-06-15: Gmail domain-wide delegation** (not SMTP). The box's existing service-account grant (which powers email capture) **includes `gmail.compose`**, which authorizes `users.messages.send` — verified by a token-mint probe **and a live `messages.send` to grant**. So the digest sends through the account the CRM already uses: **no app password, no new account, no admin change.** The narrow `gmail.send` scope is *not* granted, so the sender must request `gmail.compose`.
|
||||
|
||||
**Phase A — DONE:** (v0.1.0:75) `configureDigestSmtp` Start9 action + `docker_entrypoint.sh` `SMTP_*` export + `backend/smtp_send.py` + admin `POST /api/admin/digest/test-email` (recipient-restricted to the admin set — not an open relay) + Settings button. (v0.1.0:76, redeploy pending) `backend/email_integration/gmail_send.py` (`users.messages.send` via DWD/compose) + `backend/digest_mailer.py` (**Gmail-DWD preferred, SMTP fallback**); the endpoint + button route through it; sender = `CRM_DIGEST_SENDER` else first active admin. Tests: `test_smtp_send.py`, `test_smtp_endpoint.py`, `test_gmail_send.py`.
|
||||
|
||||
**Phase B — TODO:** daily scheduler (co-locate with `email_integration/scheduler.py`); per-user→per-investor activity query (`deleted_at IS NULL` throughout); Spark-narrative summary of captured email substance; compose + send to all admins.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user