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

Skip to content

Conversation

@steven-tey
Copy link
Collaborator

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

Summary by CodeRabbit

  • New Features
    • Improved handling of email audience subscriptions for partners and non-partners.
    • Added support for managing email sender addresses and audience IDs via centralized configuration.
    • Introduced a new email sending module with enhanced control over sender details and scheduling.
  • Bug Fixes
    • Simplified and corrected logic for sending welcome emails based on user type.
  • Chores
    • Refactored email sending and subscription management for better maintainability and modularity.

@vercel
Copy link
Contributor

vercel bot commented Jun 2, 2025

The latest updates on your projects. Learn more about Vercel for Git ↗︎

Name Status Preview Updated (UTC)
dub ✅ Ready (Inspect) Visit Preview Jun 2, 2025 5:31am

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jun 2, 2025

Walkthrough

This update refactors email subscription and sending logic across several modules. It modularizes Resend client initialization, audience constants, and email sending into separate files, updates how users are subscribed and unsubscribed from audiences, and simplifies welcome email logic. The changes also clarify and centralize configuration for Resend audiences and sender addresses.

Changes

File(s) Change Summary
apps/web/app/(ee)/api/cron/welcome-user/route.ts Simplified logic: removed workspace owner check, now subscribes user to one audience based on partner status and sends welcome email only to non-partners.
apps/web/scripts/update-subscribers.ts Modified partner user query to require projects, added pagination; logs subscription/unsubscription actions without performing them.
packages/email/src/index.ts Updated imports: now imports resend from "./resend" and sendEmailViaResend from "./send-via-resend".
packages/email/src/resend/client.ts New file: Exports a Resend client instance, initialized if API key is present, otherwise null.
packages/email/src/resend/constants.ts New file: Exports RESEND_AUDIENCES (audience IDs) and VARIANT_TO_FROM_MAP (sender addresses).
packages/email/src/resend/index.ts Refactored: removed all implementation, now re-exports from client, subscribe, and unsubscribe modules.
packages/email/src/resend/subscribe.ts
packages/email/src/resend/unsubscribe.ts
Updated imports to use new client and constants modules; reformatted error logging.
packages/email/src/send-via-resend.ts New file: Exports sendEmailViaResend function to send emails via Resend, with support for variants, reply-to, BCC, scheduling, and unsubscribe headers.

Sequence Diagram(s)

sequenceDiagram
    participant Script as update-subscribers.ts
    participant ResendClient as resend (client.ts)
    participant Audiences as RESEND_AUDIENCES (constants.ts)
    participant Subscribe as subscribe()
    participant Unsubscribe as unsubscribe()

    Script->>ResendClient: Initialize client with API key
    Script->>Audiences: Get audience IDs
    loop For each partner user
        Script->>Subscribe: subscribe({ email, name, audience: "partners.dub.co" })
        Script->>Unsubscribe: unsubscribe({ email }) (from "app.dub.co")
    end
Loading
sequenceDiagram
    participant API as welcome-user/route.ts
    participant ResendClient as resend (client.ts)
    participant Audiences as RESEND_AUDIENCES (constants.ts)
    participant Subscribe as subscribe()
    participant SendEmail as sendEmailViaResend()

    API->>Audiences: Select audience ("partners.dub.co" or "app.dub.co")
    API->>Subscribe: subscribe({ email, name, audience })
    alt User is not partner
        API->>SendEmail: sendEmailViaResend({ ...welcome email params... })
    end
Loading

Possibly related PRs

  • dubinc/dub#2462: Introduced the original welcome user route and its logic for audience subscription and welcome email sending, which is directly refactored in this PR.

Poem

A hop and a skip, the code’s now more neat,
With emails and lists, our logic’s complete.
Audiences sorted, senders defined,
Welcome notes flying—resend, re-aligned!
From burrow to inbox, the bunnies all cheer,
For tidier scripts and a system sincere!
🐇✉️


📜 Recent review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 4f45ac0 and 1d906b1.

📒 Files selected for processing (1)
  • apps/web/scripts/update-subscribers.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • apps/web/scripts/update-subscribers.ts
⏰ Context from checks skipped due to timeout of 90000ms (1)
  • GitHub Check: build
✨ Finishing Touches
  • 📝 Generate Docstrings

🪧 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.
    • Explain this complex logic.
    • 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. Examples:
    • @coderabbitai explain this code block.
    • @coderabbitai modularize this function.
  • 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 src/utils.ts and explain its main purpose.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Support

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

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai 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

Documentation and Community

  • 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.

@steven-tey steven-tey requested a review from Copilot June 2, 2025 05:14
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull Request Overview

This PR finalizes the integration of the Resend email service by modularizing the resend client and updating subscription and unsubscription flows. Key changes include:

  • Introducing a dedicated send-via-resend module for production email sending.
  • Refactoring the Resend client, subscribe, and unsubscribe functionality into separate modules with consolidated exports.
  • Updating subscriber management scripts and welcome-user APIs to utilize the new Resend integration.

Reviewed Changes

Copilot reviewed 9 out of 9 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
packages/email/src/send-via-resend.ts Adds a dedicated function to send emails via Resend with appropriate fallback checks.
packages/email/src/resend/client.ts, constants.ts, index.ts Modularizes and consolidates the Resend client and related constants.
packages/email/src/resend/subscribe.ts and unsubscribe.ts Updates the subscription and unsubscription logic with improved error logging.
apps/web/scripts/update-subscribers.ts Refactors subscriber updates to use a locally instantiated Resend client.
apps/web/app/(ee)/api/cron/welcome-user/route.ts Adjusts the welcome-user API to conditionally subscribe users and trigger welcome emails.
Comments suppressed due to low confidence (1)

apps/web/app/(ee)/api/cron/welcome-user/route.ts:50

  • [nitpick] Consider handling the conditional sendEmail call outside of Promise.all to improve readability and avoid mixing undefined values in asynchronous operations.
isPartner ? undefined : sendEmail({


main();

const resend = new Resend(process.env.RESEND_API_KEY);
Copy link

Copilot AI Jun 2, 2025

Choose a reason for hiding this comment

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

[nitpick] Consider reusing the centralized Resend client instance from the email module instead of creating a new instance locally for consistency and easier maintenance.

Copilot uses AI. Check for mistakes.
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: 2

♻️ Duplicate comments (1)
apps/web/scripts/update-subscribers.ts (1)

50-50: 🛠️ Refactor suggestion

Use the centralized Resend client instead of creating a new instance.

This duplicates the existing centralized Resend client setup. Consider importing and using the centralized client from @dub/email/resend/client for consistency.

Apply this diff to use the centralized client:

-const resend = new Resend(process.env.RESEND_API_KEY);
+import { resend } from "@dub/email/resend/client";
🧹 Nitpick comments (8)
packages/email/src/resend/unsubscribe.ts (1)

6-8: Consider standardizing error messages across modules.

The error message is appropriate but slightly different from the one in subscribe.ts. Consider using consistent error messaging across both modules for better user experience.

For consistency with subscribe.ts, consider updating the error message:

-    console.error(
-      "Resend client is not properly initialized. Skipping operation.",
-    );
+    console.error(
+      "Resend client is not initialized. This may be due to a missing or invalid RESEND_API_KEY in the .env file. Skipping.",
+    );
packages/email/src/resend/client.ts (1)

1-5: LGTM: Clean centralized client initialization pattern.

This module properly centralizes the Resend client initialization with appropriate conditional logic. The pattern of returning null when the API key is missing allows other modules to gracefully handle the unavailable service.

Consider: Invalid API key handling.

While the current implementation handles missing API keys well, consider that an invalid but present RESEND_API_KEY would still create a Resend instance that might fail during API calls. The current error handling in subscribe.ts and unsubscribe.ts should catch API-level errors, but you might want to consider adding validation or more specific error handling if needed.

packages/email/src/resend/constants.ts (2)

1-4: Explicit typing + immutability will prevent accidental audience mutations

RESEND_AUDIENCES is currently inferred as a mutable { [key: string]: string }.
If one of the audiences were to be reassigned elsewhere, Type-Script would not stop us.
Marking the object as readonly (or as const) – and optionally declaring it as a Record<string, string> – locks the mapping down at compile-time:

-export const RESEND_AUDIENCES = {
+export const RESEND_AUDIENCES: Readonly<Record<string, string>> = {
   "app.dub.co": "f5ff0661-4234-43f6-b0ca-a3f3682934e3",
   "partners.dub.co": "6caf6898-941a-45b6-a59f-d0780c3004ac",
 };

This makes accidental writes a type error and documents the intent that these are constants.


6-10: Leverage as const to tighten the variantfrom mapping

Right now VARIANT_TO_FROM_MAP is inferred as an open { [key: string]: string }, which means VARIANT_TO_FROM_MAP['non-existent'] is perfectly legal and returns string | undefined.
By marking the object as const, we achieve:

  • Literal type union 'primary' | 'notifications' | 'marketing' for the keys.
  • Narrow string literals for the values.
  • Compile-time safety when we later index the map.
-export const VARIANT_TO_FROM_MAP = {
+export const VARIANT_TO_FROM_MAP = {
   primary: "Dub.co <[email protected]>",
   notifications: "Dub.co <[email protected]>",
   marketing: "Steven from Dub.co <[email protected]>",
-};
+} as const;

Downstream (e.g. in send-via-resend.ts) you can then write
VARIANT_TO_FROM_MAP[variant] knowing variant is one of the mapped keys.

packages/email/src/resend/index.ts (1)

1-3: Consider re-exporting constants (and possibly sendEmailViaResend) for a cleaner public surface

Consumers currently need three different import paths:

import { resend } from "@dub/email/dist/resend";
import { subscribe } from "@dub/email/dist/resend";
import { VARIANT_TO_FROM_MAP } from "@dub/email/dist/resend/constants";

Re-exporting * from ./constants (and potentially ../send-via-resend) here would allow a single barrel import:

 export * from "./client";
 export * from "./subscribe";
 export * from "./unsubscribe";
+export * from "./constants";

This is optional, but it can make the package ergonomics nicer while still keeping the modular file structure.

packages/email/src/send-via-resend.ts (3)

14-24: Type-safe variant – narrow it to the known literals

variant defaults to "primary" but the type coming from ResendEmailOptions is presumably string.
Narrowing the type guards against typos ("primay"). You can achieve this by:

type Variant = keyof typeof VARIANT_TO_FROM_MAP; // "primary" | "notifications" | "marketing"

export interface ResendEmailOptions {
  // …
  variant?: Variant;
}

and then:

const { variant = "primary" as Variant,} = opts;

This pairs nicely with the earlier as const change.


26-35: Remove redundant await to propagate errors & slightly improve latency

return await <promise> adds an extra micro-tick and obscures stack-traces; you can simply return the promise:

-  return await resend.emails.send({
+  return resend.emails.send({
     to: email,

Functionally identical but a hair cleaner.


26-40: Headers spread pattern is clever but brittle – prefer explicit conditional merge

Spreading false is valid JS, yet it’s a minor foot-gun and can confuse readers unfamiliar with the technique.
An explicit conditional keeps intent clear:

-    ...(variant === "marketing" && {
-      headers: {
-        "List-Unsubscribe": "https://app.dub.co/account/settings",
-      },
-    }),
+    ...(variant === "marketing"
+      ? {
+          headers: {
+            "List-Unsubscribe": "https://app.dub.co/account/settings",
+          },
+        }
+      : {}),

Purely readability, no behavioral change.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a89c581 and 4f45ac0.

📒 Files selected for processing (9)
  • apps/web/app/(ee)/api/cron/welcome-user/route.ts (1 hunks)
  • apps/web/scripts/update-subscribers.ts (2 hunks)
  • packages/email/src/index.ts (1 hunks)
  • packages/email/src/resend/client.ts (1 hunks)
  • packages/email/src/resend/constants.ts (1 hunks)
  • packages/email/src/resend/index.ts (1 hunks)
  • packages/email/src/resend/subscribe.ts (2 hunks)
  • packages/email/src/resend/unsubscribe.ts (1 hunks)
  • packages/email/src/send-via-resend.ts (1 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (3)
packages/email/src/resend/unsubscribe.ts (1)
packages/email/src/resend/client.ts (1)
  • resend (3-5)
packages/email/src/send-via-resend.ts (2)
packages/email/src/resend/client.ts (1)
  • resend (3-5)
packages/email/src/resend/constants.ts (1)
  • VARIANT_TO_FROM_MAP (6-10)
apps/web/scripts/update-subscribers.ts (4)
packages/email/src/resend/subscribe.ts (1)
  • subscribe (4-30)
packages/email/src/resend/unsubscribe.ts (1)
  • unsubscribe (4-19)
packages/email/src/resend/client.ts (1)
  • resend (3-5)
packages/email/src/resend/constants.ts (1)
  • RESEND_AUDIENCES (1-4)
🔇 Additional comments (10)
packages/email/src/resend/subscribe.ts (2)

1-2: LGTM: Clean modularization of imports.

The updated imports properly separate concerns by importing the Resend client from a dedicated ./client module and constants from ./constants. This improves code organization and maintainability.


14-16: LGTM: Improved error message formatting.

The multi-line formatting of the console.error improves readability while maintaining the same informative error message.

packages/email/src/index.ts (2)

1-1: LGTM: Clean import from refactored resend module.

The import continues to use the clean ./resend path, which now re-exports from the modularized structure. This maintains a good abstraction while supporting the internal refactoring.


4-4: LGTM: Email sending logic properly modularized.

Moving sendEmailViaResend to a dedicated module improves separation of concerns and makes the codebase more maintainable.

packages/email/src/resend/unsubscribe.ts (1)

1-2: LGTM: Consistent modularization with subscribe.ts.

The import updates match the pattern established in subscribe.ts, creating consistency across the resend modules.

apps/web/app/(ee)/api/cron/welcome-user/route.ts (3)

4-4: Good refactoring to use centralized subscription logic.

The import of the centralized subscribe function aligns well with the modularization effort and reduces code duplication.


44-48: Clean subscription logic with audience-based targeting.

The simplified subscription approach using audience-based targeting ("partners.dub.co" or "app.dub.co") is much cleaner than the previous implementation and properly leverages the centralized subscription functionality.


50-61: Conditional email sending improves user experience.

The ternary operator approach to conditionally send welcome emails only to non-partner users is a good improvement. This prevents sending inappropriate welcome emails to partners while maintaining the functionality for regular users.

apps/web/scripts/update-subscribers.ts (2)

1-1: Good use of centralized constants.

Importing RESEND_AUDIENCES from the centralized constants module aligns with the modularization effort.


16-20: Enhanced query parameters improve script reliability.

The addition of orderBy, skip, and take parameters provides better control over data retrieval and supports pagination, which is essential for processing large datasets.

@steven-tey steven-tey merged commit 50274be into main Jun 2, 2025
6 of 7 checks passed
@steven-tey steven-tey deleted the resend-audience branch June 2, 2025 05:34
@coderabbitai coderabbitai bot mentioned this pull request Jun 9, 2025
@coderabbitai coderabbitai bot mentioned this pull request Sep 4, 2025
@coderabbitai coderabbitai bot mentioned this pull request Oct 4, 2025
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