-
Notifications
You must be signed in to change notification settings - Fork 2.8k
Use program emailDomains for invite email #3243
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.
|
WalkthroughThis PR refactors email domain handling across campaign and partner invite features. It generalizes the Changes
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)
📜 Recent review detailsConfiguration used: defaults Review profile: CHILL Plan: Pro 📒 Files selected for processing (3)
🚧 Files skipped from review as they are similar to previous changes (1)
🧰 Additional context used🧠 Learnings (2)📚 Learning: 2025-10-28T19:17:44.390ZApplied to files:
📚 Learning: 2025-10-06T15:48:45.956ZApplied to files:
🧬 Code graph analysis (2)apps/web/lib/api/programs/get-program-or-throw.ts (3)
apps/web/app/(ee)/api/programs/[programId]/route.ts (2)
⏰ 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)
🔇 Additional comments (5)
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 |
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: 2
🧹 Nitpick comments (1)
apps/web/lib/api/programs/get-program-or-throw.ts (1)
29-34: ThefinalIncludelogic incorrectly forcescategories: falsewhen not requested.When a caller requests
include: { emailDomains: true }, the spread preservesemailDomains, but the explicitcategories: include.categories ? true : falsealways setscategoriestofalse. This is harmless but redundant. More importantly, ifincludeisundefined,finalIncludebecomesundefined, which is correct—but ifincludeis{}(empty object), the conditional still triggers and produces{ categories: false }.Consider simplifying:
- const finalInclude = include - ? ({ - ...include, - categories: include.categories ? true : false, - } as T) - : undefined; + const finalInclude = include && Object.keys(include).length > 0 + ? include + : undefined;The category boolean normalization (
truevs truthy) appears unnecessary since Prisma accepts truthy values for includes.
📜 Review details
Configuration used: defaults
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (4)
apps/web/app/(ee)/api/campaigns/[campaignId]/preview/route.ts(2 hunks)apps/web/app/(ee)/api/programs/[programId]/route.ts(1 hunks)apps/web/lib/actions/partners/invite-partner.ts(2 hunks)apps/web/lib/api/programs/get-program-or-throw.ts(2 hunks)
🧰 Additional context used
🧠 Learnings (6)
📚 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/lib/actions/partners/invite-partner.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/lib/actions/partners/invite-partner.ts
📚 Learning: 2025-10-06T15:48:14.205Z
Learnt from: TWilson023
Repo: dubinc/dub PR: 2935
File: apps/web/lib/actions/partners/invite-partner-from-network.ts:21-28
Timestamp: 2025-10-06T15:48:14.205Z
Learning: For the network invites limit check in apps/web/lib/actions/partners/invite-partner-from-network.ts, the team accepts that concurrent invites may bypass the limit due to race conditions. Perfect atomicity is not required for this feature.
Applied to files:
apps/web/lib/actions/partners/invite-partner.ts
📚 Learning: 2025-08-25T17:42:13.600Z
Learnt from: devkiran
Repo: dubinc/dub PR: 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/actions/partners/invite-partner.ts
📚 Learning: 2025-11-17T05:19:11.972Z
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.972Z
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/app/(ee)/api/campaigns/[campaignId]/preview/route.ts
📚 Learning: 2025-10-28T19:17:28.085Z
Learnt from: TWilson023
Repo: dubinc/dub PR: 2985
File: apps/web/app/(ee)/api/network/programs/[programSlug]/route.ts:20-27
Timestamp: 2025-10-28T19:17:28.085Z
Learning: In this codebase, Prisma's extendedWhereUnique feature is enabled (or Prisma 5.x is used), allowing findUniqueOrThrow to accept additional non-unique filters alongside unique fields like slug. This pattern is valid and acceptable.
Applied to files:
apps/web/lib/api/programs/get-program-or-throw.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 (5)
apps/web/lib/api/programs/get-program-or-throw.ts (1)
8-17: LGTM on schema and type definitions.The
ProgramWithInviteEmailSchemaextension andProgramWithInclude<T>type provide good type safety for combining Zod validation with Prisma's dynamic includes. The type intersection approach is appropriate for this use case.apps/web/app/(ee)/api/programs/[programId]/route.ts (1)
10-12: LGTM!The migration from
includeCategories: truetoinclude: { categories: true }correctly adopts the new generic include API pattern.apps/web/lib/actions/partners/invite-partner.ts (2)
27-29: Verify thatemailDomainsis available after Zod parsing.This include depends on
getProgramOrThrowproperly returning theemailDomainsrelation. As noted in the review ofget-program-or-throw.ts, the current Zodparse()call may strip this field. Ensure that fix is applied for this code to function correctly.
91-95: LGTM on the conditional from address logic.The defensive check
program.emailDomains.length > 0correctly handles the case where no email domains are configured, falling back toundefinedwhich uses the default sender.apps/web/app/(ee)/api/campaigns/[campaignId]/preview/route.ts (1)
41-43: Same verification note applies regardingemailDomainsavailability.As with the invite flow, this include depends on
getProgramOrThrowproperly returning theemailDomainsrelation after Zod parsing.
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: 0
🧹 Nitpick comments (1)
apps/web/lib/api/programs/get-program-or-throw.ts (1)
35-45: Consider extracting the category transformation to improve type safety.The
@ts-ignorecomment works but could be avoided by narrowing the type more explicitly. If this pattern becomes common, consider a small helper.🔎 Optional: Type-safe alternative
// Transform categories if included - const transformedProgram = - include?.categories && "categories" in program - ? { - ...program, - // @ts-ignore conditionally transforming categories - categories: program.categories?.map(({ category }) => category) ?? [], - } - : program; + const transformedProgram = { + ...program, + ...(include?.categories && + "categories" in program && { + categories: + (program.categories as { category: string }[] | undefined)?.map( + ({ category }) => category, + ) ?? [], + }), + };
📜 Review details
Configuration used: defaults
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (3)
apps/web/app/(ee)/api/programs/[programId]/route.ts(1 hunks)apps/web/lib/api/programs/get-program-or-throw.ts(2 hunks)apps/web/lib/zod/schemas/programs.ts(2 hunks)
🧰 Additional context used
🧠 Learnings (4)
📓 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.972Z
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-07-30T15:25:13.936Z
Learnt from: TWilson023
Repo: dubinc/dub PR: 2673
File: apps/web/ui/partners/rewards/add-edit-reward-sheet.tsx:56-66
Timestamp: 2025-07-30T15:25:13.936Z
Learning: In apps/web/ui/partners/rewards/add-edit-reward-sheet.tsx, the form schema uses partial condition objects to allow users to add empty/unconfigured condition fields without type errors, while submission validation uses strict schemas to ensure data integrity. This two-stage validation pattern improves UX by allowing progressive completion of complex forms.
Applied to files:
apps/web/lib/zod/schemas/programs.ts
📚 Learning: 2025-10-28T19:17:44.390Z
Learnt from: TWilson023
Repo: dubinc/dub PR: 2985
File: apps/web/app/(ee)/api/network/programs/[programSlug]/route.ts:32-37
Timestamp: 2025-10-28T19:17:44.390Z
Learning: In Prisma queries, the `include` clause is only used for relationships (one-to-one, one-to-many, many-to-many). Regular scalar fields, JSON fields, and other non-relational columns are automatically included in the query result and do not need to be specified in the `include` object.
Applied to files:
apps/web/lib/api/programs/get-program-or-throw.ts
📚 Learning: 2025-10-06T15:48:45.956Z
Learnt from: TWilson023
Repo: dubinc/dub PR: 2935
File: packages/prisma/schema/workspace.prisma:21-36
Timestamp: 2025-10-06T15:48:45.956Z
Learning: In the Dub repository (dubinc/dub), Prisma schema changes are not managed with separate migration files. Do not flag missing Prisma migration files when schema changes are made to files like `packages/prisma/schema/workspace.prisma` or other schema files.
Applied to files:
apps/web/lib/api/programs/get-program-or-throw.ts
🧬 Code graph analysis (2)
apps/web/lib/zod/schemas/programs.ts (1)
apps/web/lib/zod/schemas/program-invite-email.ts (1)
programInviteEmailDataSchema(3-9)
apps/web/app/(ee)/api/programs/[programId]/route.ts (2)
apps/web/lib/api/programs/get-program-or-throw.ts (1)
getProgramOrThrow(12-46)apps/web/lib/zod/schemas/programs.ts (1)
ProgramSchema(27-53)
⏰ 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)
apps/web/app/(ee)/api/programs/[programId]/route.ts (1)
3-16: LGTM!The refactored approach is well-structured:
- Using the generic
includeparameter aligns with the updatedgetProgramOrThrowsignature- Applying
ProgramSchema.parse()at the API boundary ensures runtime validation of the response before it's sent to clients- The categories transformation in
getProgramOrThrowcorrectly maps to the enum array format expected byProgramSchemaapps/web/lib/zod/schemas/programs.ts (1)
22-22: LGTM!The
inviteEmailDatafield is correctly added using the.nullish()schema, making it appropriately optional. The TODO comment provides good visibility for planned future refactoring.Also applies to: 44-44
apps/web/lib/api/programs/get-program-or-throw.ts (1)
7-10: Type definition looks correct for the use case.The intersection type combines the base
ProgramSchemashape with the Prisma include payload, allowing callers to have type safety for both the base program fields and any included relations. Since validation happens at the API boundary (viaProgramSchema.parsein route handlers), this approach keeps the utility function flexible while maintaining type information.
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
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/campaigns/[campaignId]/preview/route.ts (1)
93-93: Remove debug logging before merging.The
console.logstatement appears to be debug output that should be removed or replaced with structured logging for production.🔎 Suggested fix:
- console.log("Resend response:", data);
📜 Review details
Configuration used: defaults
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
apps/web/app/(ee)/api/campaigns/[campaignId]/preview/route.ts(2 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-11-17T05:19:11.972Z
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.972Z
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/app/(ee)/api/campaigns/[campaignId]/preview/route.ts
🧬 Code graph analysis (1)
apps/web/app/(ee)/api/campaigns/[campaignId]/preview/route.ts (1)
apps/web/lib/api/errors.ts (1)
DubApiError(58-75)
⏰ 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 (1)
apps/web/app/(ee)/api/campaigns/[campaignId]/preview/route.ts (1)
41-44: LGTM!The inclusion of
emailDomainsis correctly added to support the new validation logic.
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: 0
🧹 Nitpick comments (1)
apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/partners/invite-partner-sheet.tsx (1)
517-519: Consider adding a visual indicator when using fallback email domain.The silent fallback to
[email protected]works correctly, but users may not realize they haven't configured a custom email domain. The campaign editor (lines 341-348 in campaign-editor.tsx) provides a helpful tooltip explaining what to do when no verified domain exists—consider a similar approach here for consistency.Potential enhancement
Add an info tooltip or badge when the fallback domain is being used:
<p className="text-xs text-neutral-500"> <strong className="font-medium text-neutral-900">From: </strong> {verifiedEmailDomain ? `partners@${verifiedEmailDomain.slug}` : "[email protected]"} + {!verifiedEmailDomain && ( + <InfoTooltip content="Configure a custom email domain in workspace settings to use your own domain." /> + )} </p>
📜 Review details
Configuration used: defaults
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (3)
apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/campaigns/[campaignId]/campaign-editor.tsx(3 hunks)apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/partners/invite-partner-sheet.tsx(3 hunks)apps/web/lib/swr/use-email-domains.ts(2 hunks)
🧰 Additional context used
🧠 Learnings (3)
📚 Learning: 2025-10-15T01:05:43.266Z
Learnt from: steven-tey
Repo: dubinc/dub PR: 2958
File: apps/web/app/app.dub.co/(dashboard)/[slug]/settings/members/page-client.tsx:432-457
Timestamp: 2025-10-15T01:05:43.266Z
Learning: In apps/web/app/app.dub.co/(dashboard)/[slug]/settings/members/page-client.tsx, defer refactoring the custom MenuItem component (lines 432-457) to use the shared dub/ui MenuItem component to a future PR, as requested by steven-tey.
Applied to files:
apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/partners/invite-partner-sheet.tsxapps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/campaigns/[campaignId]/campaign-editor.tsx
📚 Learning: 2025-11-17T05:19:11.972Z
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.972Z
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/app/app.dub.co/(dashboard)/[slug]/(ee)/program/partners/invite-partner-sheet.tsxapps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/campaigns/[campaignId]/campaign-editor.tsx
📚 Learning: 2025-07-17T06:41:45.620Z
Learnt from: devkiran
Repo: dubinc/dub PR: 2637
File: apps/web/app/(ee)/api/singular/webhook/route.ts:0-0
Timestamp: 2025-07-17T06:41:45.620Z
Learning: In the Singular integration (apps/web/app/(ee)/api/singular/webhook/route.ts), the event names in the singularToDubEvent object have intentionally different casing: "Copy GAID" and "copy IDFA". This casing difference is valid and should not be changed, as these are the correct event names expected from Singular.
Applied to files:
apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/campaigns/[campaignId]/campaign-editor.tsx
🧬 Code graph analysis (3)
apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/partners/invite-partner-sheet.tsx (1)
apps/web/lib/swr/use-email-domains.ts (1)
useEmailDomains(7-35)
apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/campaigns/[campaignId]/campaign-editor.tsx (1)
apps/web/lib/swr/use-email-domains.ts (1)
useEmailDomains(7-35)
apps/web/lib/swr/use-email-domains.ts (2)
apps/web/lib/swr/use-workspace.ts (1)
useWorkspace(7-48)apps/web/lib/types.ts (1)
EmailDomainProps(692-692)
⏰ 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 (2)
apps/web/lib/swr/use-email-domains.ts (1)
24-26: LGTM! Clean memoization of verified domain.The memoized
verifiedEmailDomaincomputation correctly finds the first verified domain. TheuseMemodependency array is accurate, and consumers properly handle the undefined case with optional chaining.apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/campaigns/[campaignId]/campaign-editor.tsx (1)
331-367: Excellent integration with proper disabled states and helpful UX.The From field implementation correctly:
- Derives the domain suffix from
verifiedEmailDomain.slug- Disables the field when no verified domain exists
- Provides an actionable tooltip directing users to configure an email domain
- Safely constructs the email address with optional chaining
The logic properly handles loading states, readonly campaigns, and missing domains.
Summary by CodeRabbit
New Features
Improvements
✏️ Tip: You can customize this high-level summary in your review settings.