Skip to content

Commit 97af0af

Browse files
CarinaWolliCarinaWolli
andauthored
fix: only generate sms message bodies when needed (calcom#21566)
* only generate template when needed * rename function to getSMSMessageWithVariables --------- Co-authored-by: CarinaWolli <wollencarina@gmail.com>
1 parent e9b2692 commit 97af0af

2 files changed

Lines changed: 223 additions & 162 deletions

File tree

packages/features/ee/workflows/lib/reminders/smsReminderManager.ts

Lines changed: 122 additions & 162 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
import 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";
48
import logger from "@calcom/lib/logger";
59
import { safeStringify } from "@calcom/lib/safeStringify";
610
import { getTranslation } from "@calcom/lib/server/i18n";
@@ -10,7 +14,6 @@ import prisma from "@calcom/prisma";
1014
import type { Prisma } from "@calcom/prisma/client";
1115
import { WorkflowTemplates, WorkflowActions, WorkflowMethods } from "@calcom/prisma/enums";
1216
import { WorkflowTriggerEvents } from "@calcom/prisma/enums";
13-
import { bookingMetadataSchema } from "@calcom/prisma/zod-utils";
1417
import type { CalEventResponses, RecurringEvent } from "@calcom/types/Calendar";
1518

1619
import { isAttendeeAction } from "../actionHelperFunctions";
@@ -20,8 +23,6 @@ import { WorkflowOptOutService } from "../service/workflowOptOutService";
2023
import type { ScheduleReminderArgs } from "./emailReminderManager";
2124
import { scheduleSmsOrFallbackEmail, sendSmsOrFallbackEmail } from "./messageDispatcher";
2225
import * as twilio from "./providers/twilioProvider";
23-
import type { VariablesType } from "./templates/customTemplate";
24-
import customTemplate from "./templates/customTemplate";
2526
import smsReminderTemplate from "./templates/smsReminderTemplate";
2627

2728
export 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

Comments
 (0)