Files
ten31-database/docs/guides/email.md
T
Keysat 661ad35ee5 Handoff: document the digest send path; trim Current state
- docs/guides/email.md: new "Outbound mail — the daily digest" section (Gmail-DWD
  primary → SMTP fallback; gmail.compose send capability; the internal-digest
  exemption from the agents-draft rule).
- AGENTS.md: add digest env names (CRM_DIGEST_SENDER, SMTP_*); consolidate the
  v75/v76 deploy bullets into one current bullet; drop finished v74 narrative.
2026-06-15 20:49:34 -05:00

2.7 KiB

paths
paths
backend/email_integration/**

Email capture & drafts (Gmail)

Read this before editing Gmail capture or draft creation.

What it does

  • backend/email_integration/ captures Gmail via domain-wide delegation (credentials.py, matcher.py, parser.py, db.py, sync.py, scheduler.py, routes.py) and creates Tier-B in-thread drafts (compose.py). It has its own migrations/.
  • Captured email becomes CRM activity through a propose → approve flow — nothing lands on a contact record until a human approves the proposal.

Hard rule

  • Agents draft; humans send. Never let an agent send email, post, or contact an LP autonomously. Tier-B compose.py only creates a Gmail draft for human review.

Outbound mail — the daily digest (internal; exempt from "agents draft")

The CRM sends an internal daily activity digest to the fund's own admins. This is the ONE automated send path, and it does not violate the hard rule above: that rule governs outward LP/prospect contact. An internal ops email to the team's own inboxes is a different category. Never extend this path to send to LPs/prospects.

  • Transport selector: backend/digest_mailer.py (top-level, not in this package) — send_digest(conn, to_addrs, subject, body) picks Gmail-DWD (preferred) → SMTP (fallback). DWD-impersonation sender = CRM_DIGEST_SENDER env, else the first active admin.
  • Gmail-DWD path: gmail_send.py (this package) — reuses credentials.py's DWDCredentialProvider with the gmail.compose scope to call users.messages.send (REST, mirrors compose.py; body is {raw} not the draft's {message:{raw}}). The deployment's DWD grant includes gmail.compose (which authorizes send) but not the narrow gmail.send — so request gmail.compose. Verified live 2026-06-15 (token mint + a real messages.send).
  • SMTP fallback: backend/smtp_send.py (top-level) — stdlib smtplib reading SMTP_* env, populated on the box by the Configure Digest SMTP Start9 action (writes /data/secrets/smtp/*; entrypoint exports SMTP_*). A dedicated per-package account, independent of any StartOS system-wide SMTP.
  • The admin POST /api/admin/digest/test-email restricts recipients to the active-admin set (not an open relay), and logs send failures rather than echoing them (an auth error can carry a token/credential). Digest content generation (Phase B) runs on Spark, never Claude — the digest is deliberately un-anonymized.

Known gap

  • Tier-B drafts currently reply to the LP only; reply-all is the next change (see AGENTS.md → Current state).

See also docs/gmail-enablement-runbook.md.