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

Skip to content

Conversation

@steven-tey
Copy link
Collaborator

@steven-tey steven-tey commented Sep 2, 2025

Summary by CodeRabbit

  • New Features

    • Partner notifications now use a unified "commission" model covering sale, lead, click, and custom types with contextual messages and referral link when available.
  • Enhancements

    • Program owner alerts are sent only for sale-type commissions.
    • Workflow triggers remain and run asynchronously for reliability; partner-notification timing adjusted.
  • Content

    • Partner email templates updated (titles, messaging, dynamic commission wording).
    • Welcome email copy simplified.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Sep 2, 2025

Warning

Rate limit exceeded

@steven-tey has exceeded the limit for the number of commits or files that can be reviewed per hour. Please wait 2 minutes and 48 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

📥 Commits

Reviewing files that changed from the base of the PR and between 890ea0f and 0d4de52.

📒 Files selected for processing (1)
  • apps/web/lib/partners/create-partner-commission.ts (3 hunks)

Walkthrough

Partner sale notifications were removed and replaced with a commission-centered notification flow. Webhook and sales handlers no longer call notifyPartnerSale; a new notifyPartnerCommission API and updated email templates are introduced and invoked during commission creation. Minor copy tweak in partner welcome email.

Changes

Cohort / File(s) Summary of Changes
Stripe webhooks
apps/web/app/(ee)/api/stripe/integration/webhook/checkout-session-completed.ts, apps/web/app/(ee)/api/stripe/integration/webhook/invoice-paid.ts
Removed notifyPartnerSale usage; now only triggers workflows via executeWorkflows/waitUntil. Replaced prior Promise.allSettled that included partner notification.
Conversion & Shopify sales flows
apps/web/lib/api/conversions/track-sale.ts, apps/web/lib/integrations/shopify/create-sale.ts
Dropped partner-notification path: notifyPartnerSale import/usage removed; createPartnerCommission still called but its result is not used for immediate notification. Workflow/webhook triggers preserved.
Partner notification & commission creation
apps/web/lib/api/partners/notify-partner-commission.ts, apps/web/lib/partners/create-partner-commission.ts
Added notifyPartnerCommission with new signature (program, workspace, commission). create-partner-commission now fetches enriched program + workspace, calls notifyPartnerCommission within async side-effects alongside webhooks/audits/workflows.
Email templates
packages/email/src/templates/new-commission-alert-partner.tsx, packages/email/src/templates/new-sale-alert-program-owner.tsx, packages/email/src/templates/welcome-email-partner.tsx
Renamed/reworked partner alert to NewCommissionAlertPartner and updated props (commission shape, optional shortLink). Program-owner template props changed from salecommission. Minor copy change in welcome email.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor User as User/Customer
  participant Source as Source (Shopify/Stripe/Conversions)
  participant Server as App Server
  participant DB as Database
  participant Mail as Email Service
  participant Hooks as Webhooks/Workflows

  User->>Source: Purchase or action
  Source->>Server: Emit sale/commission event
  Server->>DB: createPartnerCommission(...)
  DB-->>Server: Commission record
  rect rgba(230,245,255,0.5)
    note right of Server: Async side effects (waitUntil)
    Server-)Mail: notifyPartnerCommission(program, workspace, commission)
    Server-)Hooks: Trigger webhooks, audits, workflows
  end
Loading
sequenceDiagram
  autonumber
  participant Stripe as Stripe
  participant WH as Webhook handler
  participant Work as Workflows

  Stripe-->>WH: checkout-session-completed / invoice-paid event
  WH-)Work: executeWorkflows()
  note right of WH: Partner sale notifications removed from webhook handlers
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Suggested reviewers

  • devkiran

Poem

Thump-thump, I hop to say,
Commissions bloom where sales once lay.
Emails flutter, short links shine,
Workflows hum in gentle time.
Carrots for partners—cheers, fine! 🥕🐇

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch notify-partner-commission

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
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

Type @coderabbitai help to get the list of available commands.

Other keywords and placeholders

  • Add @coderabbitai ignore or @coderabbit ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@vercel
Copy link
Contributor

vercel bot commented Sep 2, 2025

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

Project Deployment Preview Updated (UTC)
dub Ready Ready Preview Sep 2, 2025 9:27pm

@steven-tey
Copy link
Collaborator Author

@coderabbitai full review

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Sep 2, 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: 3

Caution

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

⚠️ Outside diff range comments (1)
apps/web/lib/api/partners/notify-partner-commission.ts (1)

105-116: Ensure a fallback recipient when no partner users are opted-in.

If no partner user has commissionCreated enabled, the partner won’t get notified even if partner.email exists.

Apply this refactor to add a safe fallback and de-duplicate:

-  const partnerEmailsToNotify = partner.users
-    .map(({ user }) => user.email)
-    .filter(Boolean) as string[];
+  const partnerEmailsToNotify = Array.from(
+    new Set(
+      (partner.users.map(({ user }) => user.email).filter(Boolean) as string[]),
+    ),
+  );
+  const finalPartnerEmails =
+    partnerEmailsToNotify.length > 0
+      ? partnerEmailsToNotify
+      : partner.email
+        ? [partner.email]
+        : [];

And use finalPartnerEmails below:

-    ...partnerEmailsToNotify.map((email) => ({
+    ...finalPartnerEmails.map((email) => ({
🧹 Nitpick comments (7)
apps/web/app/(ee)/api/stripe/integration/webhook/checkout-session-completed.ts (1)

367-371: Harden background task with error logging (keep fire-and-forget semantics).

Wrap the workflow call in .catch to avoid unhandled rejections and to log context.

-      waitUntil(
-        executeWorkflows({
-          trigger: WorkflowTrigger.saleRecorded,
-          programId: link.programId,
-          partnerId: link.partnerId,
-        }),
-      );
+      waitUntil(
+        executeWorkflows({
+          trigger: WorkflowTrigger.saleRecorded,
+          programId: link.programId,
+          partnerId: link.partnerId,
+        }).catch((err) =>
+          console.error("[executeWorkflows] saleRecorded failed", {
+            err,
+            programId: link.programId,
+            partnerId: link.partnerId,
+          }),
+        ),
+      );

Also confirm createPartnerCommission is idempotent so workflows aren’t triggered twice on duplicate Stripe events.

apps/web/app/(ee)/api/stripe/integration/webhook/invoice-paid.ts (1)

214-219: Add .catch on background workflow execution.

Same rationale as the checkout handler—log but don’t fail the request.

-      waitUntil(
-        executeWorkflows({
-          trigger: WorkflowTrigger.saleRecorded,
-          programId: link.programId,
-          partnerId: link.partnerId,
-        }),
-      );
+      waitUntil(
+        executeWorkflows({
+          trigger: WorkflowTrigger.saleRecorded,
+          programId: link.programId,
+          partnerId: link.partnerId,
+        }).catch((err) =>
+          console.error("[executeWorkflows] saleRecorded failed", {
+            err,
+            programId: link.programId,
+            partnerId: link.partnerId,
+          }),
+        ),
+      );
packages/email/src/templates/new-commission-alert-partner.tsx (1)

92-96: Fix invalid Tailwind class; optionally use component.

Use font-semibold (not text-semibold) and drop the conflicting font-medium.

-                    <a
-                      href={shortLink}
-                      className="text-semibold font-medium text-black underline"
-                    >
+                    <a
+                      href={shortLink}
+                      className="font-semibold text-black underline"
+                    >

Optionally:

-                    <a
+                    <Link
                       href={shortLink}
-                      className="font-semibold text-black underline"
+                      className="font-semibold text-black underline"
                     >
                       {getPrettyUrl(shortLink)}
-                    </a>
+                    </Link>
apps/web/lib/api/partners/notify-partner-commission.ts (3)

124-126: Handle missing partner name in owner email subject.

Avoid “New commission for null”.

-          subject: `New commission for ${partner.name}`,
+          subject: `New commission${partner.name ? ` for ${partner.name}` : ""}`,

141-144: Fail fast when email client isn’t configured.

resend?.batch.send(...) silently no-ops if resend is undefined, making issues hard to detect.

-  await Promise.all(
-    emailChunks.map((emailChunk) => resend?.batch.send(emailChunk)),
-  );
+  if (!resend) {
+    console.warn("[notifyPartnerCommission] Resend client not configured; skipping email send.");
+    return;
+  }
+  await Promise.all(emailChunks.map((emailChunk) => resend.batch.send(emailChunk)));

68-79: Drop unnecessary Promise.resolve wrappers.

Simplifies readability without changing behavior.

-    commission.linkId
-      ? Promise.resolve(
-          prisma.link.findUnique({
-            where: {
-              id: commission.linkId,
-            },
-            select: {
-              shortLink: true,
-            },
-          }),
-        )
-      : Promise.resolve(null),
+    commission.linkId
+      ? prisma.link.findUnique({
+          where: { id: commission.linkId },
+          select: { shortLink: true },
+        })
+      : null,
apps/web/lib/partners/create-partner-commission.ts (1)

243-262: Good to fetch program+workspace in waitUntil; minor select tightening.

Nice placement off the request path. You can drop id from the program select since it's unused here to shave payload a bit.

I’m also leveraging the prior learning that each workspace has exactly one program—fetching by programId and destructuring workspace is consistent with that rule.

           select: {
-            id: true,
             name: true,
             slug: true,
             logo: true,
             holdingPeriodDays: true,
             workspace: {
               select: {
                 id: true,
                 slug: true,
                 name: true,
                 webhookEnabled: true,
               },
             },
           },
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between bade1e6 and b2d40ce.

📒 Files selected for processing (9)
  • apps/web/app/(ee)/api/stripe/integration/webhook/checkout-session-completed.ts (1 hunks)
  • apps/web/app/(ee)/api/stripe/integration/webhook/invoice-paid.ts (1 hunks)
  • apps/web/lib/api/conversions/track-sale.ts (1 hunks)
  • apps/web/lib/api/partners/notify-partner-commission.ts (4 hunks)
  • apps/web/lib/integrations/shopify/create-sale.ts (1 hunks)
  • apps/web/lib/partners/create-partner-commission.ts (3 hunks)
  • packages/email/src/templates/new-commission-alert-partner.tsx (2 hunks)
  • packages/email/src/templates/new-sale-alert-program-owner.tsx (2 hunks)
  • packages/email/src/templates/welcome-email-partner.tsx (1 hunks)
🧰 Additional context used
🧠 Learnings (3)
📚 Learning: 2025-08-25T17:42:13.600Z
Learnt from: devkiran
PR: dubinc/dub#2736
File: apps/web/lib/api/get-workspace-users.ts:76-83
Timestamp: 2025-08-25T17:42:13.600Z
Learning: Business rule confirmed: Each workspace has exactly one program. The code should always return workspace.programs[0] since there's only one program per workspace.

Applied to files:

  • apps/web/lib/partners/create-partner-commission.ts
📚 Learning: 2025-06-10T19:16:23.445Z
Learnt from: devkiran
PR: dubinc/dub#2510
File: apps/web/lib/actions/partners/onboard-program.ts:16-19
Timestamp: 2025-06-10T19:16:23.445Z
Learning: Business rule: Each workspace may have at most one program; attempting to create more must be blocked in code.

Applied to files:

  • apps/web/lib/partners/create-partner-commission.ts
📚 Learning: 2025-05-29T04:45:18.504Z
Learnt from: devkiran
PR: dubinc/dub#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:

  • packages/email/src/templates/new-commission-alert-partner.tsx
🧬 Code graph analysis (8)
apps/web/lib/api/conversions/track-sale.ts (1)
apps/web/lib/partners/create-partner-commission.ts (1)
  • createPartnerCommission (25-336)
apps/web/app/(ee)/api/stripe/integration/webhook/checkout-session-completed.ts (2)
apps/web/lib/api/workflows/execute-workflows.ts (1)
  • executeWorkflows (17-122)
packages/prisma/client.ts (1)
  • WorkflowTrigger (25-25)
packages/email/src/templates/new-sale-alert-program-owner.tsx (1)
packages/utils/src/functions/currency-formatter.ts (1)
  • currencyFormatter (5-16)
apps/web/app/(ee)/api/stripe/integration/webhook/invoice-paid.ts (2)
apps/web/lib/api/workflows/execute-workflows.ts (1)
  • executeWorkflows (17-122)
packages/prisma/client.ts (1)
  • WorkflowTrigger (25-25)
apps/web/lib/partners/create-partner-commission.ts (1)
apps/web/lib/api/partners/notify-partner-commission.ts (1)
  • notifyPartnerCommission (10-145)
apps/web/lib/integrations/shopify/create-sale.ts (1)
apps/web/lib/partners/create-partner-commission.ts (1)
  • createPartnerCommission (25-336)
apps/web/lib/api/partners/notify-partner-commission.ts (2)
packages/email/src/templates/new-commission-alert-partner.tsx (1)
  • NewCommissionAlertPartner (17-165)
packages/email/src/templates/new-sale-alert-program-owner.tsx (1)
  • NewSaleAlertProgramOwner (19-203)
packages/email/src/templates/new-commission-alert-partner.tsx (2)
packages/utils/src/constants/main.ts (1)
  • DUB_WORDMARK (72-72)
packages/utils/src/functions/currency-formatter.ts (1)
  • currencyFormatter (5-16)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Vade Review
  • GitHub Check: build
🔇 Additional comments (8)
packages/email/src/templates/welcome-email-partner.tsx (1)

39-41: Copy tweak LGTM.

Concise and reads well.

packages/email/src/templates/new-sale-alert-program-owner.tsx (1)

38-41: Prop shape migration to commission looks good.

Default values and downstream computations read correctly.

Also applies to: 61-64, 69-75

apps/web/lib/api/partners/notify-partner-commission.ts (2)

121-136: Confirm owner notification preference usage.

Owner notifications filter by notificationPreference.newPartnerSale. If the intent is “commission-based” going forward, confirm this is the right flag or plan a rename/migration.


67-78: Short link may be missing protocol.

Templates use href={shortLink}. If link.shortLink is domain/path-only, this becomes a relative URL.

Optional in-place hardening:

-    shortLink: partnerLink?.shortLink ?? null,
+    shortLink: partnerLink?.shortLink
+      ? partnerLink.shortLink.startsWith("http")
+        ? partnerLink.shortLink
+        : `https://${partnerLink.shortLink}`
+      : null,

Also applies to: 102-103

apps/web/lib/api/conversions/track-sale.ts (1)

221-241: LGTM; obsolete sale-notification patterns removed
Awaiting createPartnerCommission here is correct—commission creation now handles notifications via notifyPartnerCommission; rg search found no notifyPartnerSale or related remnants.

apps/web/lib/integrations/shopify/create-sale.ts (1)

140-156: LGTM: unified on commission-based notifications.

Shifting partner notifications into createPartnerCommission keeps Shopify flow consistent with Track Sale.

apps/web/lib/partners/create-partner-commission.ts (2)

13-13: Correct import for new notification flow.

Importing notifyPartnerCommission aligns this module with the commission-based notifications. Looks good.


264-265: Destructuring workspace is clean and avoids re-selects.

This keeps the call sites concise and matches downstream expectations.

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 (2)
apps/web/lib/api/conversions/track-sale.ts (1)

221-247: Gate saleRecorded workflows on commission creation
createPartnerCommission may no-op (no reward, duration exceeded, cap hit); only call executeWorkflows when a commission was actually created.

Apply to apps/web/lib/api/conversions/track-sale.ts:

-        await createPartnerCommission({
+        const commission = await createPartnerCommission({
           event: "sale",
           programId: link.programId,
           partnerId: link.partnerId,
           linkId: link.id,
           eventId,
           customerId: customer.id,
           amount: saleData.amount,
           quantity: 1,
           invoiceId,
           currency,
           context: {
             customer: {
               country: customer.country,
             },
             sale: {
               productId: metadata?.productId as string,
             },
           },
         });
-
-        await executeWorkflows({
-          trigger: WorkflowTrigger.saleRecorded,
-          programId: link.programId,
-          partnerId: link.partnerId,
-        });
+        if (commission) {
+          await executeWorkflows({
+            trigger: WorkflowTrigger.saleRecorded,
+            programId: link.programId,
+            partnerId: link.partnerId,
+          });
+        }
apps/web/lib/integrations/shopify/create-sale.ts (1)

140-156: Restore saleRecorded workflow trigger for program links (regression vs Stripe/track-sale).

After removing notifyPartnerSale, no workflow runs here. Fire saleRecorded only when a commission is created.

Diff in this block:

-    await createPartnerCommission({
+    const commission = await createPartnerCommission({
       event: "sale",
       programId: link.programId,
       partnerId: link.partnerId,
       linkId: link.id,
       eventId: saleData.event_id,
       customerId: customer.id,
       amount: saleData.amount,
       quantity: 1,
       invoiceId: saleData.invoice_id,
       currency: saleData.currency,
       context: {
         customer: {
           country: customer.country,
         },
       },
     });
+    if (commission) {
+      waitUntil(
+        executeWorkflows({
+          trigger: WorkflowTrigger.saleRecorded,
+          programId: link.programId,
+          partnerId: link.partnerId,
+        }),
+      );
+    }

Add imports (outside this hunk):

+import { executeWorkflows } from "@/lib/api/workflows/execute-workflows";
+import { WorkflowTrigger } from "@dub/prisma/client";
♻️ Duplicate comments (3)
packages/email/src/templates/new-commission-alert-partner.tsx (1)

53-57: Stray “$” in Preview copy has been fixed.

No more dangling character; preview reads correctly.

apps/web/lib/api/partners/notify-partner-commission.ts (1)

81-84: Gate zero/negative earnings to avoid misleading emails.

Clawbacks/adjustments (earnings <= 0) currently send “You just made a commission…”. Skip or route to a dedicated template.

   if (!partner) {
     return;
   }
+
+  // Skip misleading notifications for clawbacks/zero-earnings.
+  if (commission.earnings <= 0) {
+    // TODO: consider a dedicated "commission adjusted/clawback" notification.
+    return;
+  }
apps/web/lib/partners/create-partner-commission.ts (1)

266-268: Don’t email partners for clawbacks/fraud/canceled or zero/negative earnings.

This was already raised; notifyPartnerCommission is still unconditional here. Gate it to avoid misleading “you earned a commission” emails.

Apply:

         const isClawback = earnings < 0;
         const shouldTriggerWorkflow = !isClawback && !skipWorkflow;
+        const shouldNotify =
+          !isClawback &&
+          commission.earnings > 0 &&
+          commission.status !== "fraud" &&
+          commission.status !== "canceled";
@@
-          notifyPartnerCommission({
-            program,
-            workspace,
-            commission,
-          }),
+          ...(shouldNotify
+            ? [
+                notifyPartnerCommission({
+                  program,
+                  workspace,
+                  commission,
+                }),
+              ]
+            : []),

Alternative: enforce this centrally inside notifyPartnerCommission with an early return on disallowed statuses or non-positive earnings.

Also applies to: 289-294

🧹 Nitpick comments (10)
packages/email/src/templates/welcome-email-partner.tsx (1)

39-40: Copy tweak looks good.

Shorter line reads cleanly. If you prefer a slightly warmer tone, consider “It’s time to start earning…”.

packages/email/src/templates/new-sale-alert-program-owner.tsx (1)

69-75: Verify currency duplication with currencyFormatter.

If currencyFormatter already includes “$”, appending “USD” in the rows below results in “$X.XX USD”. Confirm intended style; otherwise drop the “ USD” suffix for consistency.

packages/email/src/templates/new-commission-alert-partner.tsx (1)

89-98: Use and remove invalid Tailwind class.

Replace the raw with the react-email for consistency, and drop “text-semibold” (not a valid Tailwind class).

-                    <a
-                      href={shortLink}
-                      className="text-semibold font-medium text-black underline"
-                    >
-                      {getPrettyUrl(shortLink)}
-                    </a>
+                    <Link
+                      href={shortLink}
+                      className="font-medium text-black underline"
+                    >
+                      {getPrettyUrl(shortLink)}
+                    </Link>
apps/web/lib/api/partners/notify-partner-commission.ts (5)

67-79: Simplify optional link fetch; avoid redundant Promise.resolve.

Promise.resolve around an existing promise is unnecessary. This also reads cleaner:

-    commission.linkId
-      ? Promise.resolve(
-          prisma.link.findUnique({
-            where: {
-              id: commission.linkId,
-            },
-            select: {
-              shortLink: true,
-            },
-          }),
-        )
-      : Promise.resolve(null),
+    commission.linkId
+      ? prisma.link.findUnique({
+          where: { id: commission.linkId },
+          select: { shortLink: true },
+        })
+      : null,

105-108: Deduplicate partner recipient emails.

Guard against duplicate addresses if multiple user records map to the same email.

-  const partnerEmailsToNotify = partner.users
-    .map(({ user }) => user.email)
-    .filter(Boolean) as string[];
+  const partnerEmailsToNotify = Array.from(
+    new Set(
+      (partner.users.map(({ user }) => user.email).filter(Boolean) as string[]) ||
+        [],
+    ),
+  );

111-120: Pass only required props to NewCommissionAlertPartner.

Avoid spreading unused fields (partner) into the template props.

-    ...partnerEmailsToNotify.map((email) => ({
+    ...partnerEmailsToNotify.map((email) => ({
       subject: "You just made a commission via Dub Partners!",
       from: VARIANT_TO_FROM_MAP.notifications,
       to: email,
-      react: NewCommissionAlertPartner({
-        email,
-        ...data,
-      }),
+      react: NewCommissionAlertPartner({
+        email,
+        program: data.program,
+        commission: data.commission,
+        shortLink: data.shortLink,
+      }),
     })),

122-126: Align owner email subject with body copy.

Body/preview says “You received a sale…”, but subject says “New commission…”. Consider a sale-focused subject for clarity when type === "sale".

-          subject: `New commission for ${partner.name}`,
+          subject: `New sale referred by ${partner.name}`,

142-144: Optional: add basic error logging around batch sends.

Helps diagnose delivery issues without failing the whole promise chain.

-  await Promise.all(
-    emailChunks.map((emailChunk) => resend?.batch.send(emailChunk)),
-  );
+  await Promise.all(
+    emailChunks.map(async (emailChunk) => {
+      try {
+        return await resend?.batch.send(emailChunk);
+      } catch (err) {
+        console.error("[notifyPartnerCommission] resend.batch.send failed", {
+          size: emailChunk.length,
+          err,
+        });
+      }
+    }),
+  );
apps/web/app/(ee)/api/stripe/integration/webhook/checkout-session-completed.ts (1)

367-371: LGTM; minor hardening for background errors.

Wrap executeWorkflows with a catch to avoid unhandled rejections inside waitUntil.

-      waitUntil(
-        executeWorkflows({
+      waitUntil(
+        executeWorkflows({
           trigger: WorkflowTrigger.saleRecorded,
           programId: link.programId,
           partnerId: link.partnerId,
-        }),
+        }).catch(console.error),
       );
apps/web/app/(ee)/api/stripe/integration/webhook/invoice-paid.ts (1)

214-218: LGTM; mirror the small error guard.

Same optional catch as in checkout-session-completed to prevent unhandled rejections inside waitUntil.

-        executeWorkflows({
+        executeWorkflows({
           trigger: WorkflowTrigger.saleRecorded,
           programId: link.programId,
           partnerId: link.partnerId,
-        }),
+        }).catch(console.error),
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between bade1e6 and 890ea0f.

📒 Files selected for processing (9)
  • apps/web/app/(ee)/api/stripe/integration/webhook/checkout-session-completed.ts (1 hunks)
  • apps/web/app/(ee)/api/stripe/integration/webhook/invoice-paid.ts (1 hunks)
  • apps/web/lib/api/conversions/track-sale.ts (1 hunks)
  • apps/web/lib/api/partners/notify-partner-commission.ts (4 hunks)
  • apps/web/lib/integrations/shopify/create-sale.ts (1 hunks)
  • apps/web/lib/partners/create-partner-commission.ts (3 hunks)
  • packages/email/src/templates/new-commission-alert-partner.tsx (2 hunks)
  • packages/email/src/templates/new-sale-alert-program-owner.tsx (2 hunks)
  • packages/email/src/templates/welcome-email-partner.tsx (1 hunks)
🧰 Additional context used
🧠 Learnings (2)
📚 Learning: 2025-08-25T17:42:13.600Z
Learnt from: devkiran
PR: dubinc/dub#2736
File: apps/web/lib/api/get-workspace-users.ts:76-83
Timestamp: 2025-08-25T17:42:13.600Z
Learning: Business rule confirmed: Each workspace has exactly one program. The code should always return workspace.programs[0] since there's only one program per workspace.

Applied to files:

  • apps/web/lib/partners/create-partner-commission.ts
📚 Learning: 2025-06-10T19:16:23.445Z
Learnt from: devkiran
PR: dubinc/dub#2510
File: apps/web/lib/actions/partners/onboard-program.ts:16-19
Timestamp: 2025-06-10T19:16:23.445Z
Learning: Business rule: Each workspace may have at most one program; attempting to create more must be blocked in code.

Applied to files:

  • apps/web/lib/partners/create-partner-commission.ts
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: build
🔇 Additional comments (3)
packages/email/src/templates/new-sale-alert-program-owner.tsx (2)

38-41: Prop rename to commission: LGTM.

Defaults and shape align with downstream usage.


61-65: Types updated for commission: LGTM.

Props match how the template computes amounts/earnings.

apps/web/lib/partners/create-partner-commission.ts (1)

243-265: Program/workspace fetch expansion looks good.

The selected fields cover notify and webhook needs without over-fetching.

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