11import dayjs from "@calcom/dayjs" ;
2- import { bulkShortenLinks } from "@calcom/ee/workflows/lib/reminders/utils" ;
3- import { SENDER_ID , WEBSITE_URL } from "@calcom/lib/constants" ;
2+ import {
3+ getAttendeeToBeUsedInSMS ,
4+ getSMSMessageWithVariables ,
5+ shouldUseTwilio ,
6+ } from "@calcom/ee/workflows/lib/reminders/utils" ;
7+ import { SENDER_ID } from "@calcom/lib/constants" ;
48import logger from "@calcom/lib/logger" ;
59import { safeStringify } from "@calcom/lib/safeStringify" ;
610import { getTranslation } from "@calcom/lib/server/i18n" ;
@@ -10,7 +14,6 @@ import prisma from "@calcom/prisma";
1014import type { Prisma } from "@calcom/prisma/client" ;
1115import { WorkflowTemplates , WorkflowActions , WorkflowMethods } from "@calcom/prisma/enums" ;
1216import { WorkflowTriggerEvents } from "@calcom/prisma/enums" ;
13- import { bookingMetadataSchema } from "@calcom/prisma/zod-utils" ;
1417import type { CalEventResponses , RecurringEvent } from "@calcom/types/Calendar" ;
1518
1619import { isAttendeeAction } from "../actionHelperFunctions" ;
@@ -20,8 +23,6 @@ import { WorkflowOptOutService } from "../service/workflowOptOutService";
2023import type { ScheduleReminderArgs } from "./emailReminderManager" ;
2124import { scheduleSmsOrFallbackEmail , sendSmsOrFallbackEmail } from "./messageDispatcher" ;
2225import * as twilio from "./providers/twilioProvider" ;
23- import type { VariablesType } from "./templates/customTemplate" ;
24- import customTemplate from "./templates/customTemplate" ;
2526import smsReminderTemplate from "./templates/smsReminderTemplate" ;
2627
2728export enum timeUnitLowerCase {
@@ -118,7 +119,6 @@ export const scheduleSMSReminder = async (args: ScheduleTextReminderArgs) => {
118119
119120 const { startTime, endTime } = evt ;
120121 const uid = evt . uid as string ;
121- const currentDate = dayjs ( ) ;
122122 const timeUnit : timeUnitLowerCase | undefined = timeSpan . timeUnit ?. toLocaleLowerCase ( ) as timeUnitLowerCase ;
123123 let scheduledDate = null ;
124124
@@ -139,180 +139,140 @@ export const scheduleSMSReminder = async (args: ScheduleTextReminderArgs) => {
139139 }
140140 const isNumberVerified = await getIsNumberVerified ( ) ;
141141
142- let attendeeToBeUsedInSMS : AttendeeInBookingInfo | null = null ;
143- if ( action === WorkflowActions . SMS_ATTENDEE ) {
144- const attendeeWithReminderPhoneAsSMSReminderNumber =
145- reminderPhone && evt . attendees . find ( ( attendee ) => attendee . email === evt . responses ?. email ?. value ) ;
146- attendeeToBeUsedInSMS = attendeeWithReminderPhoneAsSMSReminderNumber
147- ? attendeeWithReminderPhoneAsSMSReminderNumber
148- : evt . attendees [ 0 ] ;
149- } else {
150- attendeeToBeUsedInSMS = evt . attendees [ 0 ] ;
151- }
152-
153142 if ( triggerEvent === WorkflowTriggerEvents . BEFORE_EVENT ) {
154143 scheduledDate = timeSpan . time && timeUnit ? dayjs ( startTime ) . subtract ( timeSpan . time , timeUnit ) : null ;
155144 } else if ( triggerEvent === WorkflowTriggerEvents . AFTER_EVENT ) {
156145 scheduledDate = timeSpan . time && timeUnit ? dayjs ( endTime ) . add ( timeSpan . time , timeUnit ) : null ;
157146 }
158147
159- const name = action === WorkflowActions . SMS_ATTENDEE ? attendeeToBeUsedInSMS . name : "" ;
160- const attendeeName =
161- action === WorkflowActions . SMS_ATTENDEE ? evt . organizer . name : attendeeToBeUsedInSMS . name ;
162- const timeZone =
163- action === WorkflowActions . SMS_ATTENDEE ? attendeeToBeUsedInSMS . timeZone : evt . organizer . timeZone ;
164-
165- const locale =
166- action === WorkflowActions . SMS_ATTENDEE
167- ? attendeeToBeUsedInSMS . language ?. locale
168- : evt . organizer . language . locale ;
148+ if ( reminderPhone && isNumberVerified ) {
149+ const useTwilio = shouldUseTwilio ( triggerEvent , scheduledDate ) ;
150+ if ( useTwilio ) {
151+ const attendeeToBeUsedInSMS = getAttendeeToBeUsedInSMS ( action , evt , reminderPhone ) ;
169152
170- let smsMessage = message ;
153+ const name = action === WorkflowActions . SMS_ATTENDEE ? attendeeToBeUsedInSMS . name : "" ;
154+ const attendeeName =
155+ action === WorkflowActions . SMS_ATTENDEE ? evt . organizer . name : attendeeToBeUsedInSMS . name ;
156+ const timeZone =
157+ action === WorkflowActions . SMS_ATTENDEE ? attendeeToBeUsedInSMS . timeZone : evt . organizer . timeZone ;
171158
172- if ( smsMessage ) {
173- const urls = {
174- meetingUrl : bookingMetadataSchema . parse ( evt . metadata || { } ) ?. videoCallUrl || "" ,
175- cancelLink : `${ evt . bookerUrl ?? WEBSITE_URL } /booking/${ evt . uid } ?cancel=true` ,
176- rescheduleLink : `${ evt . bookerUrl ?? WEBSITE_URL } /reschedule/${ evt . uid } ` ,
177- } ;
159+ let smsMessage = message ;
178160
179- const [ { shortLink : meetingUrl } , { shortLink : cancelLink } , { shortLink : rescheduleLink } ] =
180- await bulkShortenLinks ( [ urls . meetingUrl , urls . cancelLink , urls . rescheduleLink ] ) ;
181-
182- const variables : VariablesType = {
183- eventName : evt . title ,
184- organizerName : evt . organizer . name ,
185- attendeeName : attendeeToBeUsedInSMS . name ,
186- attendeeFirstName : attendeeToBeUsedInSMS . firstName ,
187- attendeeLastName : attendeeToBeUsedInSMS . lastName ,
188- attendeeEmail : attendeeToBeUsedInSMS . email ,
189- eventDate : dayjs ( evt . startTime ) . tz ( timeZone ) ,
190- eventEndTime : dayjs ( evt . endTime ) . tz ( timeZone ) ,
191- timeZone : timeZone ,
192- location : evt . location ,
193- additionalNotes : evt . additionalNotes ,
194- responses : evt . responses ,
195- meetingUrl,
196- cancelLink,
197- rescheduleLink,
198- cancelReason : evt . cancellationReason ,
199- rescheduleReason : evt . rescheduleReason ,
200- attendeeTimezone : evt . attendees [ 0 ] . timeZone ,
201- eventTimeInAttendeeTimezone : dayjs ( evt . startTime ) . tz ( evt . attendees [ 0 ] . timeZone ) ,
202- eventEndTimeInAttendeeTimezone : dayjs ( evt . endTime ) . tz ( evt . attendees [ 0 ] . timeZone ) ,
203- } ;
204- const customMessage = customTemplate ( smsMessage , variables , locale , evt . organizer . timeFormat ) ;
205- smsMessage = customMessage . text ;
206- } else if ( template === WorkflowTemplates . REMINDER ) {
207- smsMessage =
208- smsReminderTemplate (
209- false ,
210- evt . organizer . language . locale ,
211- action ,
212- evt . organizer . timeFormat ,
213- evt . startTime ,
214- evt . title ,
215- timeZone ,
216- attendeeName ,
217- name
218- ) || message ;
219- }
220-
221- // Allows debugging generated email content without waiting for sendgrid to send emails
222- log . debug ( `Sending sms for trigger ${ triggerEvent } ` , smsMessage ) ;
161+ if ( smsMessage ) {
162+ smsMessage = await getSMSMessageWithVariables ( smsMessage , evt , attendeeToBeUsedInSMS , action ) ;
163+ } else if ( template === WorkflowTemplates . REMINDER ) {
164+ smsMessage =
165+ smsReminderTemplate (
166+ false ,
167+ evt . organizer . language . locale ,
168+ action ,
169+ evt . organizer . timeFormat ,
170+ evt . startTime ,
171+ evt . title ,
172+ timeZone ,
173+ attendeeName ,
174+ name
175+ ) || message ;
176+ }
223177
224- if ( smsMessage . length > 0 && reminderPhone && isNumberVerified ) {
225- if ( process . env . TWILIO_OPT_OUT_ENABLED === "true" ) {
226- smsMessage = await WorkflowOptOutService . addOptOutMessage ( smsMessage , evt . organizer . language . locale ) ;
227- }
178+ if ( smsMessage . length > 0 ) {
179+ if ( process . env . TWILIO_OPT_OUT_ENABLED === "true" ) {
180+ smsMessage = await WorkflowOptOutService . addOptOutMessage (
181+ smsMessage ,
182+ evt . organizer . language . locale
183+ ) ;
184+ }
185+ // Allows debugging generated email content without waiting for sendgrid to send emails
186+ log . debug ( `Sending sms for trigger ${ triggerEvent } ` , smsMessage ) ;
228187
229- //send SMS when event is booked/cancelled/rescheduled
230- if (
231- triggerEvent === WorkflowTriggerEvents . NEW_EVENT ||
232- triggerEvent === WorkflowTriggerEvents . EVENT_CANCELLED ||
233- triggerEvent === WorkflowTriggerEvents . RESCHEDULE_EVENT
234- ) {
235- try {
236- await sendSmsOrFallbackEmail ( {
237- twilioData : {
238- phoneNumber : reminderPhone ,
239- body : smsMessage ,
240- sender : senderID ,
241- bookingUid : evt . uid ,
242- userId,
243- teamId,
244- } ,
245- fallbackData : isAttendeeAction ( action )
246- ? {
247- email : evt . attendees [ 0 ] . email ,
248- t : await getTranslation ( evt . attendees [ 0 ] . language . locale , "common" ) ,
249- replyTo : evt . organizer . email ,
250- }
251- : undefined ,
252- } ) ;
253- } catch ( error ) {
254- log . error ( `Error sending SMS with error ${ error } ` ) ;
255- }
256- } else if (
257- ( triggerEvent === WorkflowTriggerEvents . BEFORE_EVENT ||
258- triggerEvent === WorkflowTriggerEvents . AFTER_EVENT ) &&
259- scheduledDate
260- ) {
261- // schedule at least 15 minutes in advance and at most 2 hours in advance
262- if (
263- currentDate . isBefore ( scheduledDate . subtract ( 15 , "minute" ) ) &&
264- ! scheduledDate . isAfter ( currentDate . add ( 2 , "hour" ) )
265- ) {
266- try {
267- const scheduledNotification = await scheduleSmsOrFallbackEmail ( {
268- twilioData : {
269- phoneNumber : reminderPhone ,
270- body : smsMessage ,
271- scheduledDate : scheduledDate . toDate ( ) ,
272- sender : senderID ,
273- bookingUid : evt . uid ,
274- userId,
275- teamId,
276- } ,
277- fallbackData : isAttendeeAction ( action )
278- ? {
279- email : evt . attendees [ 0 ] . email ,
280- t : await getTranslation ( evt . attendees [ 0 ] . language . locale , "common" ) ,
281- replyTo : evt . organizer . email ,
282- workflowStepId,
283- }
284- : undefined ,
285- } ) ;
188+ if (
189+ triggerEvent === WorkflowTriggerEvents . NEW_EVENT ||
190+ triggerEvent === WorkflowTriggerEvents . EVENT_CANCELLED ||
191+ triggerEvent === WorkflowTriggerEvents . RESCHEDULE_EVENT
192+ ) {
193+ try {
194+ await sendSmsOrFallbackEmail ( {
195+ twilioData : {
196+ phoneNumber : reminderPhone ,
197+ body : smsMessage ,
198+ sender : senderID ,
199+ bookingUid : evt . uid ,
200+ userId,
201+ teamId,
202+ } ,
203+ fallbackData : isAttendeeAction ( action )
204+ ? {
205+ email : evt . attendees [ 0 ] . email ,
206+ t : await getTranslation ( evt . attendees [ 0 ] . language . locale , "common" ) ,
207+ replyTo : evt . organizer . email ,
208+ }
209+ : undefined ,
210+ } ) ;
211+ } catch ( error ) {
212+ log . error ( `Error sending SMS with error ${ error } ` ) ;
213+ }
214+ }
286215
287- if ( scheduledNotification ?. sid ) {
288- await prisma . workflowReminder . create ( {
289- data : {
290- bookingUid : uid ,
291- workflowStepId : workflowStepId ,
292- method : WorkflowMethods . SMS ,
216+ if (
217+ ( triggerEvent === WorkflowTriggerEvents . BEFORE_EVENT ||
218+ triggerEvent === WorkflowTriggerEvents . AFTER_EVENT ) &&
219+ scheduledDate
220+ ) {
221+ try {
222+ // schedule at least 15 minutes in advance and at most 2 hours in advance
223+ const scheduledNotification = await scheduleSmsOrFallbackEmail ( {
224+ twilioData : {
225+ phoneNumber : reminderPhone ,
226+ body : smsMessage ,
293227 scheduledDate : scheduledDate . toDate ( ) ,
294- scheduled : true ,
295- referenceId : scheduledNotification . sid ,
296- seatReferenceId : seatReferenceUid ,
228+ sender : senderID ,
229+ bookingUid : evt . uid ,
230+ userId,
231+ teamId,
297232 } ,
233+ fallbackData : isAttendeeAction ( action )
234+ ? {
235+ email : evt . attendees [ 0 ] . email ,
236+ t : await getTranslation ( evt . attendees [ 0 ] . language . locale , "common" ) ,
237+ replyTo : evt . organizer . email ,
238+ workflowStepId,
239+ }
240+ : undefined ,
298241 } ) ;
242+
243+ if ( scheduledNotification ?. sid ) {
244+ await prisma . workflowReminder . create ( {
245+ data : {
246+ bookingUid : uid ,
247+ workflowStepId : workflowStepId ,
248+ method : WorkflowMethods . SMS ,
249+ scheduledDate : scheduledDate . toDate ( ) ,
250+ scheduled : true ,
251+ referenceId : scheduledNotification . sid ,
252+ seatReferenceId : seatReferenceUid ,
253+ } ,
254+ } ) ;
255+ }
256+ } catch ( error ) {
257+ log . error ( `Error scheduling SMS with error ${ error } ` ) ;
299258 }
300- } catch ( error ) {
301- log . error ( `Error scheduling SMS with error ${ error } ` ) ;
302259 }
303- } else if ( scheduledDate . isAfter ( currentDate . add ( 2 , "hour" ) ) ) {
304- // Write to DB and send to CRON if scheduled reminder date is past 2 hours from now
305- await prisma . workflowReminder . create ( {
306- data : {
307- bookingUid : uid ,
308- workflowStepId : workflowStepId ,
309- method : WorkflowMethods . SMS ,
310- scheduledDate : scheduledDate . toDate ( ) ,
311- scheduled : false ,
312- seatReferenceId : seatReferenceUid ,
313- } ,
314- } ) ;
315260 }
261+ return ;
262+ }
263+
264+ if ( ! useTwilio && scheduledDate ) {
265+ // Write to DB and send to CRON if scheduled reminder date is past 2 hours from now
266+ await prisma . workflowReminder . create ( {
267+ data : {
268+ bookingUid : uid ,
269+ workflowStepId : workflowStepId ,
270+ method : WorkflowMethods . SMS ,
271+ scheduledDate : scheduledDate . toDate ( ) ,
272+ scheduled : false ,
273+ seatReferenceId : seatReferenceUid ,
274+ } ,
275+ } ) ;
316276 }
317277 }
318278} ;
0 commit comments