feat([issue-2796]): extend Tribe unanswered-thread detection to Gmail via per-account sent ingestion#2821
Merged
Conversation
… via per-account sent ingestion Email sync ingested the inbox only, so a Gmail reply never reached the human-activity timeline and every stale inbound read as unanswered forever — detection was gated to iMessage/Signal. Gmail's API sync now folds recent sent mail (in:sent newer_than:14d) into the same cache the timeline ingest reads, producing message.sent events so the existing lastSentT >= inbound answered-check works per email thread. Ingestion is a per-account capability (syncConfig.ingestSent, default-on for Gmail, opt-out via a new ConfigTab toggle). The outreach detector's gate is now PER ACCOUNT, not source-wide: a source-wide gate would let one Gmail account's sent import vouch for every Gmail account (including ones with no sent evidence). buildTwoWayGate resolves trustworthy accounts from message-account config and filters inbound/sent events by accountId. Outlook/Teams stay one-way until a sent-fetch path exists.
…sent mail its own fetch budget - CRITICAL: the shared email mapper set no metadata.handle, so enrichActivityEvent couldn't resolve the sender's personId and every Gmail inbound was dropped by the outreach detector — the feature was non-functional for email. Set metadata.handle to the sender email on received mail (mirrors the iMessage mapper). - MEDIUM: inbox+sent were OR-combined under one shared cap, so a heavy sender's sent mail could evict unread inbox from the primary sync. Fetch sent as a separate list pass with its own SENT_INGEST_MAX budget (gmailSyncPasses). - LOW: pin SENT_INGEST_DAYS >= DEFAULT_WITHIN_DAYS with a test; note the send-as-alias direction-detection limitation in a comment. - Tests: mapper handle contract, sync-pass budgets, constant coupling.
…k, alias direction, email draft tone Fixes for 6 P1 findings from the codex review pass: - Sent mail no longer enters the inbox cache/triage/trim — syncGmail returns it separately and messageSync records it as timeline activity only (was polluting /api/messages/inbox and could be trimmed before its reply evidence was recorded). - The two-way gate now requires an enabled account with a RECENT sent-ingest watermark (sentIngestedAt, stamped on successful Gmail sync), so an account default-on at upgrade or with failing OAuth/sync is not trusted before reply evidence exists — closing the false-nudge window. - Direction now honors Gmail's authoritative SENT label, so a reply sent from a send-as alias is classified as sent and cancels its inbound. - Gmail outreach drafts use an email-toned template (greeting + sign-off) instead of the casual no-signoff chat template. - Raised the sent fetch budget to 100 (crowding gone now that sent is activity-only). - Tests: watermark/enabled/stale gate cases, SENT-label alias direction, template selection, messageSync mock updated for the new markSentIngested export. Deferred robustness (per-account event querying, full sent-window coverage, legacy-row backfill) filed as #2820.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Extends Tribe-outreach unanswered-thread detection (#2158) to Gmail. Previously it was gated to iMessage/Signal because email sync ingested the inbox only — a Gmail reply never produced a
message.senttimeline event, so every stale inbound read as unanswered forever.in:sent newer_than:14d) as a separate, capped list pass and records it into the human-activity timeline asmessage.sentevents. Sent mail is activity-only — it never enters the inbox cache/triage/trim, so it can't pollute/api/messages/inboxor evict real inbox mail. Gated per-account viasyncConfig.ingestSent(default-on for Gmail; opt out with a new "Reply detection" toggle in Messages > Config).buildTwoWayGateresolves which accounts are trustworthy and filters inbound/sent events byaccountId. A source-wide gate would let one Gmail account's sent import vouch for every Gmail account. An email account is trusted only when it's enabled, has an owner email, opts in, and has a recent sent-ingest watermark (sentIngestedAt) — so an account default-on at upgrade or with failing OAuth/sync is never trusted before reply evidence actually exists (closing the false-nudge window).metadata.handleto the sender's email on received mail, soenrichActivityEventcan tag the Tribe sender (without it, every Gmail inbound was dropped).SENTlabel, so a reply sent from a send-as alias is classified as sent and cancels its inbound.Recipient/draft grounding via
threadIdwas already supported and is reused unchanged.Follow-on robustness (per-account timeline event querying, full sent-window pagination, optional legacy-row backfill) is filed as #2820.
Test plan
server/services/tribeOutreach.test.js— per-account gate: chat-source-wide, Gmail default-on, opt-out, missing owner email, disabled account, missing/stale watermark, no cross-account vouching, Outlook never two-way; plus email-vs-chat template selection.server/services/messageGmailSync.test.js— inbox/sent query builders, separate sent-pass budget, and theSENT_INGEST_DAYS >= DEFAULT_WITHIN_DAYScoupling guard.server/services/humanActivity.test.js— received mail setsmetadata.handleto the sender; SENT-label direction classifies an alias-sent reply asmessage.sent.server/services/messageSync.test.js— updated for the newmarkSentIngestedexport; sent mail recorded as activity without entering the cache.Closes #2796