diff --git a/docs/guides/email.md b/docs/guides/email.md index 6bba6fc..b131eea 100644 --- a/docs/guides/email.md +++ b/docs/guides/email.md @@ -80,6 +80,27 @@ different category. **Never extend this path to send to LPs/prospects.** soft-delete, inbound dedup, two-section compose, fallback, policy resolver, scheduler guards — stubbed LLM + transport). +## Email-activity panel (Communications tab) — admin-only + +The **Communications** tab (frontend) is the admin-only search over captured Gmail. The +classic manual "Log Communication" form was retired (the Fundraising Grid context menu is +the manual-log path). Backed by **`GET /api/email/activity`** (`routes.py:_h_activity`, +`require_admin` server-side) → **`db.query_email_activity(conn, ...)`** (the pure, tested +query). Filters: `investor_id`, `account_id` (mailbox), `direction` (`inbound`/`outbound`), +`q` (free-text over subject/snippet/from). Non-obvious semantics to preserve: + +- **Soft-delete lives on the per-mailbox sighting**, not the email: `emails` has no + `deleted_at`. An email is "live" iff it has a sighting with `email_account_messages. + deleted_at IS NULL` — the query gates on `EXISTS(... deleted_at IS NULL)`. (Investor + links are email-level and carry no `deleted_at`, so they need no separate filter.) +- **Direction is decided at the email level** — outbound if `from_email` is one of our + `email_accounts` addresses, else inbound — mirroring `digest_builder._own_addresses`. +- **Graveyard investors** are hidden from the filter *dropdown* (CRM-wide `graveyard = 0`), + but their captured email still shows in the list and stays findable by free-text search — + it's an audit surface, so history is never hidden, only the picker is. + +Tests: `backend/email_integration/test_email_activity_panel.py`. + ## Known gap - Tier-B drafts currently reply to the **LP only**; reply-all is the next change (see AGENTS.md → Current state).