Skip to content

Comments

Comprehensive workflow reminder management for booking lifecycle events#6

Open
everettbu wants to merge 1 commit intoworkflow-queue-basefrom
workflow-queue-enhanced
Open

Comprehensive workflow reminder management for booking lifecycle events#6
everettbu wants to merge 1 commit intoworkflow-queue-basefrom
workflow-queue-enhanced

Conversation

@everettbu
Copy link

Test 6

…re still sent (#7232)

* small UI fix

* fix cancelling scheduled emails

* improve comments

* delete reminders for rescheduled bookings

* add migration file

* cancel rescheduled bookings immediately

* remove immediate delete for request reschedule

---------

Co-authored-by: CarinaWolli <wollencarina@gmail.com>
Copy link

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

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

Greptile Summary

This PR implements a comprehensive workflow reminder management system for booking lifecycle events, introducing a significant architectural change from immediate deletion to soft-deletion patterns for workflow reminders. The core enhancement involves adding a cancelled boolean field to the WorkflowReminder database schema and refactoring reminder management across multiple components.

The key changes include:

  1. Database Schema Evolution: A new migration adds an optional cancelled field to the WorkflowReminder table, enabling soft-deletion patterns that preserve audit trails while supporting proper cleanup of external services.

  2. Enhanced Reminder Managers: Both email and SMS reminder managers (emailReminderManager.ts and smsReminderManager.ts) have been updated to accept reminder IDs and support more sophisticated cancellation strategies. The email manager now supports both immediate deletion and soft-deletion via the cancelled field, while the SMS manager ensures database cleanup occurs regardless of external API success.

  3. Centralized Cancellation Logic: The workflow reminder cancellation logic has been consolidated across multiple files (handleNewBooking.ts, handleCancelBooking.ts, bookings.tsx, workflows.tsx) to use the updated reminder manager functions, removing manual database operations and complex batch processing.

  4. Cron-based Cleanup System: A new automated cleanup mechanism in scheduleEmailReminders.ts processes reminders marked as cancelled within a 1-hour window, cancelling them via SendGrid API and removing them from the database.

This refactoring centralizes reminder management logic, improves data consistency, and provides better handling of external service integrations (SendGrid for emails, Twilio for SMS) while maintaining comprehensive audit trails of workflow reminder lifecycle events.

Confidence score: 2/5

• This PR has significant implementation issues that could cause runtime errors and performance problems
• Critical problems include incorrect Prisma typing, potential race conditions with forEach async operations, and missing error handling that could break booking flows
• Files needing attention: packages/features/ee/workflows/api/scheduleEmailReminders.ts, packages/trpc/server/routers/viewer/workflows.tsx, packages/features/ee/workflows/components/WorkflowStepContainer.tsx

10 files reviewed, 7 comments

Edit Code Review Bot Settings | Greptile

});

try {
const workflowRemindersToDelete: Prisma.Prisma__WorkflowReminderClient<WorkflowReminder, never>[] = [];
Copy link

Choose a reason for hiding this comment

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

syntax: Incorrect type definition. Should be Promise<WorkflowReminder>[] instead of the Prisma client type

Suggested change
const workflowRemindersToDelete: Prisma.Prisma__WorkflowReminderClient<WorkflowReminder, never>[] = [];
const workflowRemindersToDelete: Promise<WorkflowReminder>[] = [];

Comment on lines +56 to +64
for (const reminder of remindersToCancel) {
await client.request({
url: "/v3/user/scheduled_sends",
method: "POST",
body: {
batch_id: reminder.referenceId,
status: "cancel",
},
});
Copy link

Choose a reason for hiding this comment

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

logic: Missing null check for reminder.referenceId - this will cause SendGrid API errors if referenceId is null

Comment on lines +966 to +972
originalRescheduledBooking.workflowReminders.forEach((reminder) => {
if (reminder.method === WorkflowMethods.EMAIL) {
deleteScheduledEmailReminder(reminder.id, reminder.referenceId, true);
} else if (reminder.method === WorkflowMethods.SMS) {
deleteScheduledSMSReminder(reminder.id, reminder.referenceId);
}
});
Copy link

Choose a reason for hiding this comment

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

style: The reminder cancellation logic should await the deletion operations to ensure they complete before proceeding with the reschedule

Comment on lines +573 to 580
//cancel all workflow reminders from steps that were edited
remindersToUpdate.forEach(async (reminder) => {
if (reminder.referenceId) {
if (reminder.method === WorkflowMethods.EMAIL) {
deleteScheduledEmailReminder(reminder.referenceId);
} else if (reminder.method === WorkflowMethods.SMS) {
deleteScheduledSMSReminder(reminder.referenceId);
}
if (reminder.method === WorkflowMethods.EMAIL) {
deleteScheduledEmailReminder(reminder.id, reminder.referenceId);
} else if (reminder.method === WorkflowMethods.SMS) {
deleteScheduledSMSReminder(reminder.id, reminder.referenceId);
}
await ctx.prisma.workflowReminder.deleteMany({
where: {
id: reminder.id,
},
});
});
Copy link

Choose a reason for hiding this comment

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

logic: Using forEach with async functions can cause race conditions and doesn't wait for completion. Consider using Promise.all() with map() or a for...of loop to ensure proper async handling.

Suggested change
//cancel all workflow reminders from steps that were edited
remindersToUpdate.forEach(async (reminder) => {
if (reminder.referenceId) {
if (reminder.method === WorkflowMethods.EMAIL) {
deleteScheduledEmailReminder(reminder.referenceId);
} else if (reminder.method === WorkflowMethods.SMS) {
deleteScheduledSMSReminder(reminder.referenceId);
}
if (reminder.method === WorkflowMethods.EMAIL) {
deleteScheduledEmailReminder(reminder.id, reminder.referenceId);
} else if (reminder.method === WorkflowMethods.SMS) {
deleteScheduledSMSReminder(reminder.id, reminder.referenceId);
}
await ctx.prisma.workflowReminder.deleteMany({
where: {
id: reminder.id,
},
});
});
//cancel all workflow reminders from steps that were edited
await Promise.all(remindersToUpdate.map(async (reminder) => {
if (reminder.method === WorkflowMethods.EMAIL) {
deleteScheduledEmailReminder(reminder.id, reminder.referenceId);
} else if (reminder.method === WorkflowMethods.SMS) {
deleteScheduledSMSReminder(reminder.id, reminder.referenceId);
}
}));

Comment on lines 212 to 218
scheduledReminders.forEach((reminder) => {
if (reminder.referenceId) {
if (reminder.method === WorkflowMethods.EMAIL) {
deleteScheduledEmailReminder(reminder.referenceId);
} else if (reminder.method === WorkflowMethods.SMS) {
deleteScheduledSMSReminder(reminder.referenceId);
}
if (reminder.method === WorkflowMethods.EMAIL) {
deleteScheduledEmailReminder(reminder.id, reminder.referenceId, true);
} else if (reminder.method === WorkflowMethods.SMS) {
deleteScheduledSMSReminder(reminder.id, reminder.referenceId);
}
});
Copy link

Choose a reason for hiding this comment

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

logic: The forEach calls to delete reminders are not awaited, which could lead to the workflow being deleted before all reminders are properly cancelled. This could leave orphaned scheduled reminders.

Suggested change
scheduledReminders.forEach((reminder) => {
if (reminder.referenceId) {
if (reminder.method === WorkflowMethods.EMAIL) {
deleteScheduledEmailReminder(reminder.referenceId);
} else if (reminder.method === WorkflowMethods.SMS) {
deleteScheduledSMSReminder(reminder.referenceId);
}
if (reminder.method === WorkflowMethods.EMAIL) {
deleteScheduledEmailReminder(reminder.id, reminder.referenceId, true);
} else if (reminder.method === WorkflowMethods.SMS) {
deleteScheduledSMSReminder(reminder.id, reminder.referenceId);
}
});
await Promise.all(scheduledReminders.map(async (reminder) => {
if (reminder.method === WorkflowMethods.EMAIL) {
deleteScheduledEmailReminder(reminder.id, reminder.referenceId, true);
} else if (reminder.method === WorkflowMethods.SMS) {
deleteScheduledSMSReminder(reminder.id, reminder.referenceId);
}
}));

Comment on lines +213 to +222
if (immediateDelete) {
await client.request({
url: "/v3/user/scheduled_sends",
method: "POST",
body: {
batch_id: referenceId,
status: "cancel",
},
});
return;
Copy link

Choose a reason for hiding this comment

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

logic: When immediateDelete is true, only the SendGrid cancellation happens but the database record remains. This could leave orphaned records if the SendGrid cancellation succeeds but the calling code expects the database record to be cleaned up.

@@ -0,0 +1,2 @@
-- AlterTable
ALTER TABLE "WorkflowReminder" ADD COLUMN "cancelled" BOOLEAN;
Copy link

Choose a reason for hiding this comment

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

style: Consider adding a default value (likely FALSE) to ensure consistent behavior for existing and new records

@github-actions
Copy link
Contributor

This PR is being marked as stale due to inactivity.

@GitHoobar
Copy link

Review Summary

🏷️ Draft Comments (30)

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

packages/features/bookings/lib/handleCancelBooking.ts (3)

311-321: forEach(async ...) is used for async operations on lines 311-321, which does not await the async deletions, risking incomplete calendar event deletions and resource leaks for large recurring bookings.

📊 Impact Scores:

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

🤖 AI Agent Prompt (Copy & Paste Ready):

In packages/features/bookings/lib/handleCancelBooking.ts, lines 311-321, replace the use of `forEach(async ...)` for deleting calendar events with `Promise.all([...].map(async ...))` to ensure all async deletions are awaited. This prevents incomplete deletions and resource leaks for large recurring bookings. Refactor this block so that all async deletions are properly awaited before proceeding.

31-503: The function is over 500 lines and contains deeply nested logic, making it difficult to maintain and reason about, which increases risk of performance and maintainability issues as the codebase grows.

📊 Impact Scores:

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

🤖 AI Agent Prompt (Copy & Paste Ready):

In packages/features/bookings/lib/handleCancelBooking.ts, lines 31-503, the `handler` function is excessively large and complex, containing deeply nested logic and multiple responsibilities. Refactor this function by extracting major logical sections (such as attendee deletion, calendar/video event deletion, payment handling, and reminder cancellation) into well-named helper functions. This will significantly improve maintainability, testability, and reduce the risk of performance issues as the codebase grows.

34-158: cancellationReason from req.body is directly stored in the database and included in webhook/event payloads without sanitization, allowing attackers to inject malicious content (e.g., XSS payloads) that could be rendered in downstream systems or emails.

📊 Impact Scores:

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

🤖 AI Agent Prompt (Copy & Paste Ready):

Sanitize the `cancellationReason` field in packages/features/bookings/lib/handleCancelBooking.ts (lines 34-158). The value is taken from user input and stored in the database and sent in webhook/event payloads without sanitization, which could allow XSS or malicious content injection. Add a simple sanitization function (e.g., strip `<` and `>`) and use it on the `cancellationReason` before storing or sending it.

packages/features/bookings/lib/handleNewBooking.ts (3)

966-971: deleteScheduledEmailReminder and deleteScheduledSMSReminder are called without awaiting their (likely async) execution, which can cause reminders not to be deleted before proceeding, leading to race conditions and reminders being sent for rescheduled bookings.

📊 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/features/bookings/lib/handleNewBooking.ts, lines 966-971, the code calls `deleteScheduledEmailReminder` and `deleteScheduledSMSReminder` inside a forEach loop without awaiting them. This can cause race conditions where reminders are not deleted before proceeding, leading to old reminders being sent for rescheduled bookings. Refactor this block to use `Promise.all` with `map` and `await` each deletion, ensuring all deletions complete before continuing.

281-281: console.log statements in production code (e.g., line 281) can cause significant performance degradation under high load and pollute logs, especially in serverless or multi-tenant environments.

📊 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/features/bookings/lib/handleNewBooking.ts, line 281, remove or replace the `console.log` statement with proper logging. Console statements in production can cause performance issues and pollute logs. Comment out or use the project's logger instead.

317-1284: Large, complex function handler (lines 317-1284) is over 950 lines, making it extremely difficult to maintain, test, and optimize, which significantly impacts long-term code quality and scalability.

📊 Impact Scores:

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

🤖 AI Agent Prompt (Copy & Paste Ready):

In packages/features/bookings/lib/handleNewBooking.ts, lines 317-1284, the `handler` function is extremely large and complex (over 950 lines). Refactor this function by extracting major logical sections (such as user loading, booking creation, event management, notification sending, and workflow scheduling) into well-named helper functions or modules. This will significantly improve maintainability, testability, and performance optimization opportunities.

packages/features/ee/workflows/api/scheduleEmailReminders.ts (4)

161-185: case blocks at lines 161 and 174 declare const variables without braces, causing variables to leak and potentially throw runtime errors in some JS engines.

📊 Impact Scores:

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

🤖 AI Agent Prompt (Copy & Paste Ready):

In packages/features/ee/workflows/api/scheduleEmailReminders.ts, lines 161-185, the `case WorkflowTemplates.CUSTOM:` block declares `const` variables without braces, which can cause variable leakage and runtime errors. Please wrap the entire case block in braces `{ ... }` so that the lexical declarations are scoped correctly. Only the CUSTOM case needs this fix.

56-73: for loop at lines 56-73 performs sequential API calls and DB prep, causing high latency and poor throughput for large remindersToCancel sets.

📊 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/features/ee/workflows/api/scheduleEmailReminders.ts, lines 56-73, the for-loop processes each reminder sequentially, causing high latency for large numbers of reminders. Refactor this block to perform the SendGrid cancel API requests and the Prisma deletes in parallel using Promise.all, so that all reminders are cancelled and deleted concurrently for much better throughput.

76-77,222-222: console.log statements at lines 76 and 222 may leak sensitive error information to logs, potentially exposing internal details or sensitive data if logs are accessible to unauthorized users.

📊 Impact Scores:

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

🤖 AI Agent Prompt (Copy & Paste Ready):

In packages/features/ee/workflows/api/scheduleEmailReminders.ts, lines 76-77 and 222, console.log statements are used to log errors, which may leak sensitive information if logs are accessible. Remove these console.log statements or replace them with a secure logging mechanism that does not expose sensitive data.

114-117: reminder.workflowStep.sendTo (line 117) and reminder.booking?.attendees[0].email (line 114) are used as email recipients without validation, allowing attackers to inject arbitrary email addresses and potentially send emails to unintended recipients (email injection).

📊 Impact Scores:

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

🤖 AI Agent Prompt (Copy & Paste Ready):

In packages/features/ee/workflows/api/scheduleEmailReminders.ts, lines 114-117, email addresses from user-controlled sources are used directly as recipients without validation, which could allow email injection or sending to unintended addresses. Add strict email format validation before assigning to `sendTo`.

packages/features/ee/workflows/components/WorkflowStepContainer.tsx (4)

62-62: verifiedNumbers?.map((number) => number.phoneNumber) is recomputed on every render, causing unnecessary memory allocations and CPU usage for large verified number lists.

📊 Impact Scores:

  • Production Impact: 2/5
  • Fix Specificity: 4/5
  • Urgency Impact: 2/5
  • Total Score: 8/15

🤖 AI Agent Prompt (Copy & Paste Ready):

In packages/features/ee/workflows/components/WorkflowStepContainer.tsx, lines 62-62, the code recomputes `verifiedNumbers` on every render, which is inefficient for large lists. Wrap the mapping in a `useMemo` so it only recalculates when `_verifiedNumbers` changes.

403-404: The phone number verification check uses .concat([]).find(...) on every input change, causing unnecessary array copying and O(n) search on each keystroke for large lists.

📊 Impact Scores:

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

🤖 AI Agent Prompt (Copy & Paste Ready):

In packages/features/ee/workflows/components/WorkflowStepContainer.tsx, lines 403-404, the code uses `.concat([]).find(...)` to check if a phone number is verified, which is inefficient for large arrays. Replace with `.includes(...)` to avoid unnecessary copying and improve lookup performance.

56-796: Large, complex render function in WorkflowStepContainer (over 700 lines) makes the component hard to maintain and reason about, increasing risk of performance and maintainability issues.

📊 Impact Scores:

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

🤖 AI Agent Prompt (Copy & Paste Ready):

In packages/features/ee/workflows/components/WorkflowStepContainer.tsx, lines 56-796, the `WorkflowStepContainer` component is extremely large and complex, making it difficult to maintain and optimize. Refactor the component by extracting major logical sections (such as trigger selection, action selection, phone/email input, template selection, and dialogs) into smaller, focused subcomponents. This will improve maintainability, testability, and render performance.

415-419: sendVerificationCodeMutation.mutate and verifyPhoneNumberMutation.mutate can be triggered with unvalidated or attacker-controlled phone numbers, potentially allowing SMS flooding or abuse of the verification endpoint.

📊 Impact Scores:

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

🤖 AI Agent Prompt (Copy & Paste Ready):

In packages/features/ee/workflows/components/WorkflowStepContainer.tsx, lines 415-419, the phone number passed to sendVerificationCodeMutation.mutate is not validated, allowing attackers to trigger SMS to arbitrary numbers. Add strict E.164 phone number validation before calling the mutation, and show an error toast if invalid.

packages/features/ee/workflows/lib/reminders/emailReminderManager.ts (5)

213-222: deleteScheduledEmailReminder does not cancel the scheduled SendGrid email when immediateDelete is true and referenceId is provided, but does not remove the workflow reminder from the database, potentially leaving orphaned DB records and causing reminders to be sent later.

📊 Impact Scores:

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

🤖 AI Agent Prompt (Copy & Paste Ready):

In packages/features/ee/workflows/lib/reminders/emailReminderManager.ts, lines 213-222, the function `deleteScheduledEmailReminder` cancels the scheduled SendGrid email when `immediateDelete` is true, but does not delete the corresponding workflow reminder from the database. This can leave orphaned reminders that may be processed later. Please update the code so that when `immediateDelete` is true, the workflow reminder is also deleted from the database after cancelling the SendGrid scheduled send.

62-65: scheduleEmailReminder makes a network request to SendGrid to create a batch ID (client.request) on every invocation, even for unscheduled or immediate emails, causing unnecessary network overhead and latency for high-throughput or bulk operations.

📊 Impact Scores:

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

🤖 AI Agent Prompt (Copy & Paste Ready):

In packages/features/ee/workflows/lib/reminders/emailReminderManager.ts, lines 62-65, the code always requests a new SendGrid batch ID via `client.request` regardless of whether the email is scheduled or not. This causes unnecessary network overhead for immediate sends. Refactor so that the batch ID is only requested when actually needed (i.e., for scheduled emails or when SendGrid requires it for batch operations).

29-195: The function scheduleEmailReminder contains a large, complex block with deeply nested logic and multiple responsibilities, making it difficult to maintain and extend as the workflow logic grows.

📊 Impact Scores:

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

🤖 AI Agent Prompt (Copy & Paste Ready):

In packages/features/ee/workflows/lib/reminders/emailReminderManager.ts, lines 29-195, the `scheduleEmailReminder` function is overly large and handles multiple concerns (template selection, SendGrid logic, DB writes, scheduling, etc.). Refactor this function into smaller, well-named helper functions (e.g., for template resolution, SendGrid send, DB writes) to improve maintainability and testability, while preserving all existing logic and behavior.

88-89, 117-118: emailBody and emailSubject are interpolated directly into HTML and email content without sanitization, allowing stored or reflected XSS if attacker controls these fields.

📊 Impact Scores:

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

🤖 AI Agent Prompt (Copy & Paste Ready):

In packages/features/ee/workflows/lib/reminders/emailReminderManager.ts, lines 88-89 and 117-118, user-controlled `emailBody` and `emailSubject` are interpolated into HTML and email content without sanitization, creating an XSS risk if attacker input is present. Add robust HTML and header escaping (e.g., with an `escapeHtml` and `escapeHeader` function) to sanitize these fields before use in email content and subject.

138, 166: evt.organizer.email is used as the reply-to address in outgoing emails without validation, allowing email header injection or spoofing if attacker controls this field.

📊 Impact Scores:

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

🤖 AI Agent Prompt (Copy & Paste Ready):

In packages/features/ee/workflows/lib/reminders/emailReminderManager.ts, lines 138 and 166, `evt.organizer.email` is used as the reply-to address in outgoing emails without validation. Add a robust email validation function (e.g., `validateEmail`) and only set the reply-to if the email is valid, to prevent email header injection or spoofing.

packages/features/ee/workflows/lib/reminders/smsReminderManager.ts (5)

177-189: deleteScheduledSMSReminder now requires both reminderId and referenceId, but if referenceId is null, the Twilio SMS is not cancelled, potentially leaving scheduled SMS in Twilio even after deleting the DB record.

📊 Impact Scores:

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

🤖 AI Agent Prompt (Copy & Paste Ready):

In packages/features/ee/workflows/lib/reminders/smsReminderManager.ts, lines 177-189, the function `deleteScheduledSMSReminder` deletes the DB record even if `referenceId` is null, which means the scheduled SMS in Twilio may not be cancelled. Please add a warning or appropriate handling for the case when `referenceId` is null, to avoid orphaned scheduled SMS in Twilio.

85-93: evt.attendees[0] is accessed multiple times without checking if attendees is non-empty, risking runtime errors and unnecessary repeated property access for large attendee lists.

📊 Impact Scores:

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

🤖 AI Agent Prompt (Copy & Paste Ready):

In packages/features/ee/workflows/lib/reminders/smsReminderManager.ts, lines 85-93, multiple accesses to evt.attendees[0] are made without checking if attendees is non-empty, which can cause runtime errors and repeated property access. Refactor to assign evt.attendees[0] to a variable (e.g., firstAttendee) after checking for existence, and use this variable in all subsequent references to attendee properties.

126-127: No caching or batching for Twilio SMS API calls in loops or high-frequency triggers, risking API rate limits and increased latency for bulk operations.

📊 Impact Scores:

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

🤖 AI Agent Prompt (Copy & Paste Ready):

In packages/features/ee/workflows/lib/reminders/smsReminderManager.ts, line 126, the code directly calls twilio.sendSMS for each reminder without batching or caching, which can cause performance bottlenecks and hit Twilio rate limits during bulk sends. Consider implementing batching or queueing for SMS sends to improve throughput and reliability for high-volume operations.

69-76: Potential N+1 query issue: getIsNumberVerified calls prisma.verifiedNumber.findFirst for every reminder, which can cause DB load if called in bulk or loops.

📊 Impact Scores:

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

🤖 AI Agent Prompt (Copy & Paste Ready):

In packages/features/ee/workflows/lib/reminders/smsReminderManager.ts, lines 69-76, the getIsNumberVerified function performs a database query for every reminder. If this function is called in a loop or for multiple reminders, it can cause an N+1 query problem and degrade performance. Refactor to batch these queries or cache verification results per user/phoneNumber within the request context.

128-129: console.log statements in error handling (lines 128, 159, 188) may leak sensitive error details to logs, risking exposure of internal information or user data if logs are accessible.

📊 Impact Scores:

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

🤖 AI Agent Prompt (Copy & Paste Ready):

In packages/features/ee/workflows/lib/reminders/smsReminderManager.ts, lines 128-129, replace the `console.log` statement in the error handler with a secure logging mechanism that does not leak sensitive error details. Use a proper logger and ensure sensitive data is redacted.

packages/prisma/schema.prisma (1)

0644-0644: cancelled field in WorkflowReminder model is added as Boolean? (optional), but if not set, its value will be null, which may cause logic errors if code expects a boolean (true/false) for cancellation checks.

📊 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/prisma/schema.prisma, on line 644, the `cancelled` field in the `WorkflowReminder` model is currently defined as `Boolean?`, making it optional and defaulting to null if not set. This can cause logic errors if application code checks for cancellation using boolean logic. Change the field to a required boolean with a default value of false: `cancelled Boolean @default(false)`.

packages/trpc/server/routers/viewer/bookings.tsx (1)

1182-1185: The message variable in the return statement is always set to 'Booking confirmed' due to operator precedence, regardless of the confirmed value, leading to incorrect user feedback.

📊 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/trpc/server/routers/viewer/bookings.tsx, lines 1182-1185, the `message` variable is set using `const message = "Booking " + confirmed ? "confirmed" : "rejected";`, which due to operator precedence always results in 'Booking confirmed'. Fix this by wrapping the ternary in parentheses: `const message = "Booking " + (confirmed ? "confirmed" : "rejected");`.

packages/trpc/server/routers/viewer/workflows.tsx (4)

213-216: deleteScheduledEmailReminder and deleteScheduledSMSReminder are called without awaiting their (likely) async results, which can cause reminders not to be deleted before workflow deletion, leading to orphaned scheduled reminders.

📊 Impact Scores:

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

🤖 AI Agent Prompt (Copy & Paste Ready):

In packages/trpc/server/routers/viewer/workflows.tsx, lines 213-216, the functions `deleteScheduledEmailReminder` and `deleteScheduledSMSReminder` are called without `await`, which can cause reminders not to be deleted before the workflow is deleted, leading to orphaned scheduled reminders. Update these calls to use `await` so that reminder deletions complete before proceeding.

413-492: forEach(async ...) is used for scheduling/cancelling reminders, causing unawaited async operations and potential resource leaks or race conditions for large datasets.

📊 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/trpc/server/routers/viewer/workflows.tsx, lines 413-492, replace all `forEach(async ...)` loops used for scheduling reminders with `for...of` loops to ensure async operations are properly awaited. This prevents unawaited promises, resource leaks, and race conditions when processing large numbers of reminders. Refactor both the outer and inner loops accordingly.

114-147: step.sendTo and step.reminderBody are written directly to the database and used in reminders without sanitization, allowing attackers to inject malicious content (e.g., XSS in emails/SMS or stored XSS in UIs displaying reminders).

📊 Impact Scores:

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

🤖 AI Agent Prompt (Copy & Paste Ready):

In packages/trpc/server/routers/viewer/workflows.tsx, lines 114-147, user-supplied fields like `sendTo` and potentially `reminderBody` are written to the database and used in reminders without sanitization, which could allow XSS or injection attacks. Please sanitize these fields before storing or using them, e.g., by applying a `sanitizeInput` function or equivalent. Add this sanitization in all relevant create/update paths.

458-472: step.reminderBody and step.emailSubject are used in email/SMS reminders without output encoding or sanitization, enabling stored XSS or injection if attacker controls these fields.

📊 Impact Scores:

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

🤖 AI Agent Prompt (Copy & Paste Ready):

In packages/trpc/server/routers/viewer/workflows.tsx, lines 458-472, `step.reminderBody` and `step.emailSubject` are used in email/SMS reminders without output encoding or sanitization, which could allow stored XSS or injection. Please sanitize or encode these fields before use, e.g., with a `sanitizeInput` function.

🔍 Comments beyond diff scope (3)
packages/features/bookings/lib/handleCancelBooking.ts (1)

0462-0462: apiDeletes is an array of Promises, but await apiDeletes; does not wait for their resolution, causing possible incomplete external deletions before returning.
Category: correctness


packages/features/ee/workflows/api/scheduleEmailReminders.ts (1)

22-25: apiKey is accepted from either the Authorization header or a query parameter (req.query.apiKey), allowing attackers to bypass header-based API key checks by supplying the key in the URL, which is often logged and can be intercepted, leading to unauthorized access.
Category: security


packages/trpc/server/routers/viewer/workflows.tsx (1)

262-283: Authorization logic for workflow updates (lines 262-283) only checks user ID and step workflowId, but does not verify that all nested step IDs actually belong to the workflow, allowing privilege escalation by referencing steps from other workflows.
Category: security


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

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants