Skip to content

Refactor email manager with improved utilities#12

Open
ShashankFC wants to merge 1 commit intomainfrom
refactor/email-manager-cleanup
Open

Refactor email manager with improved utilities#12
ShashankFC wants to merge 1 commit intomainfrom
refactor/email-manager-cleanup

Conversation

@ShashankFC
Copy link
Collaborator

@ShashankFC ShashankFC commented Nov 25, 2025

This PR refactors the email manager to improve error handling, adds a reusable helper function for email metadata checks, and includes new email utility functions for validation and formatting.


EntelligenceAI PR Summary

This PR refactors email management by consolidating email sending logic and adding reusable utility functions for improved maintainability and security.

  • Introduced shouldSendEmail() helper function in email-manager.ts to consolidate recipient-based email sending logic
  • Enhanced error handling in sendEmail() to properly reject promises with actual error objects
  • Created new emailHelpers.ts utility file with five helper functions: email validation, content sanitization, attendee formatting, rate limiting placeholder, and domain extraction
  • All new utilities include comprehensive JSDoc documentation and proper TypeScript typing
  • Replaced direct calls to disable email functions with the new consolidated helper

@entelligence-ai-pr-reviews
Copy link

Entelligence AI Vulnerability Scanner

Status: No security vulnerabilities found

Your code passed our comprehensive security analysis.

Analyzed 2 files in total

@entelligence-ai-pr-reviews
Copy link

Review Summary

🏷️ Draft Comments (3)

Skipped posting 3 draft comments that were valid but scored below your review threshold (>=13/15). Feel free to update them here.

packages/emails/email-manager.ts (2)

51-52: sendEmail resolves with a Promise from email.sendEmail(), but if sendEmail() throws synchronously, the rejection is not handled, potentially causing unhandled promise rejections.

📊 Impact Scores:

  • Production Impact: 4/5
  • Fix Specificity: 5/5
  • Urgency Impact: 3/5
  • Total Score: 12/15

🤖 AI Agent Prompt (Copy & Paste Ready):

In packages/emails/email-manager.ts, lines 51-52, the sendEmail function resolves with email.sendEmail(), but if email.sendEmail() throws synchronously, the rejection is not handled, which can cause unhandled promise rejections. Update these lines so that any synchronous or asynchronous errors from email.sendEmail() are properly caught and rejected. Use Promise.resolve(email.sendEmail()).then(resolve, reject); to ensure all errors are handled.

54-54: console.error in sendEmail (line 54) is a synchronous side effect in a Promise executor and can cause performance issues in high-throughput environments; use structured logging instead.

📊 Impact Scores:

  • Production Impact: 3/5
  • Fix Specificity: 5/5
  • Urgency Impact: 2/5
  • Total Score: 10/15

🤖 AI Agent Prompt (Copy & Paste Ready):

Replace the `console.error` statement with the project's logger in packages/emails/email-manager.ts at line 54. Problem: Using `console.error` in production code can cause performance and observability issues. Change it to use `logger.error` for structured logging.

packages/emails/lib/emailHelpers.ts (1)

44-53: formatAttendeeList will throw a runtime error if any attendee object is missing the name property, causing a crash when accessing attendees[0].name.

📊 Impact Scores:

  • Production Impact: 3/5
  • Fix Specificity: 5/5
  • Urgency Impact: 2/5
  • Total Score: 10/15

🤖 AI Agent Prompt (Copy & Paste Ready):

In packages/emails/lib/emailHelpers.ts, lines 44-53, the function `formatAttendeeList` assumes every attendee object has a `name` property, which can cause a runtime error if any attendee is missing `name`. Update the code to safely access `name` using optional chaining and provide a fallback like "Unknown attendee" if `name` is missing.

@entelligence-ai-pr-reviews
Copy link

🔬 Multi-Approach Review Summary

This PR was reviewed by 2 different approaches for comparison:

  • 🟢 Standard Reviewer: 0 comments
  • 🟠 LangGraph v3: 2 comments

Total: 2 review comments

Each comment is labeled with its source approach. This allows you to compare different AI review strategies.

🔒 Security Scan: Run once and shared across all approaches for efficiency.

Walkthrough

This PR refactors the email management system to improve code maintainability and error handling. The main change introduces a new shouldSendEmail() helper function that consolidates email sending logic based on metadata and recipient type. Error handling in the sendEmail() function is enhanced to properly reject promises with actual error objects. Additionally, a new utility file emailHelpers.ts is created with five reusable helper functions for email validation, content sanitization, attendee formatting, rate limiting preparation, and domain extraction. All new utilities include comprehensive JSDoc documentation and proper TypeScript typing.

Changes

File(s) Summary
packages/emails/email-manager.ts Introduced shouldSendEmail() helper function to consolidate email sending logic for attendees and hosts; improved error handling in sendEmail() to properly reject promises with error objects; updated error message to be more generic; replaced direct calls to eventTypeDisableAttendeeEmail() and eventTypeDisableHostEmail() with the new helper function in _sendScheduledEmailsAndSMS().
packages/emails/lib/emailHelpers.ts Created new utility file with five email helper functions: isValidEmail() for regex-based validation, sanitizeEmailContent() for XSS prevention, formatAttendeeList() for human-readable formatting with pluralization, shouldRateLimitEmail() as a placeholder for rate limiting, and extractEmailDomain() for domain parsing; includes comprehensive JSDoc documentation and TypeScript typing.

Sequence Diagram

This diagram shows the interactions between components:

sequenceDiagram
    participant Caller
    participant SSES as "_sendScheduledEmailsAndSMS"
    participant SSE as "shouldSendEmail"
    participant SE as "sendEmail"
    participant Email as "Email Object"

    Caller->>SSES: _sendScheduledEmailsAndSMS(calEvent, eventNameObject)
    activate SSES
    
    Note over SSES: Format calendar event
    
    alt Host email check
        SSES->>SSE: shouldSendEmail(metadata, 'host')
        activate SSE
        SSE->>SSE: Check eventTypeDisableHostEmail(metadata)
        SSE-->>SSES: return boolean
        deactivate SSE
        
        alt Should send host email
            SSES->>SE: sendEmail(() => new OrganizerScheduledEmail())
            activate SE
            SE->>Email: prepare()
            Email-->>SE: email object
            SE->>Email: sendEmail()
            Email-->>SE: result
            SE-->>SSES: Promise
            deactivate SE
        end
    end
    
    alt Attendee email check
        SSES->>SSE: shouldSendEmail(metadata, 'attendee')
        activate SSE
        SSE->>SSE: Check eventTypeDisableAttendeeEmail(metadata)
        SSE-->>SSES: return boolean
        deactivate SSE
        
        alt Should send attendee emails
            loop For each attendee
                SSES->>SE: sendEmail(() => new AttendeeScheduledEmail())
                activate SE
                SE->>Email: prepare()
                Email-->>SE: email object
                SE->>Email: sendEmail()
                alt Email preparation fails
                    Email-->>SE: throw error
                    Note over SE: Log error and reject
                end
                Email-->>SE: result
                SE-->>SSES: Promise
                deactivate SE
            end
        end
    end
    
    Note over SSES: Wait for all email promises
    SSES-->>Caller: Complete
    deactivate SSES
Loading

🔒 Security Analysis

  • Vulnerabilities: 0
  • Bugs: 0
  • Code Smells: 2
  • Security Hotspots: 0

▶️AI Code Reviews for VS Code, Cursor, Windsurf
Install the extension

Note for Windsurf Please change the default marketplace provider to the following in the windsurf settings:

Marketplace Extension Gallery Service URL: https://marketplace.visualstudio.com/_apis/public/gallery

Marketplace Gallery Item URL: https://marketplace.visualstudio.com/items

Entelligence.ai can learn from your feedback. Simply add 👍 / 👎 emojis to teach it your preferences. More shortcuts below

Emoji Descriptions:

  • ⚠️ Potential Issue - May require further investigation.
  • 🔒 Security Vulnerability - Fix to ensure system safety.
  • 💻 Code Improvement - Suggestions to enhance code quality.
  • 🔨 Refactor Suggestion - Recommendations for restructuring code.
  • ℹ️ Others - General comments and information.

Interact with the Bot:

  • Send a message or request using the format:
    @entelligenceai + *your message*
Example: @entelligenceai Can you suggest improvements for this code?
  • Help the Bot learn by providing feedback on its responses.
    @entelligenceai + *feedback*
Example: @entelligenceai Do not comment on `save_auth` function !

Also you can trigger various commands with the bot by doing
@entelligenceai command

The current supported commands are

  1. config - shows the current config
  2. retrigger_review - retriggers the review

More commands to be added soon.

Comment on lines 51 to 58
const email = prepare();
resolve(email.sendEmail());
} catch (e) {
reject(console.error(`${prepare.constructor.name}.sendEmail failed`, e));
console.error(`Email preparation failed`, e);
reject(e);
}
});
};

Choose a reason for hiding this comment

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

Correctness: 🟠 [LangGraph v3] Replace console.error with a logging utility to avoid exposing sensitive information.

📝 Committable Code Suggestion

‼️ Ensure you review the code suggestion before committing it to the branch. Make sure it replaces the highlighted code, contains no missing lines, and has no issues with indentation.

Suggested change
const email = prepare();
resolve(email.sendEmail());
} catch (e) {
reject(console.error(`${prepare.constructor.name}.sendEmail failed`, e));
console.error(`Email preparation failed`, e);
reject(e);
}
});
};
try {
const email = prepare();
resolve(email.sendEmail());
} catch (e) {
loggingUtility.error('Email preparation failed', { error: e });
reject(e);
}

Comment on lines +1 to +80
import type { CalendarEvent } from "@calcom/types/Calendar";

/**
* Email helper utilities for common email operations
*/

/**
* Validates email address format
* @param email - Email address to validate
* @returns boolean indicating if email is valid
*/
export function isValidEmail(email: string): boolean {
if (!email) return false;

const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
return emailRegex.test(email);
}

/**
* Sanitizes email content to prevent injection
* @param content - Content to sanitize
* @returns Sanitized content
*/
export function sanitizeEmailContent(content: string): string {
if (!content) return "";

return content
.replace(/<script[^>]*>.*?<\/script>/gi, "")
.replace(/<iframe[^>]*>.*?<\/iframe>/gi, "")
.replace(/javascript:/gi, "")
.trim();
}

/**
* Formats attendee list for email display
* @param attendees - Array of attendee objects
* @returns Formatted string of attendee names
*/
export function formatAttendeeList(attendees: CalendarEvent["attendees"]): string {
if (!attendees || attendees.length === 0) {
return "No attendees";
}

if (attendees.length === 1) {
return attendees[0].name;
}

if (attendees.length === 2) {
return `${attendees[0].name} and ${attendees[1].name}`;
}

const firstAttendees = attendees.slice(0, 2).map(a => a.name).join(", ");
return `${firstAttendees}, and ${attendees.length - 2} other${attendees.length - 2 > 1 ? "s" : ""}`;
}

/**
* Checks if an email should be rate limited
* @param recipientEmail - Email address of recipient
* @param eventType - Type of email event
* @returns boolean indicating if email should be sent
*/
export function shouldRateLimitEmail(recipientEmail: string, eventType: string): boolean {
// Placeholder for rate limiting logic
// In production, this would check against a rate limit store
return false;
}

/**
* Extracts domain from email address
* @param email - Email address
* @returns Domain portion of email
*/
export function extractEmailDomain(email: string): string | null {
if (!isValidEmail(email)) {
return null;
}

const parts = email.split("@");
return parts.length === 2 ? parts[1] : null;
}

Choose a reason for hiding this comment

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

Correctness: 🟠 [LangGraph v3] The recipientEmail and eventType parameters in shouldRateLimitEmail are defined but never used, leading to unnecessary code clutter. Consider removing these parameters if they are not needed.

📝 Committable Code Suggestion

‼️ Ensure you review the code suggestion before committing it to the branch. Make sure it replaces the highlighted code, contains no missing lines, and has no issues with indentation.

Suggested change
import type { CalendarEvent } from "@calcom/types/Calendar";
/**
* Email helper utilities for common email operations
*/
/**
* Validates email address format
* @param email - Email address to validate
* @returns boolean indicating if email is valid
*/
export function isValidEmail(email: string): boolean {
if (!email) return false;
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
return emailRegex.test(email);
}
/**
* Sanitizes email content to prevent injection
* @param content - Content to sanitize
* @returns Sanitized content
*/
export function sanitizeEmailContent(content: string): string {
if (!content) return "";
return content
.replace(/<script[^>]*>.*?<\/script>/gi, "")
.replace(/<iframe[^>]*>.*?<\/iframe>/gi, "")
.replace(/javascript:/gi, "")
.trim();
}
/**
* Formats attendee list for email display
* @param attendees - Array of attendee objects
* @returns Formatted string of attendee names
*/
export function formatAttendeeList(attendees: CalendarEvent["attendees"]): string {
if (!attendees || attendees.length === 0) {
return "No attendees";
}
if (attendees.length === 1) {
return attendees[0].name;
}
if (attendees.length === 2) {
return `${attendees[0].name} and ${attendees[1].name}`;
}
const firstAttendees = attendees.slice(0, 2).map(a => a.name).join(", ");
return `${firstAttendees}, and ${attendees.length - 2} other${attendees.length - 2 > 1 ? "s" : ""}`;
}
/**
* Checks if an email should be rate limited
* @param recipientEmail - Email address of recipient
* @param eventType - Type of email event
* @returns boolean indicating if email should be sent
*/
export function shouldRateLimitEmail(recipientEmail: string, eventType: string): boolean {
// Placeholder for rate limiting logic
// In production, this would check against a rate limit store
return false;
}
/**
* Extracts domain from email address
* @param email - Email address
* @returns Domain portion of email
*/
export function extractEmailDomain(email: string): string | null {
if (!isValidEmail(email)) {
return null;
}
const parts = email.split("@");
return parts.length === 2 ? parts[1] : null;
}
import type { CalendarEvent } from "@calcom/types/Calendar";
/**
* Email helper utilities for common email operations
*/
/**
* Validates email address format
* @param email - Email address to validate
* @returns boolean indicating if email is valid
*/
export function isValidEmail(email: string): boolean {
if (!email) return false;
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
return emailRegex.test(email);
}
/**
* Sanitizes email content to prevent injection
* @param content - Content to sanitize
* @returns Sanitized content
*/
export function sanitizeEmailContent(content: string): string {
if (!content) return "";
return content
.replace(/<script[^>]*>.*?<\/script>/gi, "")
.replace(/<iframe[^>]*>.*?<\/iframe>/gi, "")
.replace(/javascript:/gi, "")
.trim();
}
/**
* Formats attendee list for email display
* @param attendees - Array of attendee objects
* @returns Formatted string of attendee names
*/
export function formatAttendeeList(attendees: CalendarEvent["attendees"]): string {
if (!attendees || attendees.length === 0) {
return "No attendees";
}
if (attendees.length === 1) {
return attendees[0].name;
}
if (attendees.length === 2) {
return `${attendees[0].name} and ${attendees[1].name}`;
}
const firstAttendees = attendees.slice(0, 2).map(a => a.name).join(", ");
return `${firstAttendees}, and ${attendees.length - 2} other${attendees.length - 2 > 1 ? "s" : ""}`;
}
/**
* Checks if an email should be rate limited
* @returns boolean indicating if email should be sent
*/
export function shouldRateLimitEmail(): boolean {
// Placeholder for rate limiting logic
// In production, this would check against a rate limit store
return false;
}
/**
* Extracts domain from email address
* @param email - Email address
* @returns Domain portion of email
*/
export function extractEmailDomain(email: string): string | null {
if (!isValidEmail(email)) {
return null;
}
const parts = email.split("@");
return parts.length === 2 ? parts[1] : null;
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant