-
Notifications
You must be signed in to change notification settings - Fork 2.8k
Finalize resend package #2463
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
Finalize resend package #2463
Conversation
|
The latest updates on your projects. Learn more about Vercel for Git ↗︎
|
WalkthroughThis 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
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
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
Possibly related PRs
Poem
📜 Recent review detailsConfiguration used: CodeRabbit UI 📒 Files selected for processing (1)
🚧 Files skipped from review as they are similar to previous changes (1)
⏰ Context from checks skipped due to timeout of 90000ms (1)
✨ Finishing Touches
🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed 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)
Other keywords and placeholders
CodeRabbit Configuration File (
|
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.
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); |
Copilot
AI
Jun 2, 2025
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.
[nitpick] Consider reusing the centralized Resend client instance from the email module instead of creating a new instance locally for consistency and easier maintenance.
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
♻️ Duplicate comments (1)
apps/web/scripts/update-subscribers.ts (1)
50-50: 🛠️ Refactor suggestionUse 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/clientfor 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
nullwhen 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_KEYwould still create a Resend instance that might fail during API calls. The current error handling insubscribe.tsandunsubscribe.tsshould 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_AUDIENCESis 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 asreadonly(oras const) – and optionally declaring it as aRecord<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: Leverageas constto tighten thevariant→frommappingRight now
VARIANT_TO_FROM_MAPis inferred as an open{ [key: string]: string }, which meansVARIANT_TO_FROM_MAP['non-existent']is perfectly legal and returnsstring | undefined.
By marking the objectas 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]knowingvariantis one of the mapped keys.packages/email/src/resend/index.ts (1)
1-3: Consider re-exportingconstants(and possiblysendEmailViaResend) for a cleaner public surfaceConsumers 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-safevariant– narrow it to the known literals
variantdefaults to"primary"but the type coming fromResendEmailOptionsis presumablystring.
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 constchange.
26-35: Remove redundantawaitto 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 mergeSpreading
falseis 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
📒 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
./clientmodule 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
./resendpath, 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
sendEmailViaResendto 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
subscribefunction 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_AUDIENCESfrom the centralized constants module aligns with the modularization effort.
16-20: Enhanced query parameters improve script reliability.The addition of
orderBy,skip, andtakeparameters provides better control over data retrieval and supports pagination, which is essential for processing large datasets.
Summary by CodeRabbit