Repository navigation
fix: resume receiving listener pagination after interrupted polls - #400
Merged
felipefreitag merged 4 commits intoOct 15, 2026
Merged
felipefreitag merged 4 commits into
felipefreitag merged 4 commits into
Conversation
A poll stopped only at the saved range boundary. If that email left the list, the next poll showed older inbox history as new. Stop at the boundary or at the first seen ID, as main did before. Also remove two comments that restate the code.
The API returns not_found for an unknown after cursor. The listener retried the same cursor on each poll and exited after five failures. Drop the cursor on not_found so the next poll starts at the newest email.
…listener-pagination
felipefreitag
approved these changes
Oct 15, 2026
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.
resend emails receiving listencan stop reporting new emails after a burst larger than 500 emails or an error on a later page. The email records remain in Resend, but later listener polls do not report the unfinished part of the range.This change retains pagination progress between polls. It also preserves the output from successful pages when a later page fails.
What failed
The listener starts with one existing email as its boundary. Each poll then reads up to five pages of 100 emails. Before this change, every poll starts from the latest page, including the poll after a page limit or a page error.
The listener adds partial results to its seen-ID cache. On the next poll, it finds those IDs at the head and stops. It never returns to the unfinished pages.
For example, with 501 new emails, the first poll reports 500. The next poll starts at the head, finds an email that it has already reported, and stops. The oldest new email remains unreported. With a temporary error on page two of a 101-email range, the first 100 emails are reported but the last email is not recovered.
A separate part of the same recovery path loses accumulated results if processing a later page throws. The outer poll catches that error, but it no longer has the results from the earlier successful pages.
What changed
aftercursor when a poll reaches the page limit.has_more: true.The separate boundary is needed because the cache holds at most 10,000 IDs. A larger burst can remove the startup boundary from that cache. Keeping the boundary separately prevents old startup history from being reported when that happens.
The five-page limit per poll, the 10,000-ID cache, rate-limit retries and existing output format remain in place. Retry attempts follow the existing retry policy and do not change the page limit.
How I tested it
I updated local main from upstream and checked the current listener history and related PRs before changing the code. The base is
a724718046efc0bb32faa7625765ffaec3a16cf4. I installed dependencies with the frozen lockfile on Node 24.19.0 and pnpm 11.8.0. The installed Resend SDK is 6.31.0.The unchanged baseline passed 1,160 tests, with one skipped test. I then added regression tests before changing production code. Eight checks failed and three controls passed. The failures were output assertions, not setup errors.
The regression tests run the actual listener command through the installed SDK. HTTP responses are replaced with controlled fixtures. The fixtures return newest-first lists with exclusive
aftercursors. They reject unknown cursors. The tests do not replace SDK list methods.has_more: trueonceHealthy 101-email and 500-email ranges, plus an HTTP 429 retry, pass as controls. The large-range tests check the five-page budget, unique output IDs and exclusion of startup history. After the change, all 23 listener tests pass. The full suite passes 1,171 tests, with one existing skipped test. Lint, type checks and the production build pass.
I also ran the actual compiled CLI before and after the change. Each process used an isolated temporary home directory, fake credentials and intercepted HTTP responses. I recorded stdout, stderr, request cursors and status codes. Each process completed four observed polls, then stopped with exit code 130. No process reached the safety timeout.
The fixed runs have no duplicate IDs and no startup-history IDs. The healthy range passes in both builds.
Commands:
Behavior and limits
New arrivals during recovery are read after the pending range completes. Each poll's output keeps its existing oldest-first order. This change does not promise global chronological order across polls.
Local checks ran on macOS. The PR title check, dependency pin check and Node bundle
--version/--helpsmoke checks also pass. Windows/Linux installer checks, native credential-store integration checks and the packaged-binary matrix are left to GitHub Actions.The tests use controlled responses and fake credentials. No live Resend account was used. Progress is held in memory. Restart recovery, deleted boundary records and retention expiry are outside this change.
Summary by cubic
Fixes
resend emails receiving listenso it no longer reports only the first 500 emails of a large burst (or of a range where a later page errors) and then gets stuck on subsequent polls. Pagination progress is now retained between polls, and output from successful pages is preserved when a later page fails.aftercursor when a poll reaches the page limit and resumes the pending range on the next poll.not_foundso the next poll starts at the newest email instead of exiting after five failures.has_more: true.Each poll still reads up to five pages of 100 emails, and rate-limit and non-404 error handling are unchanged. Progress is held in memory only; restart recovery and expired/deleted boundary records are outside this change.
Written for commit 18220e9. Summary will update on new commits.