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

Skip to content

Conversation

@steven-tey
Copy link
Collaborator

@steven-tey steven-tey commented Nov 18, 2025

Summary by CodeRabbit

  • Improvements

    • Improved payout processing efficiency with chunked batch handling for large volumes.
    • Switched to template-based email queuing for more consistent message formatting and delivery.
    • Standardized batch handling to reduce per-item logging noise and report chunk progress.
  • Bug Fixes

    • Adjusted batching to include items previously excluded when missing partner email β€” may affect recipients and requires validation of email presence.

@vercel
Copy link
Contributor

vercel bot commented Nov 18, 2025

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

Project Deployment Preview Updated (UTC)
dub Ready Ready Preview Nov 18, 2025 4:47am

@steven-tey
Copy link
Collaborator Author

@coderabbitai full review

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Nov 18, 2025

Walkthrough

Consolidates recipient validation by removing per-call email presence filters and centralizing the check in queue-batch-email.ts. Adds chunked (100-item) parallel processing for Stripe payouts and switches PayPal/other batch sends to queued, template-based batch emails.

Changes

Cohort / File(s) Summary
Stripe payout batch processing
apps/web/app/(ee)/api/cron/payouts/charge-succeeded/queue-stripe-payouts.ts
Chunks partners into groups of 100 and processes chunks in parallel via Promise.allSettled. Updates logging for chunk progress. Keeps deduplication and conditional chargeId logic.
External & PayPal payout queueing
apps/web/app/(ee)/api/cron/payouts/charge-succeeded/queue-external-payouts.ts, apps/web/app/(ee)/api/cron/payouts/charge-succeeded/send-paypal-payouts.ts
Removed local filters that excluded entries without partner.email. Converted PayPal batch sends from sendBatchEmail (React component payload) to queueBatchEmail using templateName + templateProps.
Centralized batch email validation
apps/web/lib/email/queue-batch-email.ts
Adds a preprocessing filter to drop emails missing a to address before batching/queuing.
Partner / bounty scripts
apps/web/scripts/perplexity/ban-partners.ts, apps/web/scripts/perplexity/deactivate-partners.ts, apps/web/scripts/perplexity/review-bounties.ts
Removed per-script email presence filters and map directly to queued email payloads, relying on centralized filtering in queue-batch-email.

Sequence Diagram(s)

sequenceDiagram
    participant Cron as Cron Job
    participant Chunker as queue-stripe-payouts
    participant QueueBatch as queue-batch-email
    participant Worker as Email Worker

    Cron->>Chunker: Trigger payout processing
    activate Chunker
    Chunker->>Chunker: chunk(partners, 100)
    loop per chunk (concurrent)
        Chunker->>QueueBatch: queueBatchEmail(emails for chunk)
        activate QueueBatch
        Note over QueueBatch: filter out emails without `to`
        QueueBatch->>Worker: enqueue batch
        QueueBatch-->>Chunker: Promise (fulfilled/rejected)
        deactivate QueueBatch
    end
    Chunker-->>Cron: allSettled results
    deactivate Chunker
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

  • Pay attention to non-null assertions of partner.email in caller sites now relying on centralized filtering.
  • Verify idempotency and error handling semantics across chunked Promise.allSettled flows in queue-stripe-payouts.
  • Confirm template payload shape changes (React component -> templateName/templateProps) preserve email content.

Possibly related PRs

Suggested reviewers

  • devkiran
  • TWilson023

Poem

🐰
Chunks hop through the midnight queue,
One hundred strong, a tidy crew.
Emails cleaned at one small gate,
No duplicated guards β€” neat and straight.
Hooray, says the rabbit, joy in transit too! πŸ₯•

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 14.29% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
βœ… Passed checks (2 passed)
Check name Status Explanation
Description Check βœ… Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check βœ… Passed The title 'Make payout emails / queues more scalable' directly matches the PR's core objective and is reflected throughout all file changes involving payout processing and queue optimization.
✨ 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-payout-emails

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.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Nov 18, 2025

βœ… Actions performed

Full review triggered.

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: 1

🧹 Nitpick comments (3)
apps/web/lib/email/queue-batch-email.ts (1)

24-25: Centralized filtering of invalid recipients is good; consider avoiding param reassignment

Filtering emails up front by Boolean(email.to) nicely centralizes recipient validation so call sites don’t need per-item guards, and it aligns with the existing β€œfilter before send” pattern in your email stack. To keep things a bit clearer (and friendlier to lint rules that dislike parameter reassignment), you might instead assign to a new local (e.g. const filteredEmails = emails.filter(...)) and use that for the rest of the function.

Based on learnings

apps/web/app/(ee)/api/cron/payouts/charge-succeeded/send-paypal-payouts.ts (1)

1-1: PayPal payout emails correctly migrated to queueBatchEmail; consider adding idempotency

The migration to queueBatchEmail<typeof PartnerPayoutProcessed> with template-based props keeps this flow consistent with other payout email paths, and central recipient filtering makes the non-null assertions on payout.partner.email acceptable.

To avoid accidental duplicate emails if sendPaypalPayouts is re-run for the same invoice, consider passing an invoice-scoped idempotencyKey, similar to the external payouts flow. For example:

-  await queueBatchEmail<typeof PartnerPayoutProcessed>(
-    payouts.map((payout) => ({
+  await queueBatchEmail<typeof PartnerPayoutProcessed>(
+    payouts.map((payout) => ({
       variant: "notifications",
       to: payout.partner.email!,
       subject: `You've received a ${currencyFormatter(
         payout.amount,
       )} payout from ${payout.program.name}`,
       templateName: "PartnerPayoutProcessed",
       templateProps: {
         email: payout.partner.email!,
         program: payout.program,
         payout,
         variant: "paypal",
       },
-    })),
-  );
+    })),
+    { idempotencyKey: `paypal-payouts/${invoice.id}` },
+  );

Also applies to: 64-75

apps/web/scripts/perplexity/review-bounties.ts (1)

98-116: Bounty approval emails are batched correctly via queueBatchEmail

The queueBatchEmail<typeof BountyApproved> call and the templateProps structure (email, program, bounty) look consistent and should type-check cleanly. Given queueBatchEmail now filters out entries without a to address, the non-null assertions on s.partner.email won’t cause runtime issues and let call sites stay concise.

If this script is likely to be re-run for the same submissions, you could optionally pass a simple idempotency key (e.g., based on the bounty id) to reduce the chance of duplicate emails on retries, but that’s not strictly necessary for a one-off maintenance script.

πŸ“œ Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

πŸ“₯ Commits

Reviewing files that changed from the base of the PR and between f859975 and daaa436.

πŸ“’ Files selected for processing (7)
  • apps/web/app/(ee)/api/cron/payouts/charge-succeeded/queue-external-payouts.ts (1 hunks)
  • apps/web/app/(ee)/api/cron/payouts/charge-succeeded/queue-stripe-payouts.ts (2 hunks)
  • apps/web/app/(ee)/api/cron/payouts/charge-succeeded/send-paypal-payouts.ts (2 hunks)
  • apps/web/lib/email/queue-batch-email.ts (1 hunks)
  • apps/web/scripts/perplexity/ban-partners.ts (1 hunks)
  • apps/web/scripts/perplexity/deactivate-partners.ts (1 hunks)
  • apps/web/scripts/perplexity/review-bounties.ts (1 hunks)
🧰 Additional context used
🧠 Learnings (7)
πŸ““ Common learnings
Learnt from: devkiran
Repo: dubinc/dub PR: 3113
File: apps/web/app/(ee)/api/cron/payouts/charge-succeeded/send-paypal-payouts.ts:65-75
Timestamp: 2025-11-17T05:19:11.961Z
Learning: In the Dub codebase, `sendBatchEmail` (implemented in packages/email/src/send-via-resend.ts) handles filtering of emails with invalid `to` addresses internally. Call sites can safely use non-null assertions on email addresses because the email sending layer will filter out any entries with null/undefined `to` values before sending. This centralized validation pattern is intentional and removes the need for filtering at individual call sites.
πŸ“š Learning: 2025-11-17T05:19:11.961Z
Learnt from: devkiran
Repo: dubinc/dub PR: 3113
File: apps/web/app/(ee)/api/cron/payouts/charge-succeeded/send-paypal-payouts.ts:65-75
Timestamp: 2025-11-17T05:19:11.961Z
Learning: In the Dub codebase, `sendBatchEmail` (implemented in packages/email/src/send-via-resend.ts) handles filtering of emails with invalid `to` addresses internally. Call sites can safely use non-null assertions on email addresses because the email sending layer will filter out any entries with null/undefined `to` values before sending. This centralized validation pattern is intentional and removes the need for filtering at individual call sites.

Applied to files:

  • apps/web/lib/email/queue-batch-email.ts
  • apps/web/scripts/perplexity/review-bounties.ts
  • apps/web/scripts/perplexity/ban-partners.ts
  • apps/web/scripts/perplexity/deactivate-partners.ts
πŸ“š Learning: 2025-09-12T17:31:10.548Z
Learnt from: devkiran
Repo: dubinc/dub PR: 2833
File: apps/web/lib/actions/partners/approve-bounty-submission.ts:53-61
Timestamp: 2025-09-12T17:31:10.548Z
Learning: In approve-bounty-submission.ts, the logic `bounty.rewardAmount ?? rewardAmount` is intentional. Bounties with preset reward amounts should use those fixed amounts, and the rewardAmount override parameter is only used when bounty.rewardAmount is null/undefined (for custom reward bounties). This follows the design pattern where bounties are either "flat rate" (fixed amount) or "custom" (variable amount set during approval).

Applied to files:

  • apps/web/scripts/perplexity/review-bounties.ts
πŸ“š Learning: 2025-08-26T14:32:33.851Z
Learnt from: TWilson023
Repo: dubinc/dub PR: 2736
File: apps/web/lib/actions/partners/create-bounty-submission.ts:105-112
Timestamp: 2025-08-26T14:32:33.851Z
Learning: Non-performance bounties are required to have submissionRequirements. In create-bounty-submission.ts, it's appropriate to let the parsing fail if submissionRequirements is null for non-performance bounties, as this indicates a data integrity issue that should be caught.

Applied to files:

  • apps/web/scripts/perplexity/review-bounties.ts
πŸ“š Learning: 2025-11-12T22:23:10.414Z
Learnt from: TWilson023
Repo: dubinc/dub PR: 3098
File: apps/web/lib/actions/partners/message-program.ts:49-58
Timestamp: 2025-11-12T22:23:10.414Z
Learning: In apps/web/lib/actions/partners/message-program.ts, when checking if a partner can continue messaging after messaging is disabled, the code intentionally requires `senderPartnerId: null` (program-initiated messages) to prevent partners from continuing to send junk messages. Only conversations started by the program should continue after messaging is disabled, as a spam prevention mechanism.

Applied to files:

  • apps/web/scripts/perplexity/ban-partners.ts
  • apps/web/scripts/perplexity/deactivate-partners.ts
πŸ“š Learning: 2025-09-17T17:44:03.965Z
Learnt from: TWilson023
Repo: dubinc/dub PR: 2857
File: apps/web/lib/actions/partners/update-program.ts:96-0
Timestamp: 2025-09-17T17:44:03.965Z
Learning: In apps/web/lib/actions/partners/update-program.ts, the team prefers to keep the messagingEnabledAt update logic simple by allowing client-provided timestamps rather than implementing server-controlled timestamp logic to avoid added complexity.

Applied to files:

  • apps/web/scripts/perplexity/ban-partners.ts
  • apps/web/scripts/perplexity/deactivate-partners.ts
πŸ“š Learning: 2025-07-11T16:28:55.693Z
Learnt from: devkiran
Repo: dubinc/dub PR: 2635
File: packages/prisma/schema/payout.prisma:24-25
Timestamp: 2025-07-11T16:28:55.693Z
Learning: In the Dub codebase, multiple payout records can now share the same stripeTransferId because payouts are grouped by partner and processed as single Stripe transfers. This is why the unique constraint was removed from the stripeTransferId field in the Payout model - a single transfer can include multiple payouts for the same partner.

Applied to files:

  • apps/web/app/(ee)/api/cron/payouts/charge-succeeded/queue-stripe-payouts.ts
πŸ”‡ Additional comments (3)
apps/web/app/(ee)/api/cron/payouts/charge-succeeded/queue-external-payouts.ts (1)

119-142: Batch email queuing for external payouts looks correct

Using queueBatchEmail<typeof PartnerPayoutConfirmed> with an invoice-scoped idempotencyKey and the new template-based payload is consistent and keeps this flow idempotent. Non-null assertions on payout.partner.email are acceptable here since the queueing layer filters out entries without a to address before enqueueing, preventing runtime issues for missing emails.

apps/web/scripts/perplexity/deactivate-partners.ts (1)

71-89: Deactivation emails correctly wired through queueBatchEmail

The queueBatchEmail<typeof PartnerDeactivated> payload (including partner, program, and deactivatedReason) looks consistent, and relying on the central to filtering in queueBatchEmail makes the non-null assertions on p.partner.email acceptable for this script.

apps/web/scripts/perplexity/ban-partners.ts (1)

177-195: Ban-partner notification emails are correctly batched

The switch to queueBatchEmail<typeof PartnerBanned> with partner, program, and bannedReason inside templateProps looks good, and the non-null assertions on p.partner.email are fine now that invalid to addresses are filtered at the queueing layer.

@steven-tey steven-tey merged commit 2a3ca6f into main Nov 18, 2025
8 of 9 checks passed
@steven-tey steven-tey deleted the improve-payout-emails branch November 18, 2025 04:52
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.

3 participants