cocottetech/@platform/codebase/@features/ai-copilot/docs/P-inboxes.brief.md
natalie 1b719e1fd7 chore(bootstrap): initial V4 commit
Clean successor to V3 (forge: lilith/atlilith). Seeded from local Mac
working tree at ~/Code/@projects/@cocottetech/. node_modules and build
artifacts excluded via .gitignore.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-18 08:11:41 -07:00

14 KiB
Raw Blame History

P — Inboxes (unified inbound across messaging surfaces)

Goal

Quinn currently checks ≥6 inboxes by hand: iMessage, SMS, Proton email (hi@cocotte.ai-style + per-platform like myprivatedelights@protonmail.com), Tryst inbox, OF DMs, X DMs, and ad-hoc IG/TikTok/Threads. CocotteAI replaces "go check each inbox" with "ai-copilot has the queue." This brief defines the unified-inbox UX: one feed, source-labeled, with reply routing back to the originating channel and per-inbox triage policy.

Briefs C (notifications outbound) and I (audit) frame the surrounding loop; this brief is the inbound counterpart to C. Brief O — N4 enumerates the channels; this brief is their UX.

Designer skim

  • Headline UX: One feed, many sources. Source-labeled per-message ("via Tryst", "via Proton: myprivatedelights@…"), reply routes back to origin channel transparently. Threaded by prospect_id once prospect-resolver (P4) catches up.
  • Sections (5): P1 inbox sources (channels + N mailboxes) · P2 unified feed · P3 per-source triage posture · P4 inbox health + degradation · P5 threaded-by-prospect view.
  • Blocking Qs: OPEN-DECISIONS.md → P-Q1 reply-as-different-channel confirm UX.

In-the-wild copy

Source label on a message row (plain — V5 inbox rule: source clarity wins):

via Tryst · 11 min ago

Source label, per-mailbox Proton (plain):

via Proton: myprivatedelights@protonmail.com · 2 hr ago

Reply-as-different-channel confirm (plain):

They emailed. You're replying via iMessage — that uses a different identifier they may not have. Continue / switch back to email.

Inbox degraded (plain):

Tryst inbox stalled — last fetched 47 min ago. Showing what we have. Trying again.

Triage receipt, per-source policy (hearth — receipt, not decision):

Triage tended to the Tryst inbox while you slept. Six warm, nine cold (auto-replied), two flagged for your call.

Constraints

  • One feed, many sources. No "switch to Tryst inbox" affordance — the unified feed IS the inbox.
  • Reply routes back to source. A message from Tryst is replied to on Tryst (via bookings-tryst adapter); a Proton email is replied via SMTP through mail-sync:4444; an iMessage replies via mac-sync:3100. The channel never leaks into Quinn's decision-making but the system tracks it exactly.
  • Source-labeled, not source-grouped. Per-message badge ("via Tryst", "via Proton: myprivatedelights@…") — never a top-level "Tryst tab".
  • Per-channel cadence + auto-policy is opt-in, NOT global. Quinn may want triage to auto-reply on iMessage (5-check chain ported from v2 per brief J), draft-only on email, hands-off on directory inboxes initially.
  • Email account multiplicity is real. Quinn has ≥4 mailboxes in v2 user data: Proton main, per-platform Proton aliases (e.g. myprivatedelights@protonmail.com), and at least one Gmail per [registrations.md tracker contact info]. Each is a distinct inbox to ingest.
  • SMS ≠ iMessage. iMessage is Apple-protocol via mac-sync; SMS is carrier-protocol. Same prospect may send via both; same number, different channel. Treat as separate inbound surfaces; cross-link at the prospect_id resolution step (per brief I §counter-actions).

P1 — Inbox sources (the actual channel roster)

Per brief O — N4 + extended for this brief. Each row is one ingest pipeline:

Direct-messaging channels (channel_kind)

ID Display Ingest mechanism Outbound reply Notes
imessage iMessage mac-sync:3100 send-queue + LISTEN on macsync.messages mac-sync send-queue Anchor channel. v2 engine is built around this.
sms SMS Same mac-sync pipeline (Mac → carrier) Same Distinct from iMessage at the protocol level. v2 conflates them; v4 separates.
signal Signal Future MCP (no v0 listener) Manual or future bot P3+.
telegram Telegram Bot API (future) Bot API P3+.
discord Discord Bot API (future, community-server use) Bot API Light surface; lowest priority.

Email inboxes (mailbox_id — multiple per user)

Email is distinguished by mailbox, not just by service. Quinn may have N Proton inboxes (one per directory's verification email) + Gmail + others. Each gets its own row in mailboxes table; engagement-ingestor polls each.

Pattern Example Use
Personal anchor inbox quinn@cocotte.maison (canonical) Anchor; brand-tier.
Per-platform verification inbox myprivatedelights@protonmail.com Per-directory KYC + transactional emails.
Per-brand inbox bookings@tqftw.com, hi@adult-therapy-tours.com Brand-site contact forms route here.
Legacy / personal Gmail, iCloud Read-only triage; ignored for outbound.

All Proton inboxes wrap through mail-sync:4444 (one process per Proton credential — multi-credential ingestion is a mail-sync extension flagged for engineering).

Platform-native DMs (per surface, via bookings-* / content-* adapter fetchEngagement calls)

Inbound DMs from a surface are normalized into engagement_events rows tagged with surface + kind='dm' (per migration 0001 engagement_kind ENUM). UX-wise they land in the same unified feed; backend-wise they come through the per-surface adapter, not a separate channel pipeline.

Surface (from brief O) DM ingest? Notes
tryst, ts4rent, seeking, privatedelights, tsescorts, adultsearch, adultlook, eros, eroticmonkey, skipthegames, megapersonals, ts_live, slixa yes — via web-session adapter Per-directory inbox endpoint scraped on schedule. Failure surfaces per brief M.
onlyfans, fansly yes — via web-session adapter Higher cadence (every ~10 min) due to PPV-time-sensitivity.
x, instagram, tiktok, threads, bluesky yes — via real API or web-session
reddit yes — DMs + comment replies Reddit's DM API is real.
youtube, twitch, facebook low priority Either Quinn doesn't get DMs here or volume is negligible.

P2 — Unified inbox UX (the daily-driver feed)

Default view (in chat, under chat-home brief A)

  • Inbox tile at the top of chat-home, below ambient strip (brief L §L2c). One line: "Inbox: 4 waiting" + recent-sender preview.
  • Tap → opens the unified inbox drawer (new sibling drawer to those enumerated in brief B).
  • Quinn doesn't ever have to open the inbox; ai-copilot surfaces the highest-stakes items as approval cards directly in chat (the existing brief A engagement-card flow). The drawer is for: bulk triage, looking up old threads, searching across sources.

Unified inbox drawer (new — extends brief B)

  • Top filter bar: source-multiselect (default: all on), kind-filter (DM / comment / mention / email / SMS), date range, "show blocked-sender messages" toggle.
  • Threaded by prospect when prospect_id is resolved across surfaces (per brief I + brief J prospect-resolver worker). When the same fan emails AND iMessages Quinn within an hour, they appear as one thread with both messages under a single header.
  • Per-message source chip: Tryst, Proton: myprivatedelights@…, iMessage, OF DM, etc. Tappable → opens that source's settings (cadence, auth state, last successful poll).
  • Per-thread quick actions: Reply (drafts via triage per brief L §L3c), Mark resolved, Block sender (per brief K §K1), Escalate.
  • Reply composer: when Quinn replies, the composer shows where the reply is going: "Replying via Proton (myprivatedelights@…)" or "Replying via Tryst". One-tap reply uses the same source; "Reply somewhere else" affordance lets Quinn pick a different channel (e.g. fan asked via Tryst, Quinn wants to escalate to iMessage).

Source labeling in approval cards (brief A)

  • Existing engagement-card variant adds a via {source} line under the prospect identifier.
  • The card's "Reply" action defaults to the source channel. Quinn can long-press to pick a different channel for the reply.

P3 — Per-inbox triage policy

Per brief L §L3c, triage runs the 5-check eligibility chain on every inbound. Per-source posture overrides the default:

Source Default triage posture Default auto-reply Notes
imessage (warm contacts) Full 5-check chain; auto-reply for warm tier yes for Quinn-line + intro Ported verbatim from v2 engine.
imessage (cold inbound) 5-check + tier classification draft-only for cold Lower trust until Quinn graduates.
sms Same as iMessage yes for warm Treated as iMessage-equivalent at the policy layer.
Proton main (quinn@cocotte.maison) Read-only ingest + classify draft-only High-value; Quinn approves every reply.
Proton per-platform aliases Read-only ingest + classify draft-only Mostly transactional (KYC, verifications); rarely needs reply.
Gmail / legacy inboxes Read-only ingest, no outbound n/a — never reply through these Existence acknowledged; outbound disabled.
Surface DMs (tryst, OF, etc.) Per-surface specialist drafts; triage does eligibility only per-surface (OF content-onlyfans auto-sends ≥0.85 confidence; directories draft-only initially) Specialist-axis decision in brief L.
signal / telegram / discord Read-only ingest until MCP ships n/a P3+.

Per-source settings live alongside the brief C notification matrix in /settings/inbox.

P4 — Inbox health (degradation + observability)

Per brief M, inbox ingest failures are NOT silent:

  • Per-source health badge in inbox drawer header (green / yellow / red, mirrors fleet status strip).
  • "Haven't seen new mail from X in N hours" banner when a polling source goes quiet (could mean Quinn really has no new mail, OR the adapter is broken — surface both possibilities, ask Quinn to verify if uncertain).
  • Daily digest entry (brief I §I1) includes: "Pulled N messages from 6 sources today" + per-source breakdown.
  • Inbox audit row: every fetch produces an agent_actions row with action_type='fetch_inbox', target_kind='mailbox'|'channel'|'surface', success/failure recorded.

P5 — Search across the unified inbox

Quinn searches by:

  • Sender name / identifier (handles cross-surface fuzzy match via prospect_id)
  • Free-text content
  • Date range
  • Source
  • Has-draft / replied / unread / escalated state

Search runs against engagement_events.payload_json + full message bodies stored in engagement_events.body_text (TBD migration — needs a tsvector index for performance).

States to design

  • Inbox tile in chat-home — idle (0 waiting), 19 waiting (badge), 10+ waiting (overflow badge), red-dot (urgent item per K3 stakes), source-down (one or more sources degraded).
  • Unified inbox drawer — empty, dense with mixed sources, single-source filter active, threaded view (multi-message-same-prospect), search results, blocked-sender expander.
  • Reply composer — single-source default, "reply somewhere else" picker open, draft-from-triage ready, error (channel offline → fallback suggestion).
  • Per-source settings/settings/inbox page: one row per ingest source with status + cadence + posture toggles.
  • Inbox-degradation banner — one source down, multiple sources down.
  • First-run — onboarding asks Quinn to connect inboxes (Proton creds, mac-sync paired, surface logins). UX echoes brief D persona-seed flow.

Out of scope

  • Public anonymous email formsbookings@tqftw.com web form on a brand site is a posting surface, not an inbox in the messaging sense. Brand sites POST to platform.api which writes to engagement_events; inbox UX shows them like any other inbound.
  • Sending bulk email — newsletters and digests go via notifier (brief C) and content-newsletter (brief L), not the inbox.
  • Calendar/meeting invites parsed from email — future enhancement; defer until a user need lands.
  • Schema migration for mailboxes + channel_kind ENUM — engineering follow-up, not design (flagged in P1).

Open questions

  • Multi-mailbox auth on Protonmail-sync currently wraps one Proton Bridge credential. Quinn needs ≥4 Proton inboxes. Does mail-sync run one process per credential (cleanest), or extend to multi-credential (more code, one process)? Engineering call.
  • Cross-channel prospect dedup latency — when a fan emails AND iMessages within 30s, does the unified feed wait to coalesce them, or show both immediately and re-thread when prospect-resolver catches up? Lean: show immediately with a small "resolving…" pip, then merge.
  • Reply-as-different-channel UX — fan emails, Quinn replies via iMessage. Should the system prompt her to confirm ("this fan emailed you — replying via iMessage exposes a different identifier"), or just do it? Lean: silent-but-logged for warm/known prospects; confirm for unknown.
  • Surface-DM cadence per-directory — OF every 10min vs directories every hour vs others. Default policy here or per-surface specialist config? Lean: per-specialist config; bookings-directories carries default cadences in personas.facets[directory_id].inbox_poll_seconds.
  • iMessage vs SMS in v2 — v2 collapses both as "iMessage". Does v4's separation create migration friction with the v2 prospect history? Defer to brief J's P0 verdict (a) — freeze v2 history, fresh agent_actions start.
  • brief A — inbox tile + engagement-card source labeling lives in chat-home.
  • brief B — unified inbox drawer is a new sibling to B1B6.
  • brief C — outbound-notifications counterpart; inbox UX defines inbound, C defines outbound.
  • brief I — every inbox fetch + reply lands in audit; prospect dedup powers the threaded view.
  • brief Jengagement-ingestor worker + 5-check eligibility chain are the ported v2 plumbing this brief consumes.
  • brief K §K1 — prospect blocklist suppresses messages here (collapsed under "blocked messages today — review?" card).
  • brief L §L3ctriage specialist owns inbound classification + draft replies.
  • brief M — inbox degradation behaviors.
  • brief O — N4 — channel + brand-site surfaces feeding the inbox.