Skip to content

Fix Gmail API bugs, speed up bulk ops, and add sync/archive/rules features#1

Open
chrisdjbaker wants to merge 4 commits into
Mohit-Alive:mainfrom
chrisdjbaker:main
Open

Fix Gmail API bugs, speed up bulk ops, and add sync/archive/rules features#1
chrisdjbaker wants to merge 4 commits into
Mohit-Alive:mainfrom
chrisdjbaker:main

Conversation

@chrisdjbaker

Copy link
Copy Markdown

Summary

This PR fixes several bugs in the Gmail API layer, speeds up the bulk
operations, makes auto-unsubscribe land for many more senders, and adds a set
of features (incremental sync, archive, storage-size insights, per-sender
preview, undo, and saved auto-clean rules). The build, test suite, and linter
are all green, and a CI workflow is restored to keep them that way.

A full write-up lives in CHANGELOG.md.

Bug fixes

  • Bounded, centralised Gmail retries (gmailApi.ts). One transport with
    exponential backoff and a hard retry cap, replacing ad-hoc retry loops that:
    retried 429/403 forever (could hang the UI); passed swapped arguments
    on the unsubscribe-header retry; and retried permanent 403s as transient.
  • Zero-result crash in fetchMessageIds — Gmail omits messages on an
    empty query, which threw and aborted the trash/unsubscribe flow. Now guarded.
  • List-Unsubscribe parsing stripped angle brackets using the untrimmed
    length, mangling values with surrounding whitespace.
  • Fetch progress overwrote other accounts' progress and drifted past 100%;
    now merged per-account and based on messages actually processed.
  • Content script DOM lookups are null-guarded, and the account email is
    detected by pattern rather than a fixed title split position.

Performance

  • Bulk trash via batchModify (up to 1000 ids/request) instead of one
    request per message.
  • Sender scan via the multipart batch endpoint (100 sub-requests/call)
    instead of 40 individual GETs.

Auto-unsubscribe

  • RFC 8058 one-click POST (List-Unsubscribe-Post) is now the preferred
    path, so more senders unsubscribe with no email and no manual click.
  • Body link-scraping walks the full MIME tree, handling nested
    multipart/alternative bodies that were previously missed.

New features

  • Incremental sync — refresh applies only added/deleted mail via
    history.list, with a full-resync fallback when the cursor expires.
  • Storage-size insights + Archive — per-sender size (captured free during
    the scan), sort-by-size, and an Archive action (remove from Inbox).
  • Per-sender preview + Undo — preview a sender's recent subjects; undo the
    last trash batch.
  • Auto-clean rules — trash/archive future mail from a sender, implemented as
    Gmail server-side filters (no background polling).
  • Search, sort & select-all in the sender list.
  • Richer confirmations showing storage freed.

Testing & compatibility

  • Jest suite restored and expanded to 49 tests over the new and fixed logic.
  • npm run build, npm test, and npx eslint src all pass.
  • All new Gmail calls use the existing OAuth scopes (gmail.modify,
    gmail.settings.basic) — no new consent screen. Adds the unlimitedStorage
    permission for the incremental-sync message index.

I'm happy to split this into smaller PRs or adjust anything if you'd prefer to
take it in pieces.

Correctness fixes:
- Centralize Gmail API calls in gmailApi.ts with bounded exponential
  backoff. Replaces ad-hoc retry loops that (a) retried 429/403 forever
  with no cap, (b) passed swapped args on the unsubscribe-header retry,
  and (c) retried permanent 403s as if transient.
- fetchMessageIdsPage no longer crashes when Gmail omits `messages` on a
  zero-result query.
- parseListUnsubscribeHeader strips angle brackets correctly when the
  value has surrounding whitespace.
- fetchProgress is merged per-account instead of clobbering other
  accounts, and reflects messages actually processed (no float drift).

Auto-unsubscribe:
- Implement RFC 8058 one-click POST (List-Unsubscribe-Post) as the
  preferred automatic path; previously dead code.
- Recurse the full MIME tree when scraping body unsubscribe links, so
  nested multipart/alternative bodies are handled.

Performance:
- Trash via batchModify (up to 1000 ids/request) instead of one request
  per message.
- Fetch sender metadata via Gmail's multipart batch endpoint (100
  sub-requests/call) instead of 40 individual GETs.

Hardening:
- Guard content-script DOM lookups; detect the account email by pattern
  instead of a fixed title split position.
- Remove the placeholder uninstall-survey URL.
- Restore a Jest suite (32 tests) covering the above.
Feature: incremental sync
- New syncSenders.ts uses Gmail history.list to apply only added/deleted
  messages since the last refresh, with a full-resync fallback when the
  history cursor has expired (404). fetchAllSenders now records the
  history cursor and a message->sender index (senderStore.ts) so syncs
  can decrement counts/size on deletion.

Feature: storage size + archive
- Capture each message's sizeEstimate (free, same response) and surface
  per-sender size in the UI; sort by size.
- New Archive action (remove INBOX, no trash) via the shared batchModify
  path in modifySenders.ts.

Feature: drill-down + undo
- getSenderPreview shows a sender's recent subjects/snippets in a modal.
- Trashing records the affected ids; Undo restores them (remove TRASH,
  restore INBOX) from the success modal.

Feature: saved auto-clean rules
- Backed by Gmail server-side filters (create/list/delete) so Gmail
  applies them automatically; managed from a new Rules modal. blockSender
  now routes through the same rule machinery.

UI: search box, sort (emails/size/name), select-all-visible, and richer
confirmations that show storage freed.

CI: restore a GitHub Actions workflow running build, tests, and eslint.

Storage: add the unlimitedStorage permission for the message index.
Tests: 49 passing (store conversions, modify/undo, rules, history delta,
size formatting, aggregation).
The login screen previously only displayed instructions and relied on
silent token acquisition, so a first-time user could never trigger the
OAuth consent grant. Wire the login page to signInWithGoogle (interactive)
using the account detected from the active Gmail tab, with busy and error
states.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant