Skip to content

Conversation

@shnai0
Copy link
Collaborator

@shnai0 shnai0 commented Aug 4, 2025

Summary by CodeRabbit

  • New Features

    • Several new user-facing email templates (custom domain setup, Slack integration, Data Rooms info, dataroom trial reminders, upgrade welcome/check-ins, 100/1,000-views congrats).
  • Enhancements

    • Unified reusable email footer, clearer copy and CTAs, improved scheduling for delayed/scheduled emails, onboarding content tweaks, and modal now sends first-time custom-domain setup email only once per open.
  • Removals

    • Removed legacy trial-final reminder and standalone email verification sender.

@shnai0 shnai0 requested a review from mfts as a code owner August 4, 2025 18:12
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Aug 4, 2025

Walkthrough

Adds 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

Cohort / File(s) Change Summary
Add-domain modal & custom-domain flow
components/domains/add-domain-modal.tsx, components/emails/custom-domain-setup.tsx, lib/emails/send-custom-domain-setup.ts
Send first-time custom-domain setup email when modal opens or upgrade interactions occur; add email template and sender utility; ensure send-once per modal lifecycle.
New email templates
components/emails/hundred-views-congrats.tsx, components/emails/thousand-views-congrats.tsx, components/emails/slack-integration.tsx, components/emails/upgrade-one-month-checkin.tsx, components/emails/upgrade-personal-welcome.tsx, components/emails/upgrade-six-month-checkin.tsx, components/emails/dataroom-trial-24h.tsx, components/emails/data-rooms-information.tsx
Add multiple new React email templates for milestones, integrations, upgrades, and dataroom info/trial.
Shared footer & footer integrations
New: components/emails/shared/footer.tsx
Updated: many components/emails/*.tsx (e.g., viewed-document.tsx, viewed-dataroom.tsx, deleted-domain.tsx, invalid-domain.tsx, dataroom-viewer-invitation.tsx, etc.)
Introduce reusable Footer component; replace numerous inline footer blocks; some templates receive default prop values and minor markup tweaks.
Email senders / schedulers
lib/emails/send-hundred-views-congrats.ts, lib/emails/send-thousand-views-congrats.ts, lib/emails/send-slack-integration.ts, lib/emails/send-upgrade-personal-welcome.ts, lib/emails/send-upgrade-month-checkin.ts, lib/emails/send-upgrade-six-months-checkin.ts, lib/emails/send-dataroom-trial-24h.ts, lib/emails/send-dataroom-info.ts
Add sender modules for new templates; some schedule delayed sends; send-dataroom-info gains useCase param and dynamic subjects.
Removed trial email modules
components/emails/trial-end-final-reminder.tsx (deleted), lib/emails/send-trial-end-final-reminder.ts (deleted), lib/emails/send-trial-end-reminder.ts (deleted)
Remove deprecated trial reminder template(s) and corresponding sender functions.
Onboarding & template refactors
components/emails/onboarding-*.tsx, components/emails/welcome.tsx, components/emails/export-ready.tsx, components/emails/upgrade-plan.tsx, components/emails/dataroom-notification.tsx, components/emails/dataroom-viewer-invitation.tsx, components/emails/email-updated.tsx, components/emails/verification-email-change.tsx, components/emails/verification-link.tsx, components/emails/viewed-*.*
Markup and copy adjustments (lists, margins, wording), default props added in places, footers consolidated to shared Footer, minor UI text tweaks.
Render/send plumbing & deps
lib/resend.ts, package.json
Switch to @react-email/render usage (render HTML then derive plain text via toPlainText); bump/add email/render dependencies and other dependency updates.
Webhook / scheduling / trigger updates
ee/stripe/webhooks/checkout-session-completed.ts, pages/api/stripe/webhook.ts, lib/trigger/send-scheduled-email.ts, pages/api/teams/[teamId]/datarooms/trial.ts
Fix event-age units check; schedule emails via waitUntil/tasks (string delays used); add scheduled tasks (dataroom 24h reminder, upgrade one-month checkin); enable supportsResponseStreaming for webhook route.
Deleted cron route
app/api/cron/upgrade/route.ts
Remove daily cron route that handled trial reminder scheduling and downgrades (file deleted).
EE limits
ee/limits/constants.ts
Add per-plan fileSizeLimits to Business/Data Rooms/Data Rooms Plus plans.
View page title fallbacks
pages/view/** (multiple)
Remove unreachable final fallback strings from meta title fallbacks; simplify title expressions.

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
Loading
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
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~40 minutes

Possibly related PRs

Pre-merge checks (1 passed, 2 warnings)

❌ Failed checks (2 warnings)
Check name Status Explanation Resolution
Title Check ⚠️ Warning The pull request title “feat: emails updates and new emails” is overly generic and doesn’t identify the main changes, only indicating that email files were updated or added. It lacks specificity about what was changed, such as the introduction of custom domain setup emails or the overhaul of multiple email templates. As a result, it’s difficult for a teammate to understand the focus of the PR at a glance. Rename the PR title to clearly reflect the primary change, for example “feat: add custom domain setup email and update existing email templates” so reviewers can immediately grasp the intent and scope.
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch emails

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@vercel
Copy link

vercel bot commented Aug 4, 2025

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

Project Deployment Preview Comments Updated (UTC)
papermark Ready Ready Preview Comment Sep 10, 2025 9:41pm

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 22

🧹 Nitpick comments (25)
components/emails/export-ready.tsx (1)

51-55: List styling OK – verify long resourceName wrapping

break-all on 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 each
components/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

Onboarding5Email now 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 component

Same naming-mismatch concern as in send-dataroom-info.ts: Onboarding5Email now 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 snippet

The 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 text

See 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 name to 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 string is unnecessary since the email field from CreateUserEmailProps should 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>
+            Marc
lib/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:

  1. Code duplication: The custom domain access logic duplicates the conditions from lines 137-143
  2. 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

📥 Commits

Reviewing files that changed from the base of the PR and between 268dca6 and a815ac5.

📒 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 good

The 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 readability

Moving 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 emails

The 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 acknowledged

The 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:

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

Comment on lines 65 to 71
<a
href="https://www.papermark.com"
className="text-gray-400 no-underline hover:text-gray-400"
target="_blank"
>
papermark.com
</a>
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

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.

Suggested change
<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,
Copy link
Contributor

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.

Comment on lines +26 to +28
} catch (e) {
console.error(e);
}
Copy link
Contributor

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";
Copy link
Contributor

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 as casts 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;
Copy link
Contributor

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.

Suggested change
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,
Copy link
Contributor

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.

Suggested change
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.

@socket-security
Copy link

socket-security bot commented Sep 10, 2025

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 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 every className="font-seminbold…" with className="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.node

This project’s package.json specifies "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 cleanup

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

    Hr isn’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 for documentName.

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
    citeturn2run1
  • Searched for Edge/runtime markers (Edge, edgeRuntime, export const runtime = 'edge') and found none—no Edge‐only bundling paths detected. citeturn2run1
  • 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

📥 Commits

Reviewing files that changed from the base of the PR and between a815ac5 and ce8b6cc.

⛔ Files ignored due to path filters (1)
  • package-lock.json is 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/v3 when implementing Trigger.dev tasks.
You MUST NEVER use client.defineJob in Trigger.dev task files, as it is deprecated and will break the application.
You MUST export every 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 the task function from @trigger.dev/sdk/v3 and follow the correct pattern for task definition.
When implementing scheduled (cron) tasks, use schedules.task from @trigger.dev/sdk/v3 and follow the correct pattern for schedule definition.
When implementing schema-validated tasks, use schemaTask from @trigger.dev/sdk/v3 and provide a schema using Zod or another supported library.
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.
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 the metadata API from @trigger.dev/sdk/v3 only inside run functions or task lifecycle hooks.
When using idempotency, use the idempotencyKeys API from @trigger.dev/sdk/v3 and provide an idempotencyKey when triggering tasks.
When logging inside tasks, use the logger API from @trigger.dev/sdk/v3 and 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.ts
  • 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 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 ul element
  • Adding proper emphasis to "each page 💫" with the font-semibold class
  • Maintaining consistent styling

47-47: LGTM! Simplified margin classes.

The change from mb-[32px] mt-[32px] to my-8 uses 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/components to @react-email/render aligns 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 ul element 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-8 and removal of mt-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 underline and target="_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 getPlanInfo helper 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 migration

The migration to the shared Footer component aligns with the broader refactoring pattern across the email templates in this PR.


82-82: LGTM - Footer component usage

The 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 refactoring

The import of the shared Footer component follows the same pattern as other email templates in this PR.


58-58: LGTM - Default footer implementation

Using 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 operations

The addition of supportsResponseStreaming: true enables the waitUntil function, 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 integration

The import of the shared Footer component is consistent with the broader footer consolidation effort.


54-54: LGTM - Default footer usage

The 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 labels

The PLAN_TYPE_MAP provides 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 derivation

The derivation of planTypeText uses proper type checking with the keyof typeof pattern to ensure type safety.


25-25: LGTM - Dynamic subject generation

Using planTypeText in 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 import

The import of the shared Footer component follows the established pattern across the codebase.


71-82: LGTM - Customized footer with notification guidance

The Footer component is properly used with a custom footerText that 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 sendDataroomTrial24hReminderEmailTask is properly structured and follows the established pattern.


116-120: LGTM! Updated payload structure with useCase.

The addition of useCase parameter 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 withAddress prop
  • 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 useCase parameter 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 waitUntil ensures 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_SUBJECTS mapping 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 useCase parameter 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 run npx tsc --noEmit to catch any type/shape mismatches introduced by Resend v6.


97-97: lucide-react bump: confirm imports

Ensure typescript is installed and run npm run typecheck (or npx tsc --noEmit) to catch any broken import … 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 the email script unambiguously invokes the react-email package’s CLI (no conflicting bin in @react-email/preview-server).

Comment on lines +76 to +79
<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!"
/>
Copy link
Contributor

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 -n

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

Suggested change
<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`}
Copy link
Contributor

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" -C1

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

Comment on lines +11 to +18
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",
});
Copy link
Contributor

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/emails

Length 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.ts

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

Suggested change
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",
Copy link
Contributor

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 -l

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

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

♻️ Duplicate comments (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&apos;s been 6 months since you using advanced
-            Papermark features! Excited to hear your story and feedback for us.
+            Marc here. It&apos;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 avoid undefined/... 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" with target="_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&apos;s been 6 months since you started using advanced
-            Papermark features! Excited to hear your story and feedback.
+            Marc here. It&apos;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

📥 Commits

Reviewing files that changed from the base of the PR and between 80e9606 and a97ac80.

⛔ Files ignored due to path filters (1)
  • package-lock.json is 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/node 22.x with engines >=18.18 can cause ambient type mismatches (fs/promises, fetch types). Align types to your runtime LTS (or set typesVersions/skipLibCheck if 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 confirmed

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

Comment on lines +46 to +48
fileSizeLimits: {
maxFiles: 500,
},
Copy link
Contributor

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.

Comment on lines +75 to +78
fileSizeLimits: {
maxFiles: 5000,
maxPages: 1000,
},
Copy link
Contributor

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.

@mfts mfts merged commit bedfd39 into main Sep 10, 2025
9 checks passed
@github-actions github-actions bot locked and limited conversation to collaborators Sep 10, 2025
@mfts mfts deleted the emails branch September 16, 2025 14:39
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants