From 27aa5b0969189efcd04a9529e389561b91bd7397 Mon Sep 17 00:00:00 2001 From: Alex van Andel Date: Thu, 4 Sep 2025 15:52:31 +0100 Subject: [PATCH 1/6] chore: Remove SendGrid related code from emailReminderManager --- .../lib/reminders/emailReminderManager.ts | 154 ++---------------- 1 file changed, 18 insertions(+), 136 deletions(-) diff --git a/packages/features/ee/workflows/lib/reminders/emailReminderManager.ts b/packages/features/ee/workflows/lib/reminders/emailReminderManager.ts index cfde08a397e4b2..5ceac6939a5d86 100644 --- a/packages/features/ee/workflows/lib/reminders/emailReminderManager.ts +++ b/packages/features/ee/workflows/lib/reminders/emailReminderManager.ts @@ -3,14 +3,13 @@ import { v4 as uuidv4 } from "uuid"; import dayjs from "@calcom/dayjs"; import generateIcsString from "@calcom/emails/lib/generateIcsString"; -import { FeaturesRepository } from "@calcom/features/flags/features.repository"; import { preprocessNameFieldDataWithVariant } from "@calcom/features/form-builder/utils"; import tasker from "@calcom/features/tasker"; import { SENDER_NAME, WEBSITE_URL } from "@calcom/lib/constants"; import logger from "@calcom/lib/logger"; import { getTranslation } from "@calcom/lib/server/i18n"; import prisma from "@calcom/prisma"; -import type { TimeUnit } from "@calcom/prisma/enums"; +import type { TimeUnit } from "@calcom/prisma"; import { WorkflowActions, WorkflowMethods, @@ -21,7 +20,6 @@ import { bookingMetadataSchema } from "@calcom/prisma/zod-utils"; import { getWorkflowRecipientEmail } from "../getWorkflowReminders"; import { sendOrScheduleWorkflowEmails } from "./providers/emailProvider"; -import { getBatchId, sendSendgridMail } from "./providers/sendgridProvider"; import type { AttendeeInBookingInfo, BookingInfo, timeUnitLowerCase } from "./smsReminderManager"; import type { VariablesType } from "./templates/customTemplate"; import customTemplate from "./templates/customTemplate"; @@ -52,13 +50,10 @@ interface scheduleEmailReminderArgs extends ScheduleReminderArgs { evt: BookingInfo; sendTo: string[]; action: ScheduleEmailReminderAction; - userId?: number | null; - teamId?: number | null; emailSubject?: string; emailBody?: string; hideBranding?: boolean; includeCalendarEvent?: boolean; - isMandatoryReminder?: boolean; verifiedAt: Date | null; } @@ -70,17 +65,13 @@ export const scheduleEmailReminder = async (args: scheduleEmailReminderArgs) => template, sender, workflowStepId, - seatReferenceUid, sendTo, emailSubject = "", emailBody = "", hideBranding, includeCalendarEvent, - isMandatoryReminder, action, verifiedAt, - userId, - teamId, } = args; if (!verifiedAt) { @@ -90,7 +81,6 @@ export const scheduleEmailReminder = async (args: scheduleEmailReminderArgs) => const { startTime, endTime } = evt; const uid = evt.uid as string; - const currentDate = dayjs(); const timeUnit: timeUnitLowerCase | undefined = timeSpan.timeUnit?.toLocaleLowerCase() as timeUnitLowerCase; let scheduledDate = null; @@ -276,134 +266,26 @@ export const scheduleEmailReminder = async (args: scheduleEmailReminderArgs) => const mailData = await prepareEmailData(); - const isSendgridEnabled = !!(process.env.SENDGRID_API_KEY && process.env.SENDGRID_EMAIL); - - const featureRepo = new FeaturesRepository(prisma); - - const isWorkflowSmtpEmailsEnabled = teamId - ? await featureRepo.checkIfTeamHasFeature(teamId, "workflow-smtp-emails") - : userId - ? await featureRepo.checkIfUserHasFeature(userId, "workflow-smtp-emails") - : false; - - if (isWorkflowSmtpEmailsEnabled || !isSendgridEnabled) { - let reminderUid; - if (scheduledDate) { - const reminder = await prisma.workflowReminder.create({ - data: { - bookingUid: uid, - workflowStepId, - method: WorkflowMethods.EMAIL, - scheduledDate: scheduledDate.toDate(), - scheduled: true, - }, - }); - reminderUid = reminder.uuid; - } - - await sendOrScheduleWorkflowEmails({ - ...mailData, - to: sendTo, - sendAt: scheduledDate?.toDate(), - referenceUid: reminderUid ?? undefined, + let reminderUid = undefined; + if (scheduledDate) { + const reminder = await prisma.workflowReminder.create({ + data: { + bookingUid: uid, + workflowStepId, + method: WorkflowMethods.EMAIL, + scheduledDate: scheduledDate.toDate(), + scheduled: true, + }, }); - - return; + reminderUid = reminder.uuid; } - /** - * @deprecated only needed for SendGrid, use SMTP with tasker instead - */ - if ( - triggerEvent === WorkflowTriggerEvents.NEW_EVENT || - triggerEvent === WorkflowTriggerEvents.EVENT_CANCELLED || - triggerEvent === WorkflowTriggerEvents.RESCHEDULE_EVENT - ) { - try { - const promises = sendTo.map((email) => sendSendgridMail({ ...mailData, to: email })); - // TODO: Maybe don't await for this? - await Promise.all(promises); - } catch (error) { - log.error("Error sending Email"); - } - } else if ( - (triggerEvent === WorkflowTriggerEvents.BEFORE_EVENT || - triggerEvent === WorkflowTriggerEvents.AFTER_EVENT) && - scheduledDate - ) { - // Sendgrid to schedule emails - // Can only schedule at least 60 minutes and at most 72 hours in advance - // To limit the amount of canceled sends we schedule at most 2 hours in advance - if ( - currentDate.isBefore(scheduledDate.subtract(1, "hour")) && - !scheduledDate.isAfter(currentDate.add(2, "hour")) - ) { - try { - const sendgridBatchId = await getBatchId(); - - // If sendEmail failed then workflowReminer will not be created, failing E2E tests - await sendSendgridMail({ - ...mailData, - to: sendTo, - sendAt: scheduledDate.unix(), - batchId: sendgridBatchId, - }); - - if (!isMandatoryReminder) { - await prisma.workflowReminder.create({ - data: { - bookingUid: uid, - workflowStepId: workflowStepId, - method: WorkflowMethods.EMAIL, - scheduledDate: scheduledDate.toDate(), - scheduled: true, - referenceId: sendgridBatchId, - seatReferenceId: seatReferenceUid, - }, - }); - } else { - await prisma.workflowReminder.create({ - data: { - bookingUid: uid, - method: WorkflowMethods.EMAIL, - scheduledDate: scheduledDate.toDate(), - scheduled: true, - referenceId: sendgridBatchId, - seatReferenceId: seatReferenceUid, - isMandatoryReminder: true, - }, - }); - } - } catch (error) { - log.error(`Error scheduling email with error ${error}`); - } - } else if (scheduledDate.isAfter(currentDate.add(2, "hour"))) { - // Write to DB and send to CRON if scheduled reminder date is past 2 hours - if (!isMandatoryReminder) { - await prisma.workflowReminder.create({ - data: { - bookingUid: uid, - workflowStepId: workflowStepId, - method: WorkflowMethods.EMAIL, - scheduledDate: scheduledDate.toDate(), - scheduled: false, - seatReferenceId: seatReferenceUid, - }, - }); - } else { - await prisma.workflowReminder.create({ - data: { - bookingUid: uid, - method: WorkflowMethods.EMAIL, - scheduledDate: scheduledDate.toDate(), - scheduled: false, - seatReferenceId: seatReferenceUid, - isMandatoryReminder: true, - }, - }); - } - } - } + await sendOrScheduleWorkflowEmails({ + ...mailData, + to: sendTo, + sendAt: scheduledDate?.toDate(), + referenceUid: reminderUid, + }); }; export const deleteScheduledEmailReminder = async (reminderId: number) => { From a525465ae415e6a9990218fb892f2675d218032f Mon Sep 17 00:00:00 2001 From: Alex van Andel Date: Thu, 4 Sep 2025 16:51:00 +0100 Subject: [PATCH 2/6] Small type fixes in consumers --- .../features/ee/round-robin/roundRobinManualReassignment.ts | 2 -- .../ee/workflows/lib/reminders/emailReminderManager.ts | 4 ++-- .../features/ee/workflows/lib/reminders/reminderScheduler.ts | 2 -- .../ee/workflows/lib/reminders/scheduleMandatoryReminder.ts | 2 -- packages/trpc/server/routers/viewer/workflows/util.ts | 2 -- 5 files changed, 2 insertions(+), 10 deletions(-) diff --git a/packages/features/ee/round-robin/roundRobinManualReassignment.ts b/packages/features/ee/round-robin/roundRobinManualReassignment.ts index 1ed40527bc692d..b9ac0962de9b81 100644 --- a/packages/features/ee/round-robin/roundRobinManualReassignment.ts +++ b/packages/features/ee/round-robin/roundRobinManualReassignment.ts @@ -502,8 +502,6 @@ export async function handleWorkflowsUpdate({ includeCalendarEvent: workflowStep.includeCalendarEvent, workflowStepId: workflowStep.id, verifiedAt: workflowStep.verifiedAt, - userId: workflow.userId, - teamId: workflow.teamId, }); } diff --git a/packages/features/ee/workflows/lib/reminders/emailReminderManager.ts b/packages/features/ee/workflows/lib/reminders/emailReminderManager.ts index 5ceac6939a5d86..1a327edc3292eb 100644 --- a/packages/features/ee/workflows/lib/reminders/emailReminderManager.ts +++ b/packages/features/ee/workflows/lib/reminders/emailReminderManager.ts @@ -9,7 +9,7 @@ import { SENDER_NAME, WEBSITE_URL } from "@calcom/lib/constants"; import logger from "@calcom/lib/logger"; import { getTranslation } from "@calcom/lib/server/i18n"; import prisma from "@calcom/prisma"; -import type { TimeUnit } from "@calcom/prisma"; +import type { TimeUnit } from "@calcom/prisma/enums"; import { WorkflowActions, WorkflowMethods, @@ -284,7 +284,7 @@ export const scheduleEmailReminder = async (args: scheduleEmailReminderArgs) => ...mailData, to: sendTo, sendAt: scheduledDate?.toDate(), - referenceUid: reminderUid, + referenceUid: reminderUid ?? undefined, }); }; diff --git a/packages/features/ee/workflows/lib/reminders/reminderScheduler.ts b/packages/features/ee/workflows/lib/reminders/reminderScheduler.ts index d7e37c462f5d82..f1b64418f0d55f 100644 --- a/packages/features/ee/workflows/lib/reminders/reminderScheduler.ts +++ b/packages/features/ee/workflows/lib/reminders/reminderScheduler.ts @@ -159,8 +159,6 @@ const processWorkflowStep = async ( seatReferenceUid, includeCalendarEvent: step.includeCalendarEvent, verifiedAt: step.verifiedAt, - userId: workflow.userId, - teamId: workflow.teamId, }); } else if (isWhatsappAction(step.action)) { const sendTo = step.action === WorkflowActions.WHATSAPP_ATTENDEE ? smsReminderNumber : step.sendTo; diff --git a/packages/features/ee/workflows/lib/reminders/scheduleMandatoryReminder.ts b/packages/features/ee/workflows/lib/reminders/scheduleMandatoryReminder.ts index 76c80884e3727d..564e23653d7ff2 100644 --- a/packages/features/ee/workflows/lib/reminders/scheduleMandatoryReminder.ts +++ b/packages/features/ee/workflows/lib/reminders/scheduleMandatoryReminder.ts @@ -63,10 +63,8 @@ async function _scheduleMandatoryReminder({ hideBranding, seatReferenceUid, includeCalendarEvent: false, - isMandatoryReminder: true, // Template is fixed so we don't have to verify verifiedAt: new Date(), - userId: evt.organizer.id, }); } catch (error) { log.error("Error while scheduling mandatory reminders", JSON.stringify({ error })); diff --git a/packages/trpc/server/routers/viewer/workflows/util.ts b/packages/trpc/server/routers/viewer/workflows/util.ts index a041556c46107d..d07e38fc444713 100644 --- a/packages/trpc/server/routers/viewer/workflows/util.ts +++ b/packages/trpc/server/routers/viewer/workflows/util.ts @@ -794,8 +794,6 @@ export async function scheduleBookingReminders( sender: step.sender, workflowStepId: step.id, verifiedAt: step?.verifiedAt ?? null, - userId, - teamId, }); } else if (step.action === WorkflowActions.SMS_NUMBER && step.sendTo) { await scheduleSMSReminder({ From c65054b257c87b0b03e43ccc35f8a37ba9fd1488 Mon Sep 17 00:00:00 2001 From: Alex van Andel Date: Thu, 4 Sep 2025 17:02:11 +0100 Subject: [PATCH 3/6] Remove redundant userId/teamId from activateEventType.handler --- .../routers/viewer/workflows/activateEventType.handler.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/packages/trpc/server/routers/viewer/workflows/activateEventType.handler.ts b/packages/trpc/server/routers/viewer/workflows/activateEventType.handler.ts index 6c73e5c2bdc7c0..20bac521c880de 100644 --- a/packages/trpc/server/routers/viewer/workflows/activateEventType.handler.ts +++ b/packages/trpc/server/routers/viewer/workflows/activateEventType.handler.ts @@ -388,8 +388,6 @@ export const activateEventTypeHandler = async ({ ctx, input }: ActivateEventType sender: step.sender, workflowStepId: step.id, verifiedAt: step.verifiedAt, - userId: eventTypeWorkflow.userId, - teamId: eventTypeWorkflow.teamId, }); } else if (step.action === WorkflowActions.SMS_NUMBER && step.sendTo) { await scheduleSMSReminder({ From d36daa7f5e6e949aac02bf3ced5042127cdb0577 Mon Sep 17 00:00:00 2001 From: Alex van Andel Date: Thu, 4 Sep 2025 17:46:33 +0100 Subject: [PATCH 4/6] Remove sendSengridMail as dep --- .../ee/workflows/lib/test/workflows.test.ts | 64 +------------------ 1 file changed, 1 insertion(+), 63 deletions(-) diff --git a/packages/features/ee/workflows/lib/test/workflows.test.ts b/packages/features/ee/workflows/lib/test/workflows.test.ts index e5c9e992d9ac05..8809602caf9aac 100644 --- a/packages/features/ee/workflows/lib/test/workflows.test.ts +++ b/packages/features/ee/workflows/lib/test/workflows.test.ts @@ -36,7 +36,6 @@ import { FeaturesRepository } from "../../../../flags/features.repository"; import { deleteWorkfowRemindersOfRemovedMember } from "../../../teams/lib/deleteWorkflowRemindersOfRemovedMember"; import { scheduleEmailReminder } from "../reminders/emailReminderManager"; import * as emailProvider from "../reminders/providers/emailProvider"; -import * as sendgridProvider from "../reminders/providers/sendgridProvider"; const workflowSelect = { id: true, @@ -709,7 +708,6 @@ describe("scheduleBookingReminders", () => { }, }); - const allVerified = await prismock.verifiedNumber.findMany(); await scheduleBookingReminders( bookings, workflow.steps, @@ -1061,7 +1059,6 @@ describe("deleteWorkfowRemindersOfRemovedMember", () => { }); describe("Workflow SMTP Emails Feature Flag", () => { - vi.spyOn(sendgridProvider, "sendSendgridMail"); vi.spyOn(emailProvider, "sendOrScheduleWorkflowEmails"); const mockEvt = { @@ -1097,72 +1094,13 @@ describe("Workflow SMTP Emails Feature Flag", () => { beforeEach(() => { vi.clearAllMocks(); - // Mock SendGrid environment variables - process.env.SENDGRID_API_KEY = "test-key"; - process.env.SENDGRID_EMAIL = "test@example.com"; }); - test("should use SMTP when team has workflow-smtp-emails feature", async () => { - vi.spyOn(FeaturesRepository.prototype, "checkIfTeamHasFeature").mockResolvedValue(true); - - await scheduleEmailReminder({ - ...baseArgs, - teamId: 123, - }); - expect(sendgridProvider.sendSendgridMail).not.toHaveBeenCalled(); - expect(emailProvider.sendOrScheduleWorkflowEmails).toHaveBeenCalled(); - }); - - test("should use SMTP when user has workflow-smtp-emails feature", async () => { - vi.spyOn(FeaturesRepository.prototype, "checkIfUserHasFeature").mockResolvedValue(true); - - await scheduleEmailReminder({ - ...baseArgs, - userId: 123, - }); - expect(sendgridProvider.sendSendgridMail).not.toHaveBeenCalled(); - expect(emailProvider.sendOrScheduleWorkflowEmails).toHaveBeenCalled(); - }); - - test("should use SendGrid when workflow-smtp-emails feature is not enabled for team", async () => { - vi.spyOn(FeaturesRepository.prototype, "checkIfTeamHasFeature").mockResolvedValue(false); - - await scheduleEmailReminder({ - ...baseArgs, - teamId: 123, - }); - - expect(sendgridProvider.sendSendgridMail).toHaveBeenCalled(); - expect(emailProvider.sendOrScheduleWorkflowEmails).not.toHaveBeenCalled(); - }); - - test("should use SendGrid when workflow-smtp-emails feature is not enabled for user", async () => { - vi.spyOn(FeaturesRepository.prototype, "checkIfUserHasFeature").mockResolvedValue(false); - - await scheduleEmailReminder({ - ...baseArgs, - userId: 123, - }); - - expect(sendgridProvider.sendSendgridMail).toHaveBeenCalled(); - expect(emailProvider.sendOrScheduleWorkflowEmails).not.toHaveBeenCalled(); - }); - - test("should use SMTP when SendGrid is not configured", async () => { - vi.spyOn(FeaturesRepository.prototype, "checkIfTeamHasFeature").mockResolvedValue(false); - - vi.spyOn(FeaturesRepository.prototype, "checkIfUserHasFeature").mockResolvedValue(false); - - delete process.env.SENDGRID_API_KEY; - delete process.env.SENDGRID_EMAIL; - + test("should use SMTP", async () => { await scheduleEmailReminder({ ...baseArgs, teamId: 123, - userId: 456, }); - - expect(sendgridProvider.sendSendgridMail).not.toHaveBeenCalled(); expect(emailProvider.sendOrScheduleWorkflowEmails).toHaveBeenCalled(); }); }); From 5899e2162468da88a3be3b2eda108f72b2b784dc Mon Sep 17 00:00:00 2001 From: Alex van Andel Date: Thu, 4 Sep 2025 18:06:02 +0100 Subject: [PATCH 5/6] Remove teamId param --- packages/features/ee/workflows/lib/test/workflows.test.ts | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/packages/features/ee/workflows/lib/test/workflows.test.ts b/packages/features/ee/workflows/lib/test/workflows.test.ts index 8809602caf9aac..6ac763c9069834 100644 --- a/packages/features/ee/workflows/lib/test/workflows.test.ts +++ b/packages/features/ee/workflows/lib/test/workflows.test.ts @@ -1097,10 +1097,7 @@ describe("Workflow SMTP Emails Feature Flag", () => { }); test("should use SMTP", async () => { - await scheduleEmailReminder({ - ...baseArgs, - teamId: 123, - }); + await scheduleEmailReminder(baseArgs); expect(emailProvider.sendOrScheduleWorkflowEmails).toHaveBeenCalled(); }); }); From c0f075ca66410781d8af59403a01360b2921e46c Mon Sep 17 00:00:00 2001 From: Alex van Andel Date: Thu, 4 Sep 2025 19:45:46 +0100 Subject: [PATCH 6/6] Address NIT --- .../features/ee/workflows/lib/reminders/emailReminderManager.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/features/ee/workflows/lib/reminders/emailReminderManager.ts b/packages/features/ee/workflows/lib/reminders/emailReminderManager.ts index f2afd4da4397c0..5c039c1dfcace5 100644 --- a/packages/features/ee/workflows/lib/reminders/emailReminderManager.ts +++ b/packages/features/ee/workflows/lib/reminders/emailReminderManager.ts @@ -18,7 +18,6 @@ import { } from "@calcom/prisma/enums"; import { bookingMetadataSchema } from "@calcom/prisma/zod-utils"; -import { IMMEDIATE_WORKFLOW_TRIGGER_EVENTS } from "../constants"; import { getWorkflowRecipientEmail } from "../getWorkflowReminders"; import { sendOrScheduleWorkflowEmails } from "./providers/emailProvider"; import type { AttendeeInBookingInfo, BookingInfo, timeUnitLowerCase } from "./smsReminderManager";