Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Conversation

@steven-tey
Copy link
Collaborator

@steven-tey steven-tey commented Dec 19, 2025

Summary by CodeRabbit

  • New Features

    • Payout reminder processing now handles data in batches for better performance with large datasets.
    • Reminder emails are dispatched through a queue system for improved reliability.
    • System automatically schedules additional processing batches when more data exists.
  • Chores

    • Added new payout reminder email template.

✏️ Tip: You can customize this high-level summary in your review settings.

@vercel
Copy link
Contributor

vercel bot commented Dec 19, 2025

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Review Updated (UTC)
dub Ready Ready Preview Dec 19, 2025 8:46pm

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Dec 19, 2025

Walkthrough

The PR refactors a partner payout reminder cron endpoint to use a unified GET/POST handler with Qstash-verified POST support, batch processing of pending payouts, queue-based email dispatching, and recursive batch scheduling for scalability.

Changes

Cohort / File(s) Summary
Payout reminder cron handler
apps/web/app/(ee)/api/cron/payouts/reminders/partners/route.ts
Replaced single GET handler with unified handler supporting both GET (Vercel verification) and POST (Qstash signature verification). Added BATCH_SIZE-driven batch processing, queue-based email dispatch via queueBatchEmail, progress logging, and recursive scheduling via Qstash for remaining batches. Updates lastRemindedAt timestamps after processing. Enhanced error handling with shared logging utility.
Email template map
apps/web/lib/email/email-templates-map.ts
Added ConnectPayoutReminder to EMAIL_TEMPLATES_MAP export.

Sequence Diagram

sequenceDiagram
    participant Cron as Cron/Client
    participant Handler as Handler
    participant Verify as Verification
    participant DB as Database
    participant Queue as Email Queue
    participant Qstash as Qstash

    Cron->>Handler: GET/POST request
    Handler->>Verify: Verify request (Vercel or Qstash)
    alt Verification fails
        Verify-->>Handler: Error
        Handler-->>Cron: 401/403 response
    else Verification succeeds
        Verify-->>Handler: OK
        Handler->>DB: Fetch pending payouts (BATCH_SIZE limit)
        DB-->>Handler: Batch of pending partner-programs
        
        loop For each partner in batch
            Handler->>Queue: queueBatchEmail(ConnectPayoutReminder)
        end
        
        Handler->>DB: Update lastRemindedAt for batch
        DB-->>Handler: Updated
        Handler->>Handler: Check if more batches remain
        
        alt More batches exist
            Handler->>Qstash: Schedule next batch (re-invoke endpoint)
            Qstash-->>Handler: Scheduled
            Handler-->>Cron: "Next batch scheduled" message
        else No more batches
            Handler-->>Cron: "Complete" message
        end
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

  • Qstash signature verification logic: Ensure raw body handling and signature validation are correct
  • Batch processing and remaining-batch detection: Verify logic for identifying when to schedule next batch
  • Error handling paths: Review error response consistency and logging through logAndRespond utility
  • Integration with queueBatchEmail: Confirm proper email enqueueing and batch boundaries
  • Recursive scheduling: Verify Qstash re-invocation with correct request context and domain

Possibly related PRs

Suggested reviewers

  • devkiran

Poem

🐰 Hops with glee!
Batches neatly queued, reminders take flight,
Qstash schedules the hops through the night,
From GET to POST, one handler so neat,
Partner payouts dance to a recursive beat! ✨

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main objective of the PR - improving the connect payout reminders functionality through batch processing, queue-based email dispatch, and Qstash integration.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch improve-connect-payout-reminders

📜 Recent review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 67895ea and ec1b102.

📒 Files selected for processing (2)
  • apps/web/app/(ee)/api/cron/payouts/reminders/partners/route.ts (3 hunks)
  • apps/web/lib/email/email-templates-map.ts (2 hunks)
🧰 Additional context used
🧠 Learnings (4)
📓 Common learnings
Learnt from: devkiran
Repo: dubinc/dub PR: 3213
File: apps/web/app/(ee)/api/cron/auto-approve-partner/route.ts:122-122
Timestamp: 2025-12-15T16:46:01.529Z
Learning: In the Dub codebase, cron endpoints under apps/web/app/(ee)/api/cron/ use handleCronErrorResponse for error handling, which intentionally does NOT detect QStash callbacks or set Upstash-NonRetryable-Error headers. This allows QStash to retry all cron job errors using its native retry mechanism. The selective retry logic (queueFailedRequestForRetry) is only used for specific user-facing API endpoints like /api/track/lead, /api/track/sale, and /api/links to retry only transient Prisma database errors.
Learnt from: steven-tey
Repo: dubinc/dub PR: 0
File: :0-0
Timestamp: 2025-06-25T21:20:59.837Z
Learning: In the Dub codebase, payout limit validation uses a two-stage pattern: server actions perform quick sanity checks (payoutsUsage > payoutsLimit) for immediate user feedback, while the cron job (/cron/payouts) performs authoritative validation (payoutsUsage + payoutAmount > payoutsLimit) with actual calculated amounts before processing. This design provides fast user feedback while ensuring accurate limit enforcement at transaction time.
📚 Learning: 2025-05-29T04:45:18.504Z
Learnt from: devkiran
Repo: dubinc/dub PR: 2448
File: packages/email/src/templates/partner-program-summary.tsx:0-0
Timestamp: 2025-05-29T04:45:18.504Z
Learning: In the PartnerProgramSummary email template (packages/email/src/templates/partner-program-summary.tsx), the stat titles are hardcoded constants ("Clicks", "Leads", "Sales", "Earnings") that will always match the ICONS object keys after toLowerCase() conversion, so icon lookup failures are not possible.

Applied to files:

  • apps/web/lib/email/email-templates-map.ts
📚 Learning: 2025-12-09T12:54:41.818Z
Learnt from: devkiran
Repo: dubinc/dub PR: 3207
File: apps/web/lib/cron/with-cron.ts:27-56
Timestamp: 2025-12-09T12:54:41.818Z
Learning: In `apps/web/lib/cron/with-cron.ts`, the `withCron` wrapper extracts the request body once and provides it to handlers via the `rawBody` parameter. Handlers should use this `rawBody` string parameter (e.g., `JSON.parse(rawBody)`) rather than reading from the Request object via `req.json()` or `req.text()`.

Applied to files:

  • apps/web/app/(ee)/api/cron/payouts/reminders/partners/route.ts
📚 Learning: 2025-12-15T16:45:51.667Z
Learnt from: devkiran
Repo: dubinc/dub PR: 3213
File: apps/web/app/(ee)/api/cron/auto-approve-partner/route.ts:122-122
Timestamp: 2025-12-15T16:45:51.667Z
Learning: In cron endpoints under apps/web/app/(ee)/api/cron, continue using handleCronErrorResponse for error handling. Do not detect QStash callbacks or set Upstash-NonRetryable-Error headers in these cron routes, so QStash can retry cron errors via its native retry mechanism. The existing queueFailedRequestForRetry logic should remain limited to specific user-facing API endpoints (e.g., /api/track/lead, /api/track/sale, /api/links) to retry only transient Prisma/database errors. This pattern should apply to all cron endpoints under the cron directory in this codebase.

Applied to files:

  • apps/web/app/(ee)/api/cron/payouts/reminders/partners/route.ts
🔇 Additional comments (6)
apps/web/lib/email/email-templates-map.ts (1)

2-2: LGTM!

The ConnectPayoutReminder template is correctly imported and registered in the email templates map, following the existing naming and ordering conventions.

Also applies to: 14-14

apps/web/app/(ee)/api/cron/payouts/reminders/partners/route.ts (5)

19-29: LGTM!

The dual verification approach (Vercel for scheduled GET, QStash for recursive POST) is correctly implemented, properly reading the raw body once for signature verification.


144-170: LGTM!

The batch email queuing and timestamp update logic is correctly implemented. The order of operations (queue first, update timestamp after) is appropriate—if the update fails, the 3-day reminder window provides reasonable protection against duplicate emails.


172-194: Good use of recursive batch scheduling.

The QStash-based recursive processing pattern handles large partner sets gracefully without timing out. The defensive error logging for missing messageId is appropriate.


207-208: LGTM!

Exporting the handler as both GET and POST correctly supports both Vercel's scheduled cron invocation and QStash's recursive batch calls.


197-204: The error handling in this file is correct and follows the established pattern used by all cron endpoints in the codebase. handleAndReturnErrorResponse is the appropriate error handler for cron routes, not a function called handleCronErrorResponse.

Regarding error.message: While this assumes error is an Error instance, this pattern is consistent across the codebase. Some newer cron endpoints use safer checks like error instanceof Error ? error.message : String(error), but this is an optional improvement, not a requirement specific to cron endpoints.

Likely an incorrect or invalid review comment.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@steven-tey
Copy link
Collaborator Author

@coderabbitai review

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Dec 19, 2025

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
apps/web/app/(ee)/api/cron/payouts/reminders/partners/route.ts (1)

129-134: Remove the non-null assertion on program.logo or provide a fallback value.

The logo field is nullable in the schema (String?). Using program.logo! silently allows null to pass to the email template. Either filter programs without logos before pushing to the array, or provide a fallback like logo: program.logo ?? "".

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 67895ea and ec1b102.

📒 Files selected for processing (2)
  • apps/web/app/(ee)/api/cron/payouts/reminders/partners/route.ts (3 hunks)
  • apps/web/lib/email/email-templates-map.ts (2 hunks)
🧰 Additional context used
🧠 Learnings (4)
📓 Common learnings
Learnt from: devkiran
Repo: dubinc/dub PR: 3213
File: apps/web/app/(ee)/api/cron/auto-approve-partner/route.ts:122-122
Timestamp: 2025-12-15T16:46:01.529Z
Learning: In the Dub codebase, cron endpoints under apps/web/app/(ee)/api/cron/ use handleCronErrorResponse for error handling, which intentionally does NOT detect QStash callbacks or set Upstash-NonRetryable-Error headers. This allows QStash to retry all cron job errors using its native retry mechanism. The selective retry logic (queueFailedRequestForRetry) is only used for specific user-facing API endpoints like /api/track/lead, /api/track/sale, and /api/links to retry only transient Prisma database errors.
Learnt from: steven-tey
Repo: dubinc/dub PR: 0
File: :0-0
Timestamp: 2025-06-25T21:20:59.837Z
Learning: In the Dub codebase, payout limit validation uses a two-stage pattern: server actions perform quick sanity checks (payoutsUsage > payoutsLimit) for immediate user feedback, while the cron job (/cron/payouts) performs authoritative validation (payoutsUsage + payoutAmount > payoutsLimit) with actual calculated amounts before processing. This design provides fast user feedback while ensuring accurate limit enforcement at transaction time.
📚 Learning: 2025-05-29T04:45:18.504Z
Learnt from: devkiran
Repo: dubinc/dub PR: 2448
File: packages/email/src/templates/partner-program-summary.tsx:0-0
Timestamp: 2025-05-29T04:45:18.504Z
Learning: In the PartnerProgramSummary email template (packages/email/src/templates/partner-program-summary.tsx), the stat titles are hardcoded constants ("Clicks", "Leads", "Sales", "Earnings") that will always match the ICONS object keys after toLowerCase() conversion, so icon lookup failures are not possible.

Applied to files:

  • apps/web/lib/email/email-templates-map.ts
📚 Learning: 2025-12-09T12:54:41.818Z
Learnt from: devkiran
Repo: dubinc/dub PR: 3207
File: apps/web/lib/cron/with-cron.ts:27-56
Timestamp: 2025-12-09T12:54:41.818Z
Learning: In `apps/web/lib/cron/with-cron.ts`, the `withCron` wrapper extracts the request body once and provides it to handlers via the `rawBody` parameter. Handlers should use this `rawBody` string parameter (e.g., `JSON.parse(rawBody)`) rather than reading from the Request object via `req.json()` or `req.text()`.

Applied to files:

  • apps/web/app/(ee)/api/cron/payouts/reminders/partners/route.ts
📚 Learning: 2025-12-15T16:45:51.667Z
Learnt from: devkiran
Repo: dubinc/dub PR: 3213
File: apps/web/app/(ee)/api/cron/auto-approve-partner/route.ts:122-122
Timestamp: 2025-12-15T16:45:51.667Z
Learning: In cron endpoints under apps/web/app/(ee)/api/cron, continue using handleCronErrorResponse for error handling. Do not detect QStash callbacks or set Upstash-NonRetryable-Error headers in these cron routes, so QStash can retry cron errors via its native retry mechanism. The existing queueFailedRequestForRetry logic should remain limited to specific user-facing API endpoints (e.g., /api/track/lead, /api/track/sale, /api/links) to retry only transient Prisma/database errors. This pattern should apply to all cron endpoints under the cron directory in this codebase.

Applied to files:

  • apps/web/app/(ee)/api/cron/payouts/reminders/partners/route.ts
🔇 Additional comments (6)
apps/web/lib/email/email-templates-map.ts (1)

2-2: LGTM!

The ConnectPayoutReminder template is correctly imported and registered in the email templates map, following the existing naming and ordering conventions.

Also applies to: 14-14

apps/web/app/(ee)/api/cron/payouts/reminders/partners/route.ts (5)

19-29: LGTM!

The dual verification approach (Vercel for scheduled GET, QStash for recursive POST) is correctly implemented, properly reading the raw body once for signature verification.


144-170: LGTM!

The batch email queuing and timestamp update logic is correctly implemented. The order of operations (queue first, update timestamp after) is appropriate—if the update fails, the 3-day reminder window provides reasonable protection against duplicate emails.


172-194: Good use of recursive batch scheduling.

The QStash-based recursive processing pattern handles large partner sets gracefully without timing out. The defensive error logging for missing messageId is appropriate.


207-208: LGTM!

Exporting the handler as both GET and POST correctly supports both Vercel's scheduled cron invocation and QStash's recursive batch calls.


197-204: The error handling in this file is correct and follows the established pattern used by all cron endpoints in the codebase. handleAndReturnErrorResponse is the appropriate error handler for cron routes, not a function called handleCronErrorResponse.

Regarding error.message: While this assumes error is an Error instance, this pattern is consistent across the codebase. Some newer cron endpoints use safer checks like error instanceof Error ? error.message : String(error), but this is an optional improvement, not a requirement specific to cron endpoints.

Likely an incorrect or invalid review comment.

@steven-tey steven-tey merged commit 5ce27d1 into main Dec 19, 2025
8 of 10 checks passed
@steven-tey steven-tey deleted the improve-connect-payout-reminders branch December 19, 2025 20:56
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.

2 participants