Skip to content

fix: paginate weekly digest cron to prevent serverless timeout#2324

Open
Shanidhya01 wants to merge 2 commits into
Umbrella-io:mainfrom
Shanidhya01:fix/1852-weekly-digest-pagination
Open

fix: paginate weekly digest cron to prevent serverless timeout#2324
Shanidhya01 wants to merge 2 commits into
Umbrella-io:mainfrom
Shanidhya01:fix/1852-weekly-digest-pagination

Conversation

@Shanidhya01

Copy link
Copy Markdown
Contributor

Summary

Fixes a scalability issue in the weekly digest cron job where a single unbounded Supabase query loaded all opted-in users into memory at once, causing Vercel serverless timeout failures and partial email delivery at large user counts. Users are now fetched in paginated pages of 50 and processed in parallel sub-batches of 5 via Promise.allSettled.

Closes #1852


Type of Change

  • 🐛 Bug fix (non-breaking change that fixes an issue)
  • ⚡ Performance improvement

What Changed

  • src/app/api/cron/weekly-digest/route.ts — replaced the single unbounded .select() with a while (hasMore) pagination loop using Supabase .range(page * PAGE_SIZE, (page + 1) * PAGE_SIZE - 1)
  • Added PAGE_SIZE = 50 constant to control DB fetch size per round-trip (distinct from the existing BATCH_SIZE = 5 which controls email concurrency within a page)
  • Moved the "no users opted in" early-return to after the loop, since total count is unknown before iteration
  • Renamed response fields: sentCountemailsSent, failedCountemailsFailed; added totalUsersProcessed to the JSON response and the completion log line
  • Updated the module-level JSDoc to reflect the new two-level execution model (page → sub-batch)

How to Test

  1. Seed the users table with more than 50 rows where weekly_digest_opt_in = true and email IS NOT NULL.
  2. Trigger the endpoint: curl -H "Authorization: Bearer <CRON_SECRET>" http://localhost:3000/api/cron/weekly-digest
  3. Inspect the server logs — you should see multiple page-turn iterations and a final summary log.

Expected result:
Response body contains { success: true, totalUsersProcessed: N, emailsSent: N, emailsFailed: 0, skippedCount: 0, errors: [] } with totalUsersProcessed matching the seeded count. No Vercel timeout. Re-triggering within 6 days skips all users and returns skippedCount: N.


Checklist

  • Linked the related issue above
  • Self-reviewed my own diff
  • No unnecessary console.log, debug code, or commented-out blocks
  • npm run lint passes locally
  • No TypeScript errors (npm run type-check)
  • Added or updated tests where applicable
  • Updated documentation / comments if behavior changed

Additional Context

Two-level batching: PAGE_SIZE = 50 and BATCH_SIZE = 5 are intentionally separate constants. PAGE_SIZE controls DB memory footprint per query; BATCH_SIZE controls Resend API concurrency. Both can be tuned independently without touching logic.

Partial-run safety: If a DB error occurs mid-pagination, the handler returns 500, but the last_digest_sent_at cooldown guard on already-sent users prevents duplicates on the next retry.

No breaking changes: Auth flow, skippedCount, errors array shape, and the message: "No users opted in" response are all preserved.

@vercel

vercel Bot commented Jun 11, 2026

Copy link
Copy Markdown

@Shanidhya01 is attempting to deploy a commit to the PRIYANSHU DOSHI's projects Team on Vercel.

A member of the Team first needs to authorize it.

@github-actions github-actions Bot added gssoc26 GSSoC 2026 contribution type:bug GSSoC type bonus: bug fix labels Jun 11, 2026
@github-actions github-actions Bot added type:feature GSSoC type bonus: new feature type:performance GSSoC type bonus: performance (+15 pts) labels Jun 11, 2026
@github-actions

Copy link
Copy Markdown

GSSoC Label Checklist 🏷️

@Priyanshu-byte-coder — please apply the appropriate labels before merging:

Difficulty (pick one):

  • level:beginner — 20 pts
  • level:intermediate — 35 pts
  • level:advanced — 55 pts
  • level:critical — 80 pts

Quality (optional):

  • quality:clean — ×1.2 multiplier
  • quality:exceptional — ×1.5 multiplier

Validation (required to score):

  • gssoc:approved — counts for points
  • gssoc:invalid / gssoc:spam / gssoc:ai-slop — does not score

Type labels (type:*) are auto-detected from files and title. Review and adjust if needed.
Points formula: (difficulty × quality_multiplier) + type_bonus

@Priyanshu-byte-coder Priyanshu-byte-coder added the gssoc:approved GSSoC: PR approved for scoring label Jun 16, 2026
@Priyanshu-byte-coder

Copy link
Copy Markdown
Member

This PR has merge conflicts with the current main branch. Please rebase or merge main into your branch to resolve the conflicts, then push the updated branch. Once conflicts are resolved, this PR will be merged.

git fetch origin main
git merge origin/main
# resolve conflicts
git push

@Shanidhya01

Copy link
Copy Markdown
Contributor Author

@Priyanshu-byte-coder Done

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

gssoc:approved GSSoC: PR approved for scoring gssoc26 GSSoC 2026 contribution type:bug GSSoC type bonus: bug fix type:feature GSSoC type bonus: new feature type:performance GSSoC type bonus: performance (+15 pts)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Bug: weekly-digest cron fetches all opted-in users with no limit, causing serverless timeout when user count is large

2 participants