-
Notifications
You must be signed in to change notification settings - Fork 2.8k
Make payout emails / queues more scalable #3125
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. Weβll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
@coderabbitai full review |
WalkthroughConsolidates recipient validation by removing per-call email presence filters and centralizing the check in Changes
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
Estimated code review effortπ― 3 (Moderate) | β±οΈ ~20 minutes
Possibly related PRs
Suggested reviewers
Poem
Pre-merge checks and finishing touchesβ Failed checks (1 warning)
β Passed checks (2 passed)
β¨ Finishing touches
π§ͺ Generate unit tests (beta)
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. Comment |
β Actions performedFull review triggered. |
There was a problem hiding this 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 reassignmentFiltering
emailsup front byBoolean(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 idempotencyThe 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 onpayout.partner.emailacceptable.To avoid accidental duplicate emails if
sendPaypalPayoutsis re-run for the same invoice, consider passing an invoice-scopedidempotencyKey, 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 queueBatchEmailThe
queueBatchEmail<typeof BountyApproved>call and thetemplatePropsstructure (email, program, bounty) look consistent and should type-check cleanly. GivenqueueBatchEmailnow filters out entries without atoaddress, the non-null assertions ons.partner.emailwonβ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
π 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.tsapps/web/scripts/perplexity/review-bounties.tsapps/web/scripts/perplexity/ban-partners.tsapps/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.tsapps/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.tsapps/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 correctUsing
queueBatchEmail<typeof PartnerPayoutConfirmed>with an invoice-scopedidempotencyKeyand the new template-based payload is consistent and keeps this flow idempotent. Non-null assertions onpayout.partner.emailare acceptable here since the queueing layer filters out entries without atoaddress before enqueueing, preventing runtime issues for missing emails.apps/web/scripts/perplexity/deactivate-partners.ts (1)
71-89: Deactivation emails correctly wired through queueBatchEmailThe
queueBatchEmail<typeof PartnerDeactivated>payload (includingpartner,program, anddeactivatedReason) looks consistent, and relying on the centraltofiltering inqueueBatchEmailmakes the non-null assertions onp.partner.emailacceptable for this script.apps/web/scripts/perplexity/ban-partners.ts (1)
177-195: Ban-partner notification emails are correctly batchedThe switch to
queueBatchEmail<typeof PartnerBanned>withpartner,program, andbannedReasoninsidetemplatePropslooks good, and the non-null assertions onp.partner.emailare fine now that invalidtoaddresses are filtered at the queueing layer.
Summary by CodeRabbit
Improvements
Bug Fixes