From 646d18e28648ae1bb520710e05f88efe7bafbae2 Mon Sep 17 00:00:00 2001 From: Anik Dhabal Babu <81948346+anikdhabal@users.noreply.github.com> Date: Fri, 23 Jan 2026 17:12:08 +0530 Subject: [PATCH] fix: use Cal.com video URL (#27184) --- .../lib/service/EmailWorkflowService.test.ts | 120 ++++++++++++++++++ .../lib/service/EmailWorkflowService.ts | 19 ++- 2 files changed, 134 insertions(+), 5 deletions(-) diff --git a/packages/features/ee/workflows/lib/service/EmailWorkflowService.test.ts b/packages/features/ee/workflows/lib/service/EmailWorkflowService.test.ts index 4e06af36a2..0ecc0d3ba1 100644 --- a/packages/features/ee/workflows/lib/service/EmailWorkflowService.test.ts +++ b/packages/features/ee/workflows/lib/service/EmailWorkflowService.test.ts @@ -9,6 +9,7 @@ import { WorkflowTemplates, WorkflowTriggerEvents, } from "@calcom/prisma/enums"; +import { TimeFormat } from "@calcom/lib/timeFormat"; import type { CalendarEvent } from "@calcom/types/Calendar"; import { EmailWorkflowService } from "./EmailWorkflowService"; @@ -34,6 +35,20 @@ vi.mock("@calcom/emails/lib/generateIcsString", () => ({ default: vi.fn().mockReturnValue("mock-ics-content"), })); +vi.mock("@calcom/lib/constants", async () => { + const actual = await vi.importActual("@calcom/lib/constants"); + return { + ...actual, + WEBAPP_URL: "https://cal.com", + APP_NAME: "Cal.com", + }; +}); + +vi.mock("short-uuid", () => ({ + __esModule: true, + default: () => ({ fromUUID: (uid: string) => uid }), +})); + const mockWorkflowReminderRepository: Pick = { findByIdIncludeStepAndWorkflow: vi.fn(), }; @@ -582,4 +597,109 @@ describe("EmailWorkflowService", () => { expect(result.attachments).toBeUndefined(); }); }); + + describe("generateEmailPayloadForEvtWorkflow - Cal Video meeting URL", () => { + const mockBookingInfoWithCalVideo = { + uid: "booking-cal-video-123", + bookerUrl: "https://cal.com", + title: "Test Meeting with Cal Video", + startTime: "2024-12-01T10:00:00Z", + endTime: "2024-12-01T11:00:00Z", + organizer: { + name: "Organizer Name", + email: "organizer@example.com", + timeZone: "UTC", + language: { locale: "en" }, + timeFormat: TimeFormat.TWELVE_HOUR, + }, + attendees: [ + { + name: "Attendee Name", + email: "attendee@example.com", + timeZone: "UTC", + language: { locale: "en" }, + }, + ], + videoCallData: { + type: "daily_video", + url: "https://test-org.daily.co/test-room-name", + id: "test-room-name", + password: "test-password", + }, + location: "Cal Video", + }; + + test("should use Cal.com video URL instead of daily.co URL for REMINDER template when using Cal video", async () => { + const result = await emailWorkflowService.generateEmailPayloadForEvtWorkflow({ + evt: mockBookingInfoWithCalVideo, + sendTo: ["attendee@example.com"], + hideBranding: false, + emailSubject: "Test Subject", + emailBody: "", // Empty body with REMINDER template triggers REMINDER template + sender: "Cal.com", + action: WorkflowActions.EMAIL_ATTENDEE, + template: WorkflowTemplates.REMINDER, + includeCalendarEvent: false, + triggerEvent: WorkflowTriggerEvents.BEFORE_EVENT, + }); + + // The meetingUrl should NOT contain daily.co + expect(result.html).not.toContain("daily.co"); + // The meetingUrl should contain the Cal.com video URL format + expect(result.html).toContain("/video/booking-cal-video-123"); + }); + + test("should use Cal.com video URL instead of daily.co URL for custom template when using Cal video", async () => { + const customEmailBody = "Join the meeting at {MEETING_URL}"; + const result = await emailWorkflowService.generateEmailPayloadForEvtWorkflow({ + evt: mockBookingInfoWithCalVideo, + sendTo: ["attendee@example.com"], + hideBranding: false, + emailSubject: "Test Subject", + emailBody: customEmailBody, + sender: "Cal.com", + action: WorkflowActions.EMAIL_ATTENDEE, + template: WorkflowTemplates.CUSTOM, + includeCalendarEvent: false, + triggerEvent: WorkflowTriggerEvents.BEFORE_EVENT, + }); + + // The meetingUrl should NOT contain daily.co + expect(result.html).not.toContain("daily.co"); + // The meetingUrl should contain the Cal.com video URL format + expect(result.html).toContain("/video/booking-cal-video-123"); + }); + + test("should use Cal.com video URL format when videoCallData.url contains daily.co domain", async () => { + const mockBookingInfoWithDailyCoUrl = { + ...mockBookingInfoWithCalVideo, + videoCallData: { + type: "daily_video", + url: "https://some-org.daily.co/another-room-name", + id: "another-room-name", + password: "test-password", + }, + }; + + const result = await emailWorkflowService.generateEmailPayloadForEvtWorkflow({ + evt: mockBookingInfoWithDailyCoUrl, + sendTo: ["attendee@example.com"], + hideBranding: false, + emailSubject: "Test Subject", + emailBody: "", // Empty body with REMINDER template triggers REMINDER template + sender: "Cal.com", + action: WorkflowActions.EMAIL_ATTENDEE, + template: WorkflowTemplates.REMINDER, + includeCalendarEvent: false, + triggerEvent: WorkflowTriggerEvents.BEFORE_EVENT, + }); + + // Even though videoCallData.url contains daily.co, the meetingUrl should NOT contain it + expect(result.html).not.toContain("daily.co"); + expect(result.html).not.toContain("some-org.daily.co"); + expect(result.html).not.toContain("another-room-name"); + // Should use Cal.com video URL instead + expect(result.html).toContain("/video/booking-cal-video-123"); + }); + }); }); diff --git a/packages/features/ee/workflows/lib/service/EmailWorkflowService.ts b/packages/features/ee/workflows/lib/service/EmailWorkflowService.ts index a80038e774..de6f77daca 100644 --- a/packages/features/ee/workflows/lib/service/EmailWorkflowService.ts +++ b/packages/features/ee/workflows/lib/service/EmailWorkflowService.ts @@ -397,7 +397,12 @@ export class EmailWorkflowService { if (matchedTemplate === WorkflowTemplates.REMINDER) { const t = await getTranslation(locale, "common"); - + const meetingUrl = + getVideoCallUrlFromCalEvent({ + videoCallData: evt.videoCallData, + uid: evt.uid, + location: evt.location, + }) || bookingMetadataSchema.safeParse(evt.metadata || {}).data?.videoCallUrl; emailContent = emailReminderTemplate({ isEditingMode: false, locale, @@ -409,8 +414,7 @@ export class EmailWorkflowService { eventName: evt.title, timeZone, location: evt.location || "", - meetingUrl: - evt.videoCallData?.url || bookingMetadataSchema.parse(evt.metadata || {})?.videoCallUrl || "", + meetingUrl, otherPerson: attendeeName, name, }); @@ -438,7 +442,12 @@ export class EmailWorkflowService { sendToEmail: sendTo[0], }); const meetingUrl = - getVideoCallUrlFromCalEvent({ videoCallData: evt.videoCallData }) || bookingMetadataSchema.parse(evt.metadata || {})?.videoCallUrl; + getVideoCallUrlFromCalEvent({ + videoCallData: evt.videoCallData, + uid: evt.uid, + location: evt.location, + }) || bookingMetadataSchema.safeParse(evt.metadata || {}).data?.videoCallUrl; + const variables: VariablesType = { eventName: evt.title || "", organizerName: evt.organizer.name, @@ -515,7 +524,7 @@ export class EmailWorkflowService { language: { ...evt.organizer.language, translate: organizerT }, }, attendees: processedAttendees, - location: bookingMetadataSchema.parse(evt.metadata || {})?.videoCallUrl || evt.location, + location: bookingMetadataSchema.safeParse(evt.metadata || {}).data?.videoCallUrl || evt.location, }; const shouldIncludeCalendarEvent =