fix(gmail): drop an expired history cursor instead of re-saving it every poll

Merged
WA
6d ago
+219-18

Summary

  • Gmail answers history.list with 404 once the stored historyId ages out of its retained history (typically at least a week); the sync guide says to full-sync.
  • The fallback searched the inbox, then took the mailbox profile's historyId as the new cursor. A quiet mailbox reports the expired id itself, so it was saved again and every poll repeated the 404 + search + profile calls and an ERROR log.
  • The resync now adopts the newest searched message's historyId, else the profile's, but never the rejected one; with no replacement the cursor is dropped and the webhook polls through search (the same mode a new webhook starts in) until matching mail supplies a cursor.
  • pollWebhook now persists the cursor fetchNewEmails returns instead of falling back to the old one, so a cleared cursor is actually removed.
  • Logging: the expected 404/400 logs at info; other history errors (403/429/5xx) at warn, since the poll either falls back to search or the caller logs the thrown error. A transient 5xx now keeps the still-valid cursor instead of jumping to the profile's.

Type of Change

  • Bug fix

Testing

  • New lib/webhooks/polling/gmail.test.ts: quiet mailbox drops the expired cursor; full sync finding mail resumes from that message; a moved-on mailbox resumes from its profile cursor. The first case fails on the previous code (re-saves the expired id) and passes with the fix.
  • lib/webhooks/polling/ suite: 56 passed
  • biome, check:test-patterns, check:comment-hygiene

Checklist

  • Code follows project style guidelines
  • Self-reviewed my changes
  • Tests added/updated and passing (new tests pass the test-audit authoring gate)
  • No new warnings introduced
  • I confirm that I have read and agree to the terms outlined in the Contributor License Agreement (CLA)