-
Notifications
You must be signed in to change notification settings - Fork 1.1k
feat: emails updates and new emails #1801
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
WalkthroughAdds many new React email templates and sender utilities, a shared email Footer, first-time custom-domain setup email triggered from AddDomainModal, updated scheduled email tasks/webhook scheduling, render/send plumbing changes, dependency bumps, and deletes legacy trial email modules and a cron route. Changes
Sequence Diagram(s)sequenceDiagram
participant User
participant AddDomainModal
participant EmailLib as sendCustomDomainSetupEmail
participant Resend as sendEmail
User->>AddDomainModal: open modal / click upgrade
AddDomainModal->>EmailLib: sendFirstTimeEmail(email,name,plan,hasAccess)
EmailLib->>Resend: sendEmail(react: CustomDomainSetup)
Resend-->>EmailLib: success / error
EmailLib-->>AddDomainModal: mark sent / log error
sequenceDiagram
participant System
participant TaskScheduler as Task API / waitUntil
participant Sender as sendXEmail
participant Resend as sendEmail
Note over System: Scheduled triggers (trial reminders, milestone emails)
System->>TaskScheduler: trigger task(payload, delay)
TaskScheduler->>Sender: invoke sendXEmail(params)
Sender->>Resend: sendEmail(react: Template)
Resend-->>Sender: success / error
Estimated code review effort🎯 4 (Complex) | ⏱️ ~40 minutes Possibly related PRs
Pre-merge checks (1 passed, 2 warnings)❌ Failed checks (2 warnings)
✅ Passed checks (1 passed)
✨ Finishing Touches
🧪 Generate unit tests
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 |
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
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: 22
🧹 Nitpick comments (25)
components/emails/export-ready.tsx (1)
51-55: List styling OK – verify longresourceNamewrapping
break-allon the<ul>should keep very long export names from breaking the layout, but some legacy email clients ignore that rule. Consider testing with unusually long names to confirm rendering stays intact.components/emails/onboarding-4.tsx (2)
38-40: Fix typo “documnets” → “documents”- Share documnets with your{" "} + Share documents with your{" "}
48-49: Fix typo “seaprate” → “separate”- (Customization for data rooms is seaprate and available in each + (Customization for data rooms is separate and available in eachcomponents/emails/upgrade-plan.tsx (1)
75-79: Fix formatting issue after greeting simplification.The greeting change to "Marc is here" is more concise, but there appears to be a formatting issue on line 79 with unnecessary whitespace or an incomplete line.
Apply this diff to clean up the formatting:
Marc is here. I wanted to personally reach out to thank you for upgrading to Papermark{" "} -{planType}! -</Text> - +{planType}! +</Text>components/emails/upgrade-one-month-checkin.tsx (2)
18-21: Improve text formatting and readability.The text content runs together making it harder to read. Consider adding proper spacing and formatting for better readability.
Apply this diff to improve formatting:
-<Text> - It is Marc here. How has your experience been so far? Are you getting the value you - expected from the advanced features? -</Text> +<Text> + It's Marc here. How has your experience been so far? Are you getting the value you + expected from the advanced features? +</Text>
16-31: Consider adding Container component for better email client compatibility.The email lacks a Container component which can improve rendering consistency across different email clients.
Consider wrapping the content in a Container component:
<Body className="font-sans text-sm"> + <Container className="mx-auto my-10 w-[465px] p-5"> <Text>Hi {name},</Text> <!-- ... rest of content ... --> + </Container> </Body>components/emails/upgrade-personal-welcome.tsx (1)
16-30: Consider adding Container component for better email client compatibility.Similar to the check-in email, this template would benefit from a Container component for better rendering across email clients.
Consider wrapping the content in a Container component:
<Body className="font-sans text-sm"> + <Container className="mx-auto my-10 w-[465px] p-5"> <Text>Hi {name},</Text> <!-- ... rest of content ... --> + </Container> </Body>components/emails/hundred-views-congrats.tsx (2)
32-34: Clarify the "small gift" reference.The mention of a "small gift from us inside" is vague and might confuse recipients about what gift they're receiving or where to find it.
Consider either removing this line or being more specific about what the gift is and how to access it.
15-40: Consider adding Container component for consistency.For consistency with other email templates in the codebase and better email client compatibility, consider adding a Container component.
Apply this pattern used in other email templates:
<Body className="font-sans text-sm"> + <Container className="mx-auto my-10 w-[465px] p-5"> <Text>Hi {name},</Text> <!-- ... rest of content ... --> + </Container> </Body>lib/emails/send-dataroom-info.ts (1)
1-14: Rename variable to reflect the new template for clarity
Onboarding5Emailnow points to a Data-Rooms information template, which can mislead future maintainers who expect an onboarding email. Consider renaming the import and local variable to something descriptive (e.g.DataRoomsInfoEmail) to avoid cognitive overhead.-import Onboarding5Email from "@/components/emails/data-rooms-information"; +import DataRoomsInfoEmail from "@/components/emails/data-rooms-information"; … - emailTemplate = Onboarding5Email(); + emailTemplate = DataRoomsInfoEmail();lib/emails/send-onboarding.ts (1)
5-6: Align naming with the imported componentSame naming-mismatch concern as in
send-dataroom-info.ts:Onboarding5Emailnow comes from a Data-Rooms template, not an onboarding-5 template. Renaming will keep the intent clear.-import Onboarding5Email from "@/components/emails/data-rooms-information"; +import DataRoomsInfoEmail from "@/components/emails/data-rooms-information"; … - case "onboarding5": - emailTemplate = Onboarding5Email(); + case "onboarding5": + emailTemplate = DataRoomsInfoEmail();components/emails/viewed-dataroom.tsx (1)
70-81: Consider extracting the Slack-promo snippetThe exact same paragraph appears in
viewed-document.tsx. Moving it to a small shared component (e.g.<SlackPromo />) would keep the copy in one place and reduce maintenance effort.components/emails/viewed-document.tsx (1)
72-81: Duplicate Slack-promo textSee comment in
viewed-dataroom.tsx; extracting to a shared component will avoid divergence over time.lib/emails/send-slack-integration.ts (2)
17-17: Fix typo in email subject line.There's an extra space after "Slack" in the subject line.
- subject: "See who viewed your documents in Slack ", + subject: "See who viewed your documents in Slack",
13-13: Consider extracting first name for consistency.Unlike other email functions in this PR, this one passes the full
nameto the email template. For consistency with other email templates that use first names, consider extracting the first name.+ // Get the first name from the full name + const firstName = name ? name.trim().split(/\s+/)[0] : null; + - const emailTemplate = SlackIntegrationEmail({ name }); + const emailTemplate = SlackIntegrationEmail({ name: firstName });lib/emails/send-thousand-views-congrats.ts (1)
12-13: Consider using first name in subject line for better readability.Using the full name in the subject line could make it lengthy. Consider extracting the first name for a more concise and personal touch.
+ // Get the first name from the full name + const firstName = name ? name.trim().split(/\s+/)[0] : name; + - const emailTemplate = ThousandViewsCongratsEmail({ name }); + const emailTemplate = ThousandViewsCongratsEmail({ name: firstName }); try { await sendEmail({ to: email as string, - subject: `1000 views on Papermark. Awesome, ${name}`, + subject: `1000 views on Papermark. Awesome, ${firstName}`, react: emailTemplate,lib/emails/send-hundred-views-congrats.ts (2)
12-12: Remove unnecessary type assertion.The type assertion
as stringis unnecessary since theCreateUserEmailPropsshould already be properly typed. If it can be null/undefined, handle it explicitly rather than using type assertion.- to: email as string, + to: email,
17-19: Consider more robust error handling.The current error handling only logs to console. Consider whether this function should throw errors for caller handling or implement retry logic for email delivery failures.
components/emails/upgrade-six-month-checkin.tsx (2)
18-21: Fix grammatical error.The sentence has a grammatical error - missing article "a" before "biggest".
- What's been your biggest - win using Papermark? + What's been your biggest + win using Papermark?
25-30: Remove unnecessary empty lines and improve formatting.There are unnecessary empty lines that affect the email layout and readability.
- </Text> - - - <Text> - - Marc + </Text> + <Text> + Marclib/emails/send-dataroom-trial-24h.ts (1)
5-8: Consider extracting parameter interface.For consistency with other email functions and better maintainability, consider extracting the parameter type to an interface.
+interface DataroomTrial24hParams { + email: string; + name: string; +} + -export const sendDataroomTrial24hReminderEmail = async (params: { - email: string; - name: string; -}) => { +export const sendDataroomTrial24hReminderEmail = async (params: DataroomTrial24hParams) => {lib/emails/send-custom-domain-setup.ts (1)
5-10: Consider parameter object for consistency.This function uses individual parameters while other email functions use parameter objects. Consider using a consistent approach across all email functions for better maintainability.
+interface CustomDomainSetupParams { + email: string; + name?: string; + currentPlan?: string; + hasAccess?: boolean; +} + -export const sendCustomDomainSetupEmail = async ( - email: string, - name?: string, - currentPlan?: string, - hasAccess?: boolean, -) => { +export const sendCustomDomainSetupEmail = async (params: CustomDomainSetupParams) => { + const { email, name, currentPlan, hasAccess } = params;components/emails/slack-integration.tsx (1)
43-44: Fix spacing in text content.There's a missing space in "data roomviews" which should be "data room views".
Apply this fix:
With our Slack integration, you can get real-time notifications - about document and data roomviews directly in your Slack channels ! + about document and data room views directly in your Slack channels!components/emails/welcome.tsx (1)
77-79: Clean up formatting inconsistencies.There are extra line breaks and inconsistent spacing in the sign-off section.
Apply this fix:
- <Text className="text-sm text-gray-400">Marc from Papermark - - </Text> + <Text className="text-sm text-gray-400">Marc from Papermark</Text>components/domains/add-domain-modal.tsx (1)
111-130: Consider refactoring access logic and improving error handling.The email sending logic is sound, but there are opportunities for improvement:
- Code duplication: The custom domain access logic duplicates the conditions from lines 137-143
- Silent failures: Email send errors are only logged to console
Consider extracting the access logic into a helper function:
+ const hasCustomDomainAccess = () => { + return linkType === "DATAROOM_LINK" + ? (isDatarooms || isDataroomsPlus || limits?.customDomainInDataroom) + : (isBusiness || isDatarooms || isDataroomsPlus || limits?.customDomainOnPro); + }; + const sendFirstTimeEmail = async () => { if (emailSentRef.current || !session?.user?.email) return; - const hasCustomDomainAccess = linkType === "DATAROOM_LINK" - ? (isDatarooms || isDataroomsPlus || limits?.customDomainInDataroom) - : (isBusiness || isDatarooms || isDataroomsPlus || limits?.customDomainOnPro); - try { await sendCustomDomainSetupEmail( session.user.email, session.user.name || undefined, teamInfo?.currentTeam?.plan || "free", - hasCustomDomainAccess + hasCustomDomainAccess() ); emailSentRef.current = true; } catch (error) { console.error("Failed to send custom domain email:", error); } };
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (33)
components/domains/add-domain-modal.tsx(6 hunks)components/emails/custom-domain-setup.tsx(1 hunks)components/emails/data-rooms-information.tsx(1 hunks)components/emails/dataroom-trial-24h.tsx(3 hunks)components/emails/dataroom-trial-end.tsx(1 hunks)components/emails/export-ready.tsx(1 hunks)components/emails/hundred-views-congrats.tsx(1 hunks)components/emails/onboarding-1.tsx(1 hunks)components/emails/onboarding-2.tsx(1 hunks)components/emails/onboarding-3.tsx(1 hunks)components/emails/onboarding-4.tsx(1 hunks)components/emails/slack-integration.tsx(1 hunks)components/emails/thousand-views-congrats.tsx(1 hunks)components/emails/upgrade-one-month-checkin.tsx(1 hunks)components/emails/upgrade-personal-welcome.tsx(1 hunks)components/emails/upgrade-plan.tsx(1 hunks)components/emails/upgrade-six-month-checkin.tsx(1 hunks)components/emails/viewed-dataroom.tsx(2 hunks)components/emails/viewed-document.tsx(2 hunks)components/emails/welcome.tsx(3 hunks)lib/emails/send-custom-domain-setup.ts(1 hunks)lib/emails/send-dataroom-info.ts(1 hunks)lib/emails/send-dataroom-trial-24h.ts(1 hunks)lib/emails/send-dataroom-trial-end.ts(1 hunks)lib/emails/send-hundred-views-congrats.ts(1 hunks)lib/emails/send-onboarding.ts(1 hunks)lib/emails/send-slack-integration.ts(1 hunks)lib/emails/send-thousand-views-congrats.ts(1 hunks)lib/emails/send-trial-end-final-reminder.ts(0 hunks)lib/emails/send-trial-end-reminder.ts(0 hunks)lib/emails/send-upgrade-month-checkin.ts(1 hunks)lib/emails/send-upgrade-personal-welcome.ts(1 hunks)lib/emails/send-upgrade-six-months-checkin.ts(1 hunks)
💤 Files with no reviewable changes (2)
- lib/emails/send-trial-end-reminder.ts
- lib/emails/send-trial-end-final-reminder.ts
🧰 Additional context used
🪛 Biome (2.1.2)
components/emails/custom-domain-setup.tsx
[error] 139-140: Avoid using target="_blank" without rel="noopener" or rel="noreferrer".
Opening external links in new tabs without rel="noopener" is a security risk. See the explanation for more details.
Safe fix: Add the rel="noopener" attribute.
(lint/security/noBlankTarget)
components/emails/slack-integration.tsx
[error] 68-68: Avoid using target="_blank" without rel="noopener" or rel="noreferrer".
Opening external links in new tabs without rel="noopener" is a security risk. See the explanation for more details.
Safe fix: Add the rel="noopener" attribute.
(lint/security/noBlankTarget)
⏰ 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: Analyze (javascript-typescript)
🔇 Additional comments (32)
components/emails/onboarding-2.tsx (1)
39-47: Simplified list markup looks goodThe removal of the extra
<Text>wrapper around the list makes the JSX cleaner without affecting rendering. No functional or accessibility concerns spotted.components/emails/onboarding-1.tsx (1)
39-43: LGTM – list extraction improves readabilityMoving the
<ul>outside of the<Text>wrapper reduces nested markup and keeps styling intact.components/emails/onboarding-3.tsx (1)
39-47: LGTM – consistent markup with other onboarding emailsThe change mirrors the pattern used in the other onboarding templates, aiding maintainability.
components/emails/data-rooms-information.tsx (2)
33-44: Excellent improvement to HTML semantics and accessibility.The restructuring from text-based bullet points to proper HTML list elements (
<ul>and<li>) significantly improves the email's semantic structure and accessibility. This change makes the content more screen-reader friendly and follows HTML best practices.
46-54: Good simplification of text structure.The simplification of the closing text by removing nested
<Text>components makes the code more maintainable while preserving the same visual result.lib/emails/send-dataroom-trial-end.ts (1)
15-16: Subject tweak acknowledgedThe new subject line better matches branding. No further issues spotted.
lib/emails/send-hundred-views-congrats.ts (1)
7-20: LGTM! Clean email sending implementation.The function follows a consistent pattern with proper environment-based testing and React template integration. The structure is clean and maintainable.
components/emails/upgrade-six-month-checkin.tsx (1)
5-8: LGTM! Well-structured component interface.The props interface is clean with appropriate optionality and default values.
lib/emails/send-dataroom-trial-24h.ts (2)
11-13: Excellent documentation of scheduling requirements.The comments clearly explain the intended usage with cron jobs, which is helpful for implementation and maintenance.
14-25: LGTM! Consistent email sending implementation.The function follows the established pattern with proper error handling and environment-based testing.
lib/emails/send-custom-domain-setup.ts (2)
11-15: LGTM! Good default value handling.The default values are sensible and the template prop mapping is clean and readable.
17-27: LGTM! Consistent email sending implementation.The email sending logic follows the established pattern with proper error handling and environment-based testing.
components/emails/dataroom-trial-end.tsx (3)
47-54: LGTM! Improved semantic HTML structure.The change from individual Text components to a proper unordered list improves semantic structure and readability. The use of
<strong>tags for emphasis is also good for accessibility.
35-35: LGTM! Clear and consistent terminology.The update to "Data Room plan trial" provides better clarity than the previous "dataroom trial" terminology.
21-93: LGTM! Well-structured email template.The overall template structure is clean, uses appropriate React Email components, and follows good practices with proper styling and responsive design considerations.
components/emails/thousand-views-congrats.tsx (1)
1-44: LGTM! Well-structured congratulations email component.The component follows good practices with proper TypeScript typing, clean structure, and appropriate use of @react-email/components.
components/emails/dataroom-trial-24h.tsx (5)
10-10: LGTM! Proper import addition.The Link component import is correctly added to support the inline upgrade link functionality.
22-22: LGTM! Clear and specific preview text.The preview text is updated to specifically mention "Data Room Plan" which improves clarity over the generic approach.
35-44: LGTM! Improved user experience with inline upgrade link.The addition of an inline upgrade link within the text provides users with a convenient way to upgrade without scrolling to the button.
46-53: LGTM! More concise and focused feature list.The updated feature list is more concise and specifically highlights Data Room plan benefits, making it easier for users to understand the value proposition.
64-66: LGTM! More specific warning message.The warning message is now more specific about which features will be disabled (Dataroom links and advanced access controls), providing better clarity to users.
components/emails/custom-domain-setup.tsx (3)
17-41: LGTM! Well-designed conditional email component.The component uses good practices with default parameters, clear conditional logic, and appropriate content personalization based on user access levels.
62-73: LGTM! Clear plan information for users without access.The conditional section effectively communicates the different plan options and their custom domain capabilities, helping users understand upgrade paths.
76-84: LGTM! Helpful setup instructions.The ordered list provides clear, actionable steps for setting up custom domains, making the process less intimidating for users.
components/emails/slack-integration.tsx (1)
16-82: LGTM! Well-structured Slack integration promotion email.The component follows established patterns and effectively promotes the Slack integration feature with clear benefits and call-to-action.
components/emails/welcome.tsx (2)
39-41: LGTM! Improved founder introduction.The updated description of Marc as "founder" with expanded details about Papermark being an "open-source DocSend alternative" provides better context and credibility.
46-54: LGTM! More specific and actionable bullet points.The updated bullet points are clearer and more specific, replacing generic terms with actionable language like "Turn your documents into shareable links" and "secure virtual data rooms."
components/domains/add-domain-modal.tsx (5)
1-11: LGTM!The new imports are appropriate for the email functionality being added and are all utilized in the component.
46-50: LGTM!The state additions are well-structured:
emailSentRefproperly tracks email sending status- Session data access follows standard next-auth patterns
- Plan flag extensions support the new dataroom access checks
155-155: LGTM!Clean integration that triggers email sending when the upgrade button is clicked.
167-172: LGTM!Proper implementation of wrapping the state setter to trigger email sending when the upgrade modal opens, while preserving the original behavior.
181-189: LGTM!Consistent implementation with the upgrade modal pattern - properly triggers email sending when the main dialog opens while preserving original behavior.
| <a | ||
| href="https://www.papermark.com" | ||
| className="text-gray-400 no-underline hover:text-gray-400" | ||
| target="_blank" | ||
| > | ||
| papermark.com | ||
| </a> |
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.
Add security attributes to external link.
The external link uses target="_blank" without rel="noopener" or rel="noreferrer", which poses a security risk.
Apply this fix:
<a
href="https://www.papermark.com"
className="text-gray-400 no-underline hover:text-gray-400"
target="_blank"
+ rel="noopener noreferrer"
>📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| <a | |
| href="https://www.papermark.com" | |
| className="text-gray-400 no-underline hover:text-gray-400" | |
| target="_blank" | |
| > | |
| papermark.com | |
| </a> | |
| <a | |
| href="https://www.papermark.com" | |
| className="text-gray-400 no-underline hover:text-gray-400" | |
| target="_blank" | |
| rel="noopener noreferrer" | |
| > | |
| papermark.com | |
| </a> |
🧰 Tools
🪛 Biome (2.1.2)
[error] 68-68: Avoid using target="_blank" without rel="noopener" or rel="noreferrer".
Opening external links in new tabs without rel="noopener" is a security risk. See the explanation for more details.
Safe fix: Add the rel="noopener" attribute.
(lint/security/noBlankTarget)
🤖 Prompt for AI Agents
In components/emails/slack-integration.tsx around lines 65 to 71, the anchor tag
with target="_blank" lacks the rel="noopener noreferrer" attributes, which is a
security risk. Add rel="noopener noreferrer" to the anchor tag to prevent
potential security vulnerabilities when opening external links in a new tab.
| const emailTemplate = UpgradeCongratsEmail({ name: firstName, planName }); | ||
| try { | ||
| await sendEmail({ | ||
| to: email as string, |
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.
🛠️ Refactor suggestion
Remove unsafe type casting of email.
The type casting email as string bypasses TypeScript's safety checks. Consider validating email existence or updating the type definition.
- to: email as string,
+ to: email,If email can be undefined, add validation:
if (!email) {
throw new Error('Email is required');
}🤖 Prompt for AI Agents
In lib/emails/send-upgrade-personal-welcome.ts at line 20, remove the unsafe
type casting of email as string. Instead, add a validation check before this
line to ensure email is defined; if not, throw an error indicating that email is
required. This will maintain type safety without bypassing TypeScript checks.
| } catch (e) { | ||
| console.error(e); | ||
| } |
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.
🛠️ Refactor suggestion
Enhance error handling for production use.
Simply logging errors to console may not be sufficient for production monitoring. Consider throwing the error or returning a result object to inform callers of failures.
} catch (e) {
console.error(e);
+ throw new Error(`Failed to send upgrade congrats email: ${e instanceof Error ? e.message : 'Unknown error'}`);
}🤖 Prompt for AI Agents
In lib/emails/send-upgrade-personal-welcome.ts around lines 26 to 28, the
current error handling only logs errors to the console, which is insufficient
for production. Modify the catch block to either rethrow the caught error or
return a result object indicating failure, so that callers are properly informed
of the error and can handle it accordingly.
| @@ -0,0 +1,29 @@ | |||
| import SixMonthMilestoneEmail from "@/components/emails/upgrade-six-month-checkin"; | |||
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.
💡 Verification agent
🧩 Analysis chain
Verify import path matches actual component location.
Please confirm the import path @/components/emails/upgrade-six-month-checkin matches the actual component file location mentioned in the summary.
🏁 Script executed:
#!/bin/bash
# Verify the actual location of the SixMonthMilestoneEmail component
fd -t f "*six*month*" components/emails/
ast-grep --pattern "export default $_" components/emails/upgrade-six-month-checkin.tsx 2>/dev/null || echo "Component file not found at expected location"Length of output: 438
🏁 Script executed:
#!/bin/bash
# List all email component files under components/emails/ containing “month” in their names
fd -t f -e tsx components/emails/ | grep -i month || echo "No matching .tsx files with 'month' in name found"Length of output: 544
🏁 Script executed:
#!/bin/bash
# Find all .tsx files under components/emails/ whose filenames contain “month”
find components/emails/ -type f -iname '*month*.tsx' || echo "No matching .tsx files with 'month' in name found"Length of output: 150
Import Path Verified – No Changes Needed
File: lib/emails/send-upgrade-six-months-checkin.ts
Line 1
The import path
import SixMonthMilestoneEmail from "@/components/emails/upgrade-six-month-checkin";correctly resolves to
components/emails/upgrade-six-month-checkin.tsx. No updates are required for the import.
Please also address the recurring concerns shared across our upgrade-related email senders:
- Type Casting: eliminate unnecessary
ascasts by tightening your TypeScript definitions for the user and subscription objects. - Error Handling: wrap all async operations (e.g., sending the email) in try/catch blocks, log failures clearly, and surface retry logic when appropriate.
- Name Extraction: factor out the user-name parsing/formatting logic into a shared utility instead of duplicating it in each sender.
🤖 Prompt for AI Agents
In lib/emails/send-upgrade-six-months-checkin.ts at line 1, the import path is
correct and needs no change. However, to improve the code, remove unnecessary
TypeScript type casts by refining the user and subscription type definitions.
Wrap all asynchronous operations, such as sending emails, in try/catch blocks to
handle errors properly, log failures clearly, and implement retry logic if
needed. Additionally, extract any user name parsing or formatting logic into a
shared utility function to avoid duplication across upgrade-related email sender
files.
| const sixAndHalfMonthsFromNow = new Date(Date.now() + 1000 * 60 * 60 * 24 * 195).toISOString(); | ||
|
|
||
| // Get the first name from the full name | ||
| const firstName = name ? name.split(" ")[0] : null; |
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.
🛠️ Refactor suggestion
Apply same first name extraction improvement.
Same fragile name parsing issue.
- const firstName = name ? name.split(" ")[0] : null;
+ const firstName = name ? name.trim().split(/\s+/)[0] : null;📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const firstName = name ? name.split(" ")[0] : null; | |
| const firstName = name ? name.trim().split(/\s+/)[0] : null; |
🤖 Prompt for AI Agents
In lib/emails/send-upgrade-six-months-checkin.ts at line 15, the first name
extraction uses a simple split on space which is fragile. Improve this by
implementing a more robust method to extract the first name, such as trimming
whitespace and handling cases where the name might be empty or formatted
unexpectedly, to avoid errors or incorrect parsing.
| const emailTemplate = SixMonthMilestoneEmail({ name: firstName, planName }); | ||
| try { | ||
| await sendEmail({ | ||
| to: email as string, |
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.
🛠️ Refactor suggestion
Remove unsafe type casting of email.
Same type safety issue.
- to: email as string,
+ to: email,📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| to: email as string, | |
| to: email, |
🤖 Prompt for AI Agents
In lib/emails/send-upgrade-six-months-checkin.ts at line 20, remove the unsafe
type casting of the email variable using 'as string'. Instead, ensure the email
variable is properly validated or typed before this line to guarantee it is a
string, thereby maintaining type safety without forced casting.
|
Review the following changes in direct dependencies. Learn more about Socket for GitHub.
|
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: 4
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (3)
components/emails/dataroom-viewer-invitation.tsx (1)
37-37: Use font-sem ibold → font-sem ibold across all affected components
Typo prevents Tailwind’s font-sem ibold utility from applying. Replace everyclassName="font-seminbold…"withclassName="font-sem ibold…".Affected files:
- ee/features/conversations/emails/components/conversation-notification.tsx:39
- components/emails/dataroom-notification.tsx:39
- components/emails/upgrade-plan.tsx:77
- components/emails/team-invitation.tsx:39
- components/emails/deleted-domain.tsx:33
- components/emails/dataroom-viewer-invitation.tsx:37
- components/emails/dataroom-trial-end.tsx:34
- components/emails/dataroom-trial-24h.tsx:34
Diff example:
- <Text className="font-seminbold mx-0 mb-8 mt-4 p-0 text-center text-xl"> + <Text className="font-sem ibold mx-0 mb-8 mt-4 p-0 text-center text-xl">components/emails/upgrade-plan.tsx (1)
102-104: Bug: possible runtime crash when planTypeText is undefined.Using planTypeText.includes(...) will throw if planType isn’t in PLAN_TYPE_MAP. Use planType (raw) for the routing decision or guard with a fallback.
- href={`${process.env.NEXT_PUBLIC_BASE_URL}/${ - planTypeText.includes("datarooms") ? "datarooms" : "documents" - }`} + href={`${process.env.NEXT_PUBLIC_BASE_URL}/${ + planType.includes("datarooms") ? "datarooms" : "documents" + }`}package.json (1)
159-159: Align @types/node major with engines.nodeThis project’s
package.jsonspecifies"node": ">=18.18.0"but uses@types/node^22.13.5. That lets v22-only APIs compile against Node 18 (runtime break) or omits v24 APIs if you run on a newer Node. Confirmed no v20+ APIs are in use.
Option A: support Node 18 → pin typings to v18
- "@types/node": "^22.13.5", + "@types/node": "^18.19.48",Option B: drop Node 18 support → bump engines to v22
- "node": ">=18.18.0" + "node": ">=22.0.0"Option C: align with your current runtime (Node 24)
- "node": ">=18.18.0" + "node": ">=24.0.0" - "@types/node": "^22.13.5", + "@types/node": "^24.3.0",node -v npm pkg get engines.node npm pkg get dependencies["@types/node"]
♻️ Duplicate comments (7)
components/emails/upgrade-personal-welcome.tsx (1)
33-33: Fix double question mark typo.There's a typo with double question marks in the congratulations message.
Apply this diff to fix the typo:
- You now have access to advanced features. Any questions so far?? + You now have access to advanced features. Any questions so far?lib/emails/send-upgrade-personal-welcome.ts (3)
24-24: Improve first name extraction to handle edge cases.The current implementation will fail with names that have irregular spacing or are malformed.
- const firstName = name ? name.split(" ")[0] : null; + const firstName = name ? name.trim().split(/\s+/)[0] : null;
30-30: Remove unsafe type casting of email.The type assertion bypasses TypeScript's safety checks and could lead to runtime errors.
Add proper validation before the sendEmail call:
+ if (!email) { + throw new Error('Email is required for sending upgrade welcome email'); + } + try { await sendEmail({ - to: email as string, + to: email,
37-39: Enhance error handling for production use.Console logging alone is insufficient for production error monitoring and debugging.
} catch (e) { console.error(e); + throw new Error(`Failed to send upgrade personal welcome email: ${e instanceof Error ? e.message : 'Unknown error'}`); }lib/emails/send-upgrade-month-checkin.ts (1)
7-28: The function implementation is already correct.Based on the past review comments, the import path and type casting issues have been addressed in commits ffa0b1b to 3d064f9. The current implementation looks appropriate for the new one-month check-in email functionality.
components/emails/slack-integration.tsx (1)
66-71: Add rel attributes for security when using target="_blank".Prevents tab-nabbing; also silences the linter.
<a href="https://www.papermark.com" className="text-gray-400 no-underline" target="_blank" + rel="noopener noreferrer" >components/emails/welcome.tsx (1)
85-91: Add rel="noopener noreferrer" to external link.The “Customer stories” link opens a new tab but lacks security attributes.
Apply:
- <Link + <Link href="https://www.papermark.com/customers" target="_blank" - className="text-xs text-gray-400" + className="text-xs text-gray-400" + rel="noopener noreferrer" >
🧹 Nitpick comments (20)
components/emails/hundred-views-congrats.tsx (1)
1-50: Consider using the shared Footer component for consistency.While the current implementation works well, consider using the shared Footer component introduced in this PR to maintain consistency across all email templates.
You could replace the current footer-less approach with:
+import { Footer } from "./shared/footer"; // ... rest of component <Text> Thanks so much, <br /> Marc </Text> + <Footer footerText="Thanks for being a valued Papermark user!" /> </Body>components/emails/year-in-review-papermark.tsx (1)
269-269: Minor styling cleanupThe removal of hover styling from these anchor elements is a minor change that doesn't affect functionality. The links retain their base styling.
Also applies to: 282-282
components/emails/dataroom-viewer-invitation.tsx (2)
23-26: Align prop types with defaults.Props have defaults but are typed as required; consider making them optional for DX and preview rendering.
-}: { - dataroomName: string; - senderEmail: string; - url: string; -}) { +}: { + dataroomName?: string; + senderEmail?: string; + url?: string; +}) {
52-52: Minor: simplify href expression.- href={`${url}`} + href={url}lib/emails/send-dataroom-trial-24h.ts (1)
19-21: Prefer centralized logging; optionally propagate.Use the project logger for consistency; optionally rethrow to let callers handle failures.
- } catch (e) { - console.error(e); - } + } catch (e) { + console.error("Failed to send DataroomTrial24hReminderEmail:", e); + // rethrow if upstream should react + // throw e; + }components/emails/verification-email-change.tsx (1)
22-26: Match prop defaults with optional types.-interface ConfirmEmailChangeProps { - email: string; - newEmail: string; - confirmUrl: string; -} +interface ConfirmEmailChangeProps { + email?: string; + newEmail?: string; + confirmUrl?: string; +}components/emails/upgrade-plan.tsx (2)
37-37: Guard missing map entries.Provide a fallback label to avoid undefined in copy.
- const planTypeText = PLAN_TYPE_MAP[planType as keyof typeof PLAN_TYPE_MAP]; + const planTypeText = + PLAN_TYPE_MAP[planType as keyof typeof PLAN_TYPE_MAP] ?? "Pro";
38-66: Tighten typing; avoid any.Use a discriminated union and a typed Record to remove any and catch invalid planType at compile time.
-interface UpgradePlanEmailProps { - name: string | null | undefined; - planType: string; -} +type PlanType = "pro" | "business" | "datarooms" | "datarooms-plus"; +interface UpgradePlanEmailProps { + name: string | null | undefined; + planType: PlanType; +} - const features: any = { + const features: Record<PlanType, string[]> = {components/emails/export-ready.tsx (1)
20-24: Optional: make types optional to match defaults.- downloadUrl: string; - email: string; + downloadUrl?: string; + email?: string;components/emails/dataroom-trial-24h.tsx (1)
34-36: Tailwind typo: use font-semibold.- <Text className="font-seminbold mx-0 mb-8 mt-4 p-0 text-center text-xl"> + <Text className="font-semibold mx-0 mb-8 mt-4 p-0 text-center text-xl">components/emails/slack-integration.tsx (2)
42-45: Copy/punctuation fixes.“data roomviews” ➜ “data room views”; remove stray space before “!”.
- about document and data roomviews directly in your Slack channels - ! + about document and data room views directly in your Slack channels!
61-76: Optional: switch to shared Footer for consistency.Reduces duplication and aligns with other templates.
- <Hr /> - <Section className="mt-8 text-gray-400"> - <Text className="text-xs"> - © {new Date().getFullYear()}{" "} - <a - href="https://www.papermark.com" - className="text-gray-400 no-underline" - target="_blank" - > - papermark.com - </a> - </Text> - <Text className="text-xs"> - Feel free to always reach out to me or our support team. - </Text> - </Section> + <Footer footerText="Feel free to always reach out to me or our support team." />Add import at top:
+import { Footer } from "./shared/footer";components/emails/welcome.tsx (4)
58-64: Guard NEXT_PUBLIC_BASE_URL to avoid broken links.If the env var is unset, users may get an invalid href.
- href={`${process.env.NEXT_PUBLIC_BASE_URL}/welcome`} + href={`${process.env.NEXT_PUBLIC_BASE_URL ?? "https://app.papermark.com"}/welcome`}
48-56: Prefer email-friendly bullets over/
- .
Some email clients drop list styles. Use text rows with explicit bullets.
- <ul className="list-inside list-disc text-sm"> - <li>Turn your documents into shareable links</li> - <li>Create secure virtual data rooms</li> - <li> - Share your documents{" "} - <span className="italic">(with custom domain)✨</span> - </li> - <li>Watch the page-by-page insights in real-time</li> - </ul> + <Text className="text-sm">• Turn your documents into shareable links</Text> + <Text className="text-sm">• Create secure virtual data rooms</Text> + <Text className="text-sm"> + • Share your documents <span className="italic">(with custom domain)✨</span> + </Text> + <Text className="text-sm">• Watch the page-by-page insights in real-time</Text>
84-84: Avoid flex layout in email clients.display:flex is inconsistently supported; simplify for reliability.
- <Text className="flex gap-x-1 text-xs"> + <Text className="text-xs">
3-15: Remove unused import.
Hrisn’t used in this file.Body, Button, Container, Head, - Hr, Html, Link, Preview, Section, Tailwind, Text,components/emails/dataroom-notification.tsx (3)
39-39: Fix Tailwind class typo.Use
font-semibold.- <Text className="font-seminbold mb-8 mt-4 text-center text-xl"> + <Text className="font-semibold mb-8 mt-4 text-center text-xl">
63-85: Reuse the shared Footer instead of duplicating markup.Reduces duplication and keeps legal text consistent across templates.
- <Hr /> - <Section className="text-gray-400"> - <Text className="text-xs"> - © {new Date().getFullYear()} Papermark, Inc. All rights - reserved. - </Text> - <Text className="text-xs"> - You received this email from{" "} - <span className="font-semibold">{senderEmail}</span> because you - viewed the dataroom{" "} - <span className="font-semibold">{dataroomName}</span> on - Papermark. If you have any feedback or questions about this - email, simply reply to it. To unsubscribe from updates about - this dataroom,{" "} - <a - href={unsubscribeUrl} - className="text-gray-400 underline underline-offset-2" - > - click here - </a> - . - </Text> - </Section> + <Footer + footerText={ + <> + You received this email from{" "} + <span className="font-semibold">{senderEmail}</span> because you + viewed the dataroom{" "} + <span className="font-semibold">{dataroomName}</span> on Papermark. + {" "}To unsubscribe from updates about this dataroom,{" "} + <a + href={unsubscribeUrl} + className="text-gray-400 underline underline-offset-2" + > + click here + </a>. + </> + } + />Add import (outside this hunk):
+import { Footer } from "./shared/footer";
22-28: Tighten type fordocumentName.With a default value, the prop can be optional and typed as
string.-}: { - dataroomName: string; - documentName: string | undefined; - senderEmail: string; - url: string; - unsubscribeUrl: string; -}) { +}: { + dataroomName: string; + documentName?: string; + senderEmail: string; + url: string; + unsubscribeUrl: string; +}) {package.json (1)
24-24: Align AWS SDK minor versions for better dedupe
- Dependencies in package.json:
• @aws-sdk/client-lambda ^3.883.0
• @aws-sdk/client-s3 ^3.884.0
• @aws-sdk/lib-storage ^3.884.0
• @aws-sdk/s3-request-presigner ^3.884.0
• @aws-sdk/cloudfront-signer ^3.873.0
citeturn2run1- Searched for Edge/runtime markers (
Edge,edgeRuntime,export const runtime = 'edge') and found none—no Edge‐only bundling paths detected. citeturn2run1- Optional refactor: bump client-lambda and cloudfront-signer to ^3.884.0 to match the other v3 packages:
- "@aws-sdk/client-lambda": "^3.883.0", + "@aws-sdk/client-lambda": "^3.884.0", - "@aws-sdk/cloudfront-signer": "^3.873.0", + "@aws-sdk/cloudfront-signer": "^3.884.0",
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (1)
package-lock.jsonis excluded by!**/package-lock.json
📒 Files selected for processing (42)
components/emails/custom-domain-setup.tsx(1 hunks)components/emails/data-rooms-information.tsx(1 hunks)components/emails/dataroom-notification.tsx(5 hunks)components/emails/dataroom-trial-24h.tsx(1 hunks)components/emails/dataroom-trial-end.tsx(2 hunks)components/emails/dataroom-trial-welcome.tsx(1 hunks)components/emails/dataroom-viewer-invitation.tsx(2 hunks)components/emails/deleted-domain.tsx(2 hunks)components/emails/email-updated.tsx(2 hunks)components/emails/email-verification.tsx(0 hunks)components/emails/export-ready.tsx(2 hunks)components/emails/hundred-views-congrats.tsx(1 hunks)components/emails/invalid-domain.tsx(2 hunks)components/emails/onboarding-1.tsx(2 hunks)components/emails/onboarding-2.tsx(3 hunks)components/emails/onboarding-3.tsx(3 hunks)components/emails/onboarding-4.tsx(2 hunks)components/emails/shared/footer.tsx(1 hunks)components/emails/slack-integration.tsx(1 hunks)components/emails/team-invitation.tsx(2 hunks)components/emails/trial-end-final-reminder.tsx(0 hunks)components/emails/upgrade-one-month-checkin.tsx(1 hunks)components/emails/upgrade-personal-welcome.tsx(1 hunks)components/emails/upgrade-plan.tsx(5 hunks)components/emails/verification-email-change.tsx(2 hunks)components/emails/verification-link.tsx(2 hunks)components/emails/viewed-dataroom.tsx(2 hunks)components/emails/viewed-document.tsx(2 hunks)components/emails/welcome.tsx(3 hunks)components/emails/year-in-review-papermark.tsx(2 hunks)ee/stripe/webhooks/checkout-session-completed.ts(3 hunks)lib/emails/send-dataroom-info.ts(1 hunks)lib/emails/send-dataroom-trial-24h.ts(1 hunks)lib/emails/send-email-verification.ts(0 hunks)lib/emails/send-upgrade-month-checkin.ts(1 hunks)lib/emails/send-upgrade-personal-welcome.ts(1 hunks)lib/emails/send-upgrade-plan.ts(1 hunks)lib/resend.ts(2 hunks)lib/trigger/send-scheduled-email.ts(2 hunks)package.json(5 hunks)pages/api/stripe/webhook.ts(1 hunks)pages/api/teams/[teamId]/datarooms/trial.ts(2 hunks)
💤 Files with no reviewable changes (3)
- lib/emails/send-email-verification.ts
- components/emails/email-verification.tsx
- components/emails/trial-end-final-reminder.tsx
🚧 Files skipped from review as they are similar to previous changes (5)
- components/emails/viewed-dataroom.tsx
- components/emails/upgrade-one-month-checkin.tsx
- components/emails/onboarding-4.tsx
- components/emails/dataroom-trial-end.tsx
- components/emails/onboarding-1.tsx
🧰 Additional context used
📓 Path-based instructions (1)
**/trigger/**/*.ts
📄 CodeRabbit inference engine (.cursor/rules/rule-trigger-typescript.mdc)
**/trigger/**/*.ts: You MUST use@trigger.dev/sdk/v3when implementing Trigger.dev tasks.
You MUST NEVER useclient.defineJobin Trigger.dev task files, as it is deprecated and will break the application.
You MUSTexportevery task, including subtasks, in Trigger.dev task files.
If you are able to generate an example payload for a task, do so.
When implementing a Trigger.dev task, always use thetaskfunction from@trigger.dev/sdk/v3and follow the correct pattern for task definition.
When implementing scheduled (cron) tasks, useschedules.taskfrom@trigger.dev/sdk/v3and follow the correct pattern for schedule definition.
When implementing schema-validated tasks, useschemaTaskfrom@trigger.dev/sdk/v3and provide a schema using Zod or another supported library.
When triggering tasks from backend code, use thetasks.trigger,tasks.batchTrigger, ortasks.triggerAndPollmethods from@trigger.dev/sdk/v3and use type-only imports for type safety.
When triggering a task from inside another task, use the correct methods (trigger,batchTrigger,triggerAndWait,batchTriggerAndWait) on the task instance as shown in the guide.
When using metadata in tasks, use themetadataAPI from@trigger.dev/sdk/v3only inside run functions or task lifecycle hooks.
When using idempotency, use theidempotencyKeysAPI from@trigger.dev/sdk/v3and provide anidempotencyKeywhen triggering tasks.
When logging inside tasks, use theloggerAPI from@trigger.dev/sdk/v3and provide a message and a key-value object.
Files:
lib/trigger/send-scheduled-email.ts
🧠 Learnings (4)
📚 Learning: 2025-07-19T07:46:44.421Z
Learnt from: CR
PR: mfts/papermark#0
File: .cursor/rules/rule-trigger-typescript.mdc:0-0
Timestamp: 2025-07-19T07:46:44.421Z
Learning: Applies to **/trigger/**/*.ts : When implementing scheduled (cron) tasks, use `schedules.task` from `trigger.dev/sdk/v3` and follow the correct pattern for schedule definition.
Applied to files:
pages/api/teams/[teamId]/datarooms/trial.tslib/trigger/send-scheduled-email.ts
📚 Learning: 2025-07-19T07:46:44.421Z
Learnt from: CR
PR: mfts/papermark#0
File: .cursor/rules/rule-trigger-typescript.mdc:0-0
Timestamp: 2025-07-19T07:46:44.421Z
Learning: Applies to **/trigger/**/*.ts : When implementing a Trigger.dev task, always use the `task` function from `trigger.dev/sdk/v3` and follow the correct pattern for task definition.
Applied to files:
lib/trigger/send-scheduled-email.ts
📚 Learning: 2025-07-19T07:46:44.421Z
Learnt from: CR
PR: mfts/papermark#0
File: .cursor/rules/rule-trigger-typescript.mdc:0-0
Timestamp: 2025-07-19T07:46:44.421Z
Learning: Applies to **/trigger/**/*.ts : When triggering tasks from backend code, use the `tasks.trigger`, `tasks.batchTrigger`, or `tasks.triggerAndPoll` methods from `trigger.dev/sdk/v3` and use type-only imports for type safety.
Applied to files:
lib/trigger/send-scheduled-email.ts
📚 Learning: 2025-07-19T07:46:44.421Z
Learnt from: CR
PR: mfts/papermark#0
File: .cursor/rules/rule-trigger-typescript.mdc:0-0
Timestamp: 2025-07-19T07:46:44.421Z
Learning: Applies to **/trigger/**/*.ts : When implementing schema-validated tasks, use `schemaTask` from `trigger.dev/sdk/v3` and provide a schema using Zod or another supported library.
Applied to files:
lib/trigger/send-scheduled-email.ts
🧬 Code graph analysis (23)
components/emails/deleted-domain.tsx (1)
components/emails/shared/footer.tsx (1)
Footer(3-27)
components/emails/invalid-domain.tsx (1)
components/emails/shared/footer.tsx (1)
Footer(3-27)
pages/api/stripe/webhook.ts (3)
pages/api/teams/[teamId]/billing/upgrade.ts (1)
config(15-18)pages/api/teams/[teamId]/billing/manage.ts (1)
config(17-20)pages/api/stripe/webhook-old.ts (1)
config(13-17)
lib/emails/send-upgrade-month-checkin.ts (2)
lib/types.ts (1)
CreateUserEmailProps(19-24)lib/resend.ts (1)
sendEmail(12-96)
components/emails/team-invitation.tsx (1)
components/emails/shared/footer.tsx (1)
Footer(3-27)
components/emails/verification-link.tsx (1)
components/emails/shared/footer.tsx (1)
Footer(3-27)
components/emails/dataroom-viewer-invitation.tsx (1)
components/emails/shared/footer.tsx (1)
Footer(3-27)
components/emails/viewed-document.tsx (1)
components/emails/shared/footer.tsx (1)
Footer(3-27)
components/emails/custom-domain-setup.tsx (1)
components/emails/shared/footer.tsx (1)
Footer(3-27)
lib/emails/send-upgrade-plan.ts (2)
lib/types.ts (1)
CreateUserEmailProps(19-24)lib/resend.ts (1)
sendEmail(12-96)
ee/stripe/webhooks/checkout-session-completed.ts (3)
lib/emails/send-upgrade-plan.ts (1)
sendUpgradePlanEmail(13-32)lib/emails/send-upgrade-personal-welcome.ts (1)
sendUpgradePersonalEmail(14-40)lib/trigger/send-scheduled-email.ts (1)
sendUpgradeOneMonthCheckinEmailTask(114-150)
components/emails/upgrade-plan.tsx (1)
components/emails/shared/footer.tsx (1)
Footer(3-27)
lib/emails/send-upgrade-personal-welcome.ts (2)
lib/types.ts (1)
CreateUserEmailProps(19-24)lib/resend.ts (1)
sendEmail(12-96)
lib/emails/send-dataroom-trial-24h.ts (1)
lib/resend.ts (1)
sendEmail(12-96)
pages/api/teams/[teamId]/datarooms/trial.ts (1)
lib/trigger/send-scheduled-email.ts (2)
sendDataroomTrial24hReminderEmailTask(23-33)sendDataroomTrialExpiredEmailTask(35-112)
components/emails/email-updated.tsx (1)
components/emails/shared/footer.tsx (1)
Footer(3-27)
lib/emails/send-dataroom-info.ts (1)
lib/types.ts (1)
CreateUserEmailProps(19-24)
components/emails/dataroom-trial-24h.tsx (1)
components/emails/shared/footer.tsx (1)
Footer(3-27)
components/emails/welcome.tsx (1)
components/emails/shared/footer.tsx (1)
Footer(3-27)
components/emails/verification-email-change.tsx (1)
components/emails/shared/footer.tsx (1)
Footer(3-27)
components/emails/export-ready.tsx (1)
components/emails/shared/footer.tsx (1)
Footer(3-27)
components/emails/data-rooms-information.tsx (1)
components/emails/shared/footer.tsx (1)
Footer(3-27)
lib/trigger/send-scheduled-email.ts (3)
lib/emails/send-dataroom-info.ts (1)
sendDataroomInfoEmail(17-37)lib/emails/send-dataroom-trial-24h.ts (1)
sendDataroomTrial24hReminderEmail(5-22)lib/emails/send-upgrade-month-checkin.ts (1)
sendUpgradeOneMonthCheckinEmail(7-28)
🪛 Biome (2.1.2)
components/emails/slack-integration.tsx
[error] 68-68: Avoid using target="_blank" without rel="noopener" or rel="noreferrer".
Opening external links in new tabs without rel="noopener" is a security risk. See the explanation for more details.
Safe fix: Add the rel="noopener" attribute.
(lint/security/noBlankTarget)
🔇 Additional comments (66)
components/emails/onboarding-3.tsx (4)
20-20: LGTM! Simplified preview text.The direct string literal in the Preview component simplifies the code by removing the unnecessary previewText variable.
37-46: LGTM! Improved list structure and formatting.The changes enhance the readability by:
- Removing the unnecessary Text wrapper around the list
- Using semantic HTML with direct
ulelement- Adding proper emphasis to "each page 💫" with the
font-semiboldclass- Maintaining consistent styling
47-47: LGTM! Simplified margin classes.The change from
mb-[32px] mt-[32px]tomy-8uses a more concise Tailwind class while maintaining the same spacing.
62-63: LGTM! Consistent footer standardization.The change to plain text copyright aligns with the broader PR effort to standardize footers across email templates using the shared Footer component pattern.
components/emails/dataroom-trial-welcome.tsx (1)
15-15: LGTM! Improved conditional greeting.The conditional rendering
Hi{name &&${name}},properly handles cases where the name might be null/undefined, providing a graceful fallback to just "Hi," when no name is available.lib/resend.ts (2)
3-3: LGTM! Updated to use separate render functions.The import change from
@react-email/componentsto@react-email/renderaligns with the latest best practices for React Email.
44-45: LGTM! Improved plain text generation approach.The two-step approach (render HTML first, then extract plain text) provides better control over the rendering process and aligns with the updated React Email API patterns.
components/emails/onboarding-2.tsx (4)
20-20: LGTM! Simplified preview text.Direct string literal eliminates unnecessary variable declaration while maintaining the same functionality.
37-45: LGTM! Improved semantic structure.The changes enhance the email structure by:
- Using direct
ulelement instead of wrapping it in a Text component- Maintaining proper list semantics
- Keeping consistent styling
46-46: LGTM! Consistent margin standardization.The margin updates (
my-8and removal ofmt-8) create more consistent spacing throughout the email template.Also applies to: 60-60
62-63: LGTM! Footer standardization and cleanup.The changes align with the broader footer standardization effort:
- Plain text copyright instead of hyperlinked version
- Removed trailing whitespace from the feedback text
Also applies to: 67-67
components/emails/hundred-views-congrats.tsx (2)
22-22: LGTM! Consistent conditional greeting pattern.The greeting logic
Hi{name &&${name}},follows the same pattern established in other email templates, providing proper fallback when name is undefined.
29-35: LGTM! Proper external link styling.The G2 review link uses appropriate styling with
text-blue-500 underlineandtarget="_blank"for external navigation.components/emails/custom-domain-setup.tsx (5)
19-27: LGTM! Well-structured component interface.The component props are well-defined with appropriate defaults and clear typing. The optional parameters provide flexibility for different use cases.
28-43: LGTM! Clean conditional logic.The
getPlanInfohelper function provides clear separation of concerns and makes the conditional content easy to understand and maintain.
61-79: LGTM! Clear plan information presentation.The conditional rendering effectively explains the available plans and their custom domain capabilities when the user doesn't have access.
92-108: LGTM! Appropriate call-to-action based on access level.The conditional button rendering provides the right action for each user state - setup for those with access, upgrade for those without.
137-137: LGTM! Proper use of shared Footer component.The implementation correctly uses the shared Footer component with a contextually appropriate footer message, maintaining consistency across email templates.
components/emails/invalid-domain.tsx (2)
17-17: LGTM - Consistent footer migrationThe migration to the shared Footer component aligns with the broader refactoring pattern across the email templates in this PR.
82-82: LGTM - Footer component usageThe Footer component is used with default props, which will provide the standard copyright text and feedback message as intended.
components/emails/deleted-domain.tsx (2)
16-16: LGTM - Consistent footer refactoringThe import of the shared Footer component follows the same pattern as other email templates in this PR.
58-58: LGTM - Default footer implementationUsing the Footer component with default props provides consistent messaging across all domain-related emails.
pages/api/stripe/webhook.ts (1)
13-13: LGTM - Enable waitUntil for async email operationsThe addition of
supportsResponseStreaming: trueenables thewaitUntilfunction, which is consistent with other API routes in the codebase and allows for scheduling emails asynchronously without blocking the webhook response.Also applies to: 15-15
components/emails/verification-link.tsx (2)
15-15: LGTM - Footer component integrationThe import of the shared Footer component is consistent with the broader footer consolidation effort.
54-54: LGTM - Default footer usageThe Footer component is used with default props, providing consistent copyright and feedback messaging.
lib/emails/send-upgrade-plan.ts (3)
6-11: LGTM - Plan type mapping for user-friendly labelsThe
PLAN_TYPE_MAPprovides a clean way to convert internal plan identifiers to user-friendly display names. This improves the email subject line readability.
20-20: LGTM - Type-safe plan text derivationThe derivation of
planTypeTextuses proper type checking with thekeyof typeofpattern to ensure type safety.
25-25: LGTM - Dynamic subject generationUsing
planTypeTextin the email subject provides consistent, user-friendly labeling that aligns with the email template content.components/emails/viewed-document.tsx (2)
15-15: LGTM - Footer component importThe import of the shared Footer component follows the established pattern across the codebase.
71-82: LGTM - Customized footer with notification guidanceThe Footer component is properly used with a custom
footerTextthat includes both feedback messaging and specific guidance about stopping email notifications for the document link. The use of<br />elements for spacing is appropriate for email formatting.components/emails/email-updated.tsx (3)
14-14: LGTM! Proper use of shared Footer component.The import of the shared Footer component aligns well with the broader PR objective to standardize email footers across templates.
17-18: LGTM! Clear default values for email properties.The updated default values make the component more intuitive when used in development/testing scenarios.
52-52: LGTM! Footer replacement maintains consistency.The replacement of inline footer elements with the shared Footer component maintains the same visual structure while promoting code reuse across email templates.
components/emails/team-invitation.tsx (3)
16-16: LGTM! Consistent Footer component usage.The import aligns with the PR's standardization of email footers across templates.
22-22: LGTM! Sensible default URL.The default URL provides a practical fallback for the team invitation link.
64-64: LGTM! Footer standardization complete.The shared Footer component replaces the inline footer implementation, maintaining consistency across email templates.
pages/api/teams/[teamId]/datarooms/trial.ts (4)
11-11: LGTM! New 24-hour reminder task imported.The import of
sendDataroomTrial24hReminderEmailTaskis properly structured and follows the established pattern.
116-120: LGTM! Updated payload structure with useCase.The addition of
useCaseparameter enables personalized email content based on the user's specific use case, which enhances the user experience.
121-126: LGTM! New 24-hour reminder scheduling.The 24-hour reminder scheduled 6 days after trial start provides a helpful nudge before trial expiration. The payload structure is consistent with other tasks.
130-130: LGTM! Simplified delay format.The string-based delay format ("7d") is more readable and maintainable than Date-based calculations.
components/emails/shared/footer.tsx (1)
3-27: LGTM! Well-designed reusable Footer component.This component successfully standardizes email footers across templates with:
- Clean prop interface with sensible defaults
- Conditional address rendering via
withAddressprop- Flexible footer text customization
- Consistent styling and structure
The implementation promotes DRY principles and maintains consistency across email templates.
lib/trigger/send-scheduled-email.ts (4)
4-6: LGTM! Clean import organization.The import statements are properly structured and follow the established patterns for the codebase.
12-21: LGTM! Updated task with useCase parameter.The task now accepts a
useCaseparameter that enables personalized email content based on user preferences. The implementation follows the correct Trigger.dev v3 pattern.
23-33: LGTM! New 24-hour reminder task implementation.The new task follows the established patterns and provides proper error handling and logging. The implementation adheres to Trigger.dev v3 best practices.
114-150: LGTM! Comprehensive one-month check-in task.The task implementation includes:
- Proper team validation and error handling
- Plan verification to ensure appropriate targeting
- Comprehensive logging for debugging and monitoring
- Adherence to Trigger.dev v3 patterns
The business logic correctly prevents sending emails to teams that are no longer on paid plans.
ee/stripe/webhooks/checkout-session-completed.ts (3)
8-8: LGTM! waitUntil import for async scheduling.The import enables proper asynchronous email scheduling without blocking the webhook response.
115-147: LGTM! Improved email scheduling with waitUntil.The changes successfully:
- Replace synchronous email sending with asynchronous scheduling using
waitUntil- Add a 40-day delayed check-in email for better customer engagement
- Maintain proper error handling and logging
The use of
waitUntilensures the webhook responds quickly while emails are processed in the background.
106-106: Fix timestamp comparison logic.The current condition has the comparison logic inverted. Events older than 1 hour should be rejected, not recent events.
Apply this diff to fix the timestamp comparison:
- if (event.created < Date.now() / 1000 - 1 * 60 * 60) { + if (event.created < Date.now() / 1000 - 3600) {The logic should check if the event was created more than 1 hour ago (3600 seconds).
Likely an incorrect or invalid review comment.
lib/emails/send-dataroom-info.ts (4)
3-3: LGTM! Updated import for new email template.The import change from onboarding-5 to data-rooms-information reflects the updated email template structure.
7-15: LGTM! Well-structured use case mapping.The
USECASE_SUBJECTSmapping provides clear, descriptive subject lines tailored to different business contexts. This enhances the personalization of dataroom information emails.
17-20: LGTM! Enhanced function signature with useCase parameter.The addition of the
useCaseparameter enables dynamic subject line selection based on the user's specific business context.
23-25: LGTM! Dynamic subject selection implementation.The implementation correctly uses the useCase parameter to select appropriate subject lines, with proper type safety through the keyof typeof pattern.
lib/emails/send-upgrade-month-checkin.ts (1)
1-5: LGTM! Clean import structure.The imports are properly organized and follow the established patterns in the codebase.
components/emails/dataroom-viewer-invitation.tsx (1)
16-16: Good move: centralized footer adoption.Consistent with the new shared Footer; reduces duplication.
Also applies to: 63-66
components/emails/verification-email-change.tsx (1)
14-14: Footer migration looks solid.Also applies to: 64-74
components/emails/upgrade-plan.tsx (1)
118-118: Footer adoption LGTM.components/emails/export-ready.tsx (2)
53-57: Markup switch to list is good.Clearer semantics for export details.
63-73: Footer integration LGTM.components/emails/dataroom-trial-24h.tsx (1)
84-84: Footer usage is consistent.components/emails/data-rooms-information.tsx (1)
17-85: Overall content and CTA structure LGTM.components/emails/welcome.tsx (2)
71-79: Nice: security attributes added to external CTA.
rel="noopener noreferrer"is correctly included.
83-83: Adopting shared Footer improves consistency.Good move centralizing legal copy and styling.
components/emails/dataroom-notification.tsx (1)
16-28: Defaults are great for previews; confirm they’re not used in production sends.Accidentally emailing “Example Data Room/Document” would be confusing. Consider runtime guard or telemetry when defaults are used.
package.json (3)
128-128: Resend v6 upgrade: confirm API surface changes and full TS type-safety
Ensure all send utilities (e.g.resend.emails.send,resend.batch.send) and template rendering calls compile cleanly under strict TypeScript. Install the TS compiler if missing (npm install typescript --save-dev) and runnpx tsc --noEmitto catch any type/shape mismatches introduced by Resend v6.
97-97: lucide-react bump: confirm importsEnsure
typescriptis installed and runnpm run typecheck(ornpx tsc --noEmit) to catch any brokenimport … from 'lucide-react'statements caused by renamed or removed icons. Pin the dependency with an upper bound (e.g.^0.543.0 <=0.543.x) until all icons are migrated.
60-61: React Email CLI and peer dependencies verified and correct.Peer dependencies for
@react-email/[email protected]and@react-email/[email protected]are satisfied by React 18.3.1/React-DOM 18.3.1, and thereact-emailpackage’s CLI (no conflicting bin in@react-email/preview-server).
| <Footer | ||
| withAddress={false} | ||
| footerText="This is a last onboarding email. If you have any feedback or questions about this email, simply reply to it. I'd love to hear from you!" | ||
| /> |
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.
💡 Verification agent
🧩 Analysis chain
Consider including address for marketing emails.
If this is marketing/onboarding, pass withAddress={true} and include an unsubscribe link via sendEmail’s unsubscribeUrl for compliance.
- <Footer
- withAddress={false}
+ <Footer
+ withAddress={true}
footerText="This is a last onboarding email. If you have any feedback or questions about this email, simply reply to it. I'd love to hear from you!"
/>To audit:
🏁 Script executed:
#!/bin/bash
# Find emails likely marketing without address
rg -n "Data Rooms|onboarding|Upgrade|Slack" components/emails -C1
rg -n "withAddress={false}" components/emails -n
rg -n "unsubscribeUrl" lib/emails -nLength of output: 10333
Enable mailing address and unsubscribe link for compliance
In components/emails/data-rooms-information.tsx, change
- <Footer withAddress={false} …
+ <Footer withAddress={true} …and update the sending logic to pass unsubscribeUrl (from your send-email helper) so the Footer renders an unsubscribe link.
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| <Footer | |
| withAddress={false} | |
| footerText="This is a last onboarding email. If you have any feedback or questions about this email, simply reply to it. I'd love to hear from you!" | |
| /> | |
| <Footer | |
| withAddress={true} | |
| footerText="This is a last onboarding email. If you have any feedback or questions about this email, simply reply to it. I'd love to hear from you!" | |
| /> |
🤖 Prompt for AI Agents
In components/emails/data-rooms-information.tsx around lines 76–79, the Footer
is rendered with withAddress={false} and no unsubscribe link; change withAddress
to true and add an unsubscribeUrl prop (e.g. unsubscribeUrl={unsubscribeUrl}) so
the Footer can render the mailing address and unsubscribe link, and then update
the send-email/sending logic that renders this component to pass the
unsubscribeUrl value obtained from the send-email helper into the component
props.
| <Section className="mb-[32px] mt-[32px] text-center"> | ||
| <Button | ||
| className="rounded bg-black text-center text-xs font-semibold text-white no-underline" | ||
| href={`${process.env.NEXT_PUBLIC_BASE_URL}/settings/integrations`} |
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.
💡 Verification agent
🧩 Analysis chain
Verify env var fallback for links.
If NEXT_PUBLIC_BASE_URL is unset in the email rendering env, link becomes “undefined/...”. Consider defaulting to https://app.papermark.com.
🏁 Script executed:
#!/bin/bash
rg -n "process\.env\.NEXT_PUBLIC_BASE_URL" -C1Length of output: 8524
Add fallback for NEXT_PUBLIC_BASE_URL in Slack integration email link – use
href={`${process.env.NEXT_PUBLIC_BASE_URL || 'https://app.papermark.com'}/settings/integrations`}to prevent undefined/... when the env var is unset (components/emails/slack-integration.tsx:49).
🤖 Prompt for AI Agents
In components/emails/slack-integration.tsx around line 49 the href uses
NEXT_PUBLIC_BASE_URL directly which can be undefined and produce URLs like
"undefined/..."; change the expression to use a fallback base URL (for example
'https://app.papermark.com') when process.env.NEXT_PUBLIC_BASE_URL is falsy so
the href always builds a valid absolute URL to /settings/integrations.
| const emailTemplate = DataroomTrial24hReminderEmail({ name }); | ||
| try { | ||
| await sendEmail({ | ||
| to: email, | ||
| subject: "Your Data Room plan trial expires in 24 hours", | ||
| react: emailTemplate, | ||
| test: process.env.NODE_ENV === "development", | ||
| }); |
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.
💡 Verification agent
🧩 Analysis chain
Confirm sender classification and compliance headers.
Is this lifecycle/system or marketing? If system, set system: true for the proper from-address. If marketing, pass marketing: true and an unsubscribeUrl to populate List-Unsubscribe; consider Footer with address in the template.
await sendEmail({
to: email,
subject: "Your Data Room plan trial expires in 24 hours",
react: emailTemplate,
+ system: true,
test: process.env.NODE_ENV === "development",
});To audit current usage:
🏁 Script executed:
#!/bin/bash
# Where do we already set system/marketing or unsubscribe headers?
rg -nP 'sendEmail\(\{[^}]*\b(system|marketing|unsubscribeUrl)\s*:' -C2 lib/emailsLength of output: 0
🏁 Script executed:
#!/bin/bash
# Locate sendEmail implementation to inspect its parameters
rg -nP 'export (async )?function sendEmail' -C5 .
# Locate the SendEmailOptions/interface to see if system, marketing, unsubscribeUrl are supported
rg -nP 'interface SendEmailOptions' -C5 .Length of output: 0
🏁 Script executed:
#!/bin/bash
# Find all call sites of sendEmail
rg -nH 'sendEmail\(' -C2 .
# Find where sendEmail is imported
rg -nH 'import.*sendEmail' -C2 .
# Search for the sendEmail implementation/export
rg -nH 'export .*sendEmail' -C2 .
# Search for unsubscribeUrl usage
rg -nH 'unsubscribeUrl' -C2 .
# Look for types or interfaces related to sendEmail parameters
rg -nH 'type .*SendEmail.*' -C2 .Length of output: 34753
🏁 Script executed:
#!/bin/bash
sed -n '1,200p' lib/resend.tsLength of output: 2374
Add system: true to the 24h trial reminder email
Ensures this lifecycle notification is sent from the system address.
lib/emails/send-dataroom-trial-24h.ts
await sendEmail({
to: email,
subject: "Your Data Room plan trial expires in 24 hours",
react: emailTemplate,
+ system: true,
test: process.env.NODE_ENV === "development",
});📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const emailTemplate = DataroomTrial24hReminderEmail({ name }); | |
| try { | |
| await sendEmail({ | |
| to: email, | |
| subject: "Your Data Room plan trial expires in 24 hours", | |
| react: emailTemplate, | |
| test: process.env.NODE_ENV === "development", | |
| }); | |
| const emailTemplate = DataroomTrial24hReminderEmail({ name }); | |
| try { | |
| await sendEmail({ | |
| to: email, | |
| subject: "Your Data Room plan trial expires in 24 hours", | |
| react: emailTemplate, | |
| system: true, | |
| test: process.env.NODE_ENV === "development", | |
| }); |
🤖 Prompt for AI Agents
In lib/emails/send-dataroom-trial-24h.ts around lines 11 to 18, the sendEmail
call for the 24h trial reminder is missing the system flag; update the options
passed to sendEmail to include system: true so the email is sent from the system
address (add system: true alongside to, subject, react, and test).
| "pdf-lib": "^1.17.1", | ||
| "postcss": "^8.5.6", | ||
| "posthog-js": "^1.261.7", | ||
| "posthog-js": "^1.262.0", |
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.
💡 Verification agent
🧩 Analysis chain
posthog-js bump: confirm SSR-safe import paths.
Ensure client-only initialization (e.g., inside useEffect or guarded dynamic import) to avoid SSR crashes with newer builds.
If imported in shared modules, move initialization to a client component and pass a thin wrapper via context.
🏁 Script executed:
#!/bin/bash
rg -nP "posthog-js" -C2
rg -nP "useEffect\(|typeof window" -g '!**/node_modules/**' | wc -lLength of output: 1962
Guard posthog-js initialization for SSR. Top-level imports in lib/analytics/index.ts and components/providers/posthog-provider.tsx execute during server-side rendering and can crash with v1.262.0. Move all posthog setup into a client-only context—for example inside a useEffect or behind a typeof window !== 'undefined' check—or dynamically import the PostHog provider with SSR disabled.
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 (4)
components/emails/upgrade-six-month-checkin.tsx (1)
30-32: Fix grammar and tighten the closing sentence.Update "since you using" and drop "for us" for natural phrasing.
- Marc here. It's been 6 months since you using advanced - Papermark features! Excited to hear your story and feedback for us. + Marc here. It's been 6 months since you started using advanced + Papermark features! Excited to hear your story and feedback.package.json (1)
113-113: Guard PostHog for SSR (repeat).Top-level or server imports can break with newer posthog-js. Ensure initialization only runs client-side (e.g., inside useEffect or dynamic import with ssr: false).
components/emails/slack-integration.tsx (2)
49-49: Add fallback for base URL to avoidundefined/...links.In preview/production without NEXT_PUBLIC_BASE_URL, this becomes invalid. Provide a safe default.
Apply:
- href={`${process.env.NEXT_PUBLIC_BASE_URL}/settings/integrations`} + href={`${process.env.NEXT_PUBLIC_BASE_URL || "https://app.papermark.com"}/settings/integrations`}
68-69: Harden external link opened in new tab.Add
rel="noopener noreferrer"withtarget="_blank".Apply:
- target="_blank" + target="_blank" + rel="noopener noreferrer"
🧹 Nitpick comments (14)
components/emails/upgrade-six-month-checkin.tsx (4)
27-27: Guard against missing names to avoid “Hi ,”.Provide a safe fallback.
- <Text>Hi {name},</Text> + <Text>Hi {name?.trim() || "there"},</Text>
22-22: Set document language for accessibility and better client hints.Add lang attribute on the root Html.
- <Html> + <Html lang="en">
26-26: Improve readability in email clients with explicit line-height.Adds consistent spacing across clients.
- <Body className="font-sans text-sm"> + <Body className="font-sans text-sm leading-6">
30-32: Optional personalization: reference the user’s plan.You already accept planName; consider using it to increase relevance.
- Marc here. It's been 6 months since you started using advanced - Papermark features! Excited to hear your story and feedback. + Marc here. It's been 6 months since you upgraded to the {planName} plan! + Excited to hear your story and feedback.pages/view/[linkId]/index.tsx (3)
328-333: Harden title fallback to avoid empty or “undefined | Powered by Papermark”.Guard against blank metaTitle and missing document name.
- meta.metaTitle ?? `${link?.document?.name} | Powered by Papermark` + meta.metaTitle?.trim() || + (link?.document?.name + ? `${link?.document?.name} | Powered by Papermark` + : "Papermark")Repeat the same change for Line 372.
Also applies to: 372-377
408-413: Apply the same robust fallback for dataroom titles.Prevents “undefined | Powered by Papermark” during loading or missing names.
- meta.metaTitle ?? `${link?.dataroom?.name} | Powered by Papermark` + meta.metaTitle?.trim() || + (link?.dataroom?.name + ? `${link?.dataroom?.name} | Powered by Papermark` + : "Papermark")Repeat the same change for Line 452.
Also applies to: 452-457
328-333: DRY: centralize title-building.This logic repeats across multiple pages. Consider a tiny helper (e.g., buildTitle(metaTitle, name)) or push this into CustomMetaTag.
pages/view/domains/[domain]/[slug]/index.tsx (2)
321-326: Document titles: add safe, user-friendly fallback.Same rationale—avoid empty or “undefined” titles.
- meta.metaTitle ?? `${link?.document?.name} | Powered by Papermark` + meta.metaTitle?.trim() || + (link?.document?.name + ? `${link?.document?.name} | Powered by Papermark` + : "Papermark")Repeat for Line 364.
Also applies to: 364-369
399-404: Dataroom titles: mirror the robust fallback.- meta.metaTitle ?? `${link?.dataroom?.name} | Powered by Papermark` + meta.metaTitle?.trim() || + (link?.dataroom?.name + ? `${link?.dataroom?.name} | Powered by Papermark` + : "Papermark")Repeat for Line 443.
Also applies to: 443-448
pages/view/domains/[domain]/[slug]/d/[documentId].tsx (1)
107-113: Dataroom document titles: ensure graceful fallback.Avoids blank/undefined titles during load or absent names.
- meta.metaTitle ?? - `${link?.dataroomDocument?.document?.name} | Powered by Papermark` + meta.metaTitle?.trim() || + (link?.dataroomDocument?.document?.name + ? `${link?.dataroomDocument?.document?.name} | Powered by Papermark` + : "Papermark")Apply to both occurrences.
Also applies to: 152-158
pages/view/[linkId]/d/[documentId].tsx (1)
107-112: Dataroom document titles: consistent, safe fallback.- meta.metaTitle ?? - `${link?.dataroomDocument?.document?.name} | Powered by Papermark` + meta.metaTitle?.trim() || + (link?.dataroomDocument?.document?.name + ? `${link?.dataroomDocument?.document?.name} | Powered by Papermark` + : "Papermark")Apply to both occurrences.
Also applies to: 152-157
components/emails/slack-integration.tsx (3)
21-21: Polish copy: capitalize Slack in preview.Apply:
- const previewText = `See who viewed your documents in slack in 2 clicks`; + const previewText = `See who viewed your documents in Slack in 2 clicks`;
42-45: Fix typo and punctuation in body copy.“data roomviews” → “data room views” and tidy punctuation.
Apply:
- With our Slack integration, you can get real-time notifications - about document and data roomviews directly in your Slack channels - ! + With our Slack integration, you can get real-time notifications + about document and data room views directly in your Slack channels!
62-77: Prefer shared Footer component for consistency.This PR introduces a centralized footer; use it here to avoid duplication and ensure brand/legal consistency across emails.
Apply within this section:
- <Section className="mt-8 text-gray-400"> - <Text className="text-xs"> - © {new Date().getFullYear()}{" "} - <a - href="https://www.papermark.com" - className="text-gray-400 no-underline" - target="_blank" - > - papermark.com - </a> - </Text> - <Text className="text-xs"> - Feel free to always reach out to me or our support team. - </Text> - </Section> + <Footer />Add the import (outside the selected lines):
import Footer from "./shared/footer";
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (1)
package-lock.jsonis excluded by!**/package-lock.json
📒 Files selected for processing (14)
components/emails/custom-domain-setup.tsx(1 hunks)components/emails/dataroom-trial-24h.tsx(1 hunks)components/emails/hundred-views-congrats.tsx(1 hunks)components/emails/shared/footer.tsx(1 hunks)components/emails/slack-integration.tsx(1 hunks)components/emails/thousand-views-congrats.tsx(1 hunks)components/emails/upgrade-personal-welcome.tsx(1 hunks)components/emails/upgrade-six-month-checkin.tsx(1 hunks)ee/limits/constants.ts(3 hunks)package.json(6 hunks)pages/view/[linkId]/d/[documentId].tsx(2 hunks)pages/view/[linkId]/index.tsx(4 hunks)pages/view/domains/[domain]/[slug]/d/[documentId].tsx(2 hunks)pages/view/domains/[domain]/[slug]/index.tsx(4 hunks)
🚧 Files skipped from review as they are similar to previous changes (6)
- components/emails/dataroom-trial-24h.tsx
- components/emails/custom-domain-setup.tsx
- components/emails/hundred-views-congrats.tsx
- components/emails/upgrade-personal-welcome.tsx
- components/emails/shared/footer.tsx
- components/emails/thousand-views-congrats.tsx
🧰 Additional context used
🪛 Biome (2.1.2)
components/emails/slack-integration.tsx
[error] 68-68: Avoid using target="_blank" without rel="noopener" or rel="noreferrer".
Opening external links in new tabs without rel="noopener" is a security risk. See the explanation for more details.
Safe fix: Add the rel="noopener" attribute.
(lint/security/noBlankTarget)
🔇 Additional comments (6)
ee/limits/constants.ts (1)
60-62: Clarify “unlimited” semantics and keep them consistent.Top-of-file comment says “null means unlimited,” but here you’ve set finite numbers. If any plan is intended to be unlimited, use
null; otherwise keep this consistent across all plans and update the comment if semantics changed.package.json (5)
159-159: Node types vs engines mismatch.
@types/node22.x with engines >=18.18 can cause ambient type mismatches (fs/promises, fetch types). Align types to your runtime LTS (or settypesVersions/skipLibCheckif intentional).
78-79: @vercel/functions v3: confirmed supported APIs
ipAddress, geolocation, and waitUntil remain available in v3.1.0 with no deprecated exports detected.
60-62: React Email stack upgrades: test all templates and resend render
- lib/resend.ts calls render(…) and toPlainText(…); verify these still produce correct HTML + plain-text
- There are ~40 TSX templates under components/emails/*; run npm run email and manually preview each to catch prop or Tailwind changes
128-129: Resend v6 API usage confirmedAll used send params (from, to, cc, replyTo, subject, text, react, scheduledAt, headers) match the v6 Node.js SDK signature (npmjs.com, resend.com); v6.0.0’s only breaking change (inlineContentId → contentId) applies solely to attachments, which aren’t used here (dependabot.ecosyste.ms)
23-27: AWS SDK v3 minor upgrade: validate presigner & Upload behavior
You don’t inject any custom middleware, but you’re using getSignedUrl in your S3 endpoints (get-presigned-post/get URLs, multipart) and the Upload class for chunked/streaming uploads. After bumping to 3.886.0, verify that presigned-URL generation, multipart upload retries, chunk handling, and default timeouts haven’t regressed.
| fileSizeLimits: { | ||
| maxFiles: 500, | ||
| }, |
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.
🛠️ Refactor suggestion
Add fileSizeLimits to the type and normalize across plans.
fileSizeLimits exists on some plans but isn’t declared in TPlanLimits, which risks silent drift and weak typing. Make it an optional nested type and (ideally) include it on all plans for a consistent shape (use null for “unlimited” per the file’s header note).
Proposed type additions (outside the selected lines):
type TFileSizeLimits = {
maxFiles: number | null;
maxPages?: number | null;
};
export type TPlanLimits = {
// ...
fileSizeLimits?: TFileSizeLimits;
conversationsInDataroom?: boolean | null; // see Plus plan below
};Optionally enforce shape via satisfies:
export const BUSINESS_PLAN_LIMITS = {
// ...
fileSizeLimits: { maxFiles: 500 },
} as const satisfies TPlanLimits;🤖 Prompt for AI Agents
In ee/limits/constants.ts around lines 46-48, the fileSizeLimits object exists
on some plans but TPlanLimits doesn't declare it; add a new TFileSizeLimits type
(with maxFiles: number | null and optional maxPages?: number | null), add
fileSizeLimits?: TFileSizeLimits to the exported TPlanLimits type, and update
all plan constant objects to include a fileSizeLimits key (use null for
unlimited per the file header note) so plan shapes are consistent; optionally
add `as const satisfies TPlanLimits` to enforce the shape at compile time.
| fileSizeLimits: { | ||
| maxFiles: 5000, | ||
| maxPages: 1000, | ||
| }, |
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.
🛠️ Refactor suggestion
Type gap: conversationsInDataroom isn’t in TPlanLimits.
DATAROOMS_PLUS_PLAN_LIMITS includes conversationsInDataroom, but the type doesn’t. Add it (optional) to prevent accidental drops or typos and to enable satisfies TPlanLimits checks.
Additionally, consider adding fileSizeLimits to Free/Pro (even as { maxFiles: 50 } / null etc.) so consumers can safely access nested keys without per-plan guards.
🤖 Prompt for AI Agents
In ee/limits/constants.ts around lines 75-78, the DATAROOMS_PLUS_PLAN_LIMITS
object includes conversationsInDataroom but the TPlanLimits type lacks this
property; update the TPlanLimits type to include an optional
conversationsInDataroom?: number so the constant can satisfy TPlanLimits and
prevent accidental omissions or typos. Also add fileSizeLimits to the Free and
Pro plan objects (e.g., a minimal shape like { maxFiles: 50 } or null-consistent
values) so consumers can safely access nested keys without per-plan guards, and
run the TypeScript satisfies checks to ensure all plans conform to the updated
TPlanLimits shape.
Summary by CodeRabbit
New Features
Enhancements
Removals