Skip to content

Email watch is forward-only: existing matching backlog is never processed #482

Description

@Sheldenshi

Summary

An email watch (and whole-inbox triage) is a forward-only monitor. On the first (seeding) tick it baselines the cursor at the newest matching email and drafts at most that one; every older matching email already sitting in the inbox falls below the after:<watermark> clause and is never listed again. So if you set up "watch investor inbound" and several unanswered investor emails are already in your inbox, the watch processes at most the single newest one and silently skips the rest.

Accepted for now: picking up the latest matching email on seed is fine. This issue tracks adding a one-time backlog sweep so a watch can also work through mail that was already waiting when it was created.

Why this happens

skills/google/gmail-watch/scripts/detect.ts, seeding regime (~detect.ts:733-781):

  • Seeding sets cursor = newest.internalDate and drafts only window.ids[0] (the newest), recording boundary-second siblings as seen.
  • Steady-state then bounds every list with after:<cursorSec> (detect.ts:717-725), so anything older than the seed cursor can never be re-listed — newness is defined by the forward-only watermark, not by read/unread state.
  • The only acknowledgement of a backlog today is the >1000-row truncation notice, which punts: "Ask me to triage this inbox if you want me to work through them" (detect.ts:796-799).

Net: the existing-backlog case is unhandled by design — the watch only ever sees mail that arrives after it was created.

Proposed approach

Add a one-time, bounded backlog sweep as a distinct mode from the forward watch:

  • Classify + act over the existing inbox when a watch/rule is created (oldest-first, paginated across ticks, hard-capped so it can't run away).
  • Independent of the forward after: watermark — the sweep walks the current inbox once; the forward watch takes over for new mail.
  • Honest reporting: surface "swept N, acted on M" rather than silently advancing the cursor.
  • Scope is configurable (all in:inbox / unread only / last N days) and opt-in per watch.

This composes with the general criterion→action primitive (see linked issue) — a backlog sweep is what makes batch actions (e.g. "move all existing marketing to spam") meaningful, not just future mail.

Acceptance criteria

  • Creating a watch can optionally run a one-time backlog sweep over the existing inbox, bounded and paginated, that classifies + acts on every qualifying email (not just the newest).
  • The sweep cannot run away on a huge inbox (hard cap + paging), and reports what it covered and what it dropped.
  • The forward watch continues unchanged for newly-arrived mail (no double-processing across the sweep/forward boundary).

Notes / pointers

  • Relevant code: skills/google/gmail-watch/scripts/detect.ts (seeding regime, after: watermark, the truncated-window notice), src/state/email-watchers.ts, ADR docs/adr/email-watch.md.
  • Related: the general mail-rule (criterion → action) primitive issue — the sweep is the batch-action enabler for it.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions