fix: WhatsApp workflows are not working (#21421)

* fix: whatsapp template

* update

* fixes for sending whatsapp messages

* add message service id when scheduling

* add timezone to startTime variable

---------

Co-authored-by: CarinaWolli <wollencarina@gmail.com>
This commit is contained in:
Anik Dhabal BabuandCarinaWolli authored and GitHub committed 2025-05-21 08:12:12 +00:00
1 parent 25246ef8ff
commit 3e8f0b125d
7 files changed
+172 -31

No files matched your search

+4
View File
@@ -188,6 +188,10 @@ TWILIO_TOKEN=
TWILIO_MESSAGING_SID=
TWILIO_PHONE_NUMBER=
TWILIO_WHATSAPP_PHONE_NUMBER=
TWILIO_WHATSAPP_REMINDER_CONTENT_SID=
TWILIO_WHATSAPP_CANCELLED_CONTENT_SID=
TWILIO_WHATSAPP_RESCHEDULED_CONTENT_SID=
TWILIO_WHATSAPP_COMPLETED_CONTENT_SID=
# For NEXT_PUBLIC_SENDER_ID only letters, numbers and spaces are allowed (max. 11 characters)
NEXT_PUBLIC_SENDER_ID=
TWILIO_VERIFY_SID=
@@ -12,6 +12,10 @@ import { getWhatsappTemplateFunction, isAttendeeAction } from "../lib/actionHelp
import type { PartialWorkflowReminder } from "../lib/getWorkflowReminders";
import { select } from "../lib/getWorkflowReminders";
import { scheduleSmsOrFallbackEmail } from "../lib/reminders/messageDispatcher";
import {
getContentSidForTemplate,
getContentVariablesForTemplate,
} from "../lib/reminders/templates/whatsapp/ContentSidMapping";
export async function handler(req: NextRequest) {
const apiKey = req.headers.get("authorization") || req.nextUrl.searchParams.get("apiKey");
@@ -74,13 +78,29 @@ export async function handler(req: NextRequest) {
? reminder.booking?.attendees[0].timeZone
: reminder.booking?.user?.timeZone;
const startTime = reminder.booking?.startTime.toISOString();
const locale = reminder.booking.user?.locale || "en";
const timeFormat = getTimeFormatStringFromUserTimeFormat(reminder.booking.user?.timeFormat);
const templateFunction = getWhatsappTemplateFunction(reminder.workflowStep.template);
const contentSid = getContentSidForTemplate(reminder.workflowStep.template);
const contentVariables = getContentVariablesForTemplate({
name: userName,
attendeeName: attendeeName || "",
eventName: reminder.booking?.eventType?.title,
eventDate: dayjs(startTime).tz(timeZone).locale(locale).format("YYYY MMM D"),
startTime: dayjs(startTime)
.tz(timeZone)
.locale(locale)
.format(timeFormat || "h:mma"),
timeZone,
});
const message = templateFunction(
false,
reminder.booking.user?.locale || "en",
reminder.workflowStep.action,
getTimeFormatStringFromUserTimeFormat(reminder.booking.user?.timeFormat),
reminder.booking?.startTime.toISOString() || "",
timeFormat,
startTime || "",
reminder.booking?.eventType?.title || "",
timeZone || "",
attendeeName || "",
@@ -98,6 +118,8 @@ export async function handler(req: NextRequest) {
userId,
teamId,
isWhatsapp: true,
contentSid,
contentVariables,
},
fallbackData:
reminder.workflowStep.action && isAttendeeAction(reminder.workflowStep.action)
@@ -18,6 +18,8 @@ export async function sendSmsOrFallbackEmail(props: {
userId?: number | null;
teamId?: number | null;
isWhatsapp?: boolean;
contentSid?: string;
contentVariables?: Record<string, string>;
};
fallbackData?: {
email: string;
@@ -62,6 +64,8 @@ export async function scheduleSmsOrFallbackEmail(props: {
userId?: number | null;
teamId?: number | null;
isWhatsapp?: boolean;
contentSid?: string;
contentVariables?: Record<string, string>;
};
fallbackData?: {
email: string;
@@ -40,6 +40,8 @@ export const sendSMS = async ({
userId,
teamId,
isWhatsapp = false,
contentSid,
contentVariables,
}: {
phoneNumber: string;
body: string;
@@ -48,8 +50,10 @@ export const sendSMS = async ({
userId?: number | null;
teamId?: number | null;
isWhatsapp?: boolean;
contentSid?: string;
contentVariables?: Record<string, string>;
}) => {
log.silly("sendSMS", JSON.stringify({ phoneNumber, body, sender, userId, teamId }));
log.silly("sendSMS", JSON.stringify({ phoneNumber, body, sender, userId, teamId, contentSid }));
const isSMSSendingLocked = await isLockedForSMSSending(userId, teamId);
@@ -80,15 +84,32 @@ export const sendSMS = async ({
});
}
const response = await twilio.messages.create({
body: body,
messagingServiceSid: process.env.TWILIO_MESSAGING_SID,
to: getSMSNumber(phoneNumber, isWhatsapp),
from: isWhatsapp ? getDefaultSender(isWhatsapp) : sender || getDefaultSender(),
statusCallback: getStatusCallbackUrl(userId, teamId, bookingUid),
});
if (isWhatsapp) {
const messageOptions: any = {
contentSid: contentSid,
to: getSMSNumber(phoneNumber, isWhatsapp),
from: getDefaultSender(isWhatsapp),
statusCallback: getStatusCallbackUrl(userId, teamId, bookingUid),
messagingServiceSid: process.env.TWILIO_MESSAGING_SID,
};
return response;
if (contentVariables) {
messageOptions.contentVariables = JSON.stringify(contentVariables);
}
const response = await twilio.messages.create(messageOptions);
return response;
} else {
const response = await twilio.messages.create({
body: body,
messagingServiceSid: process.env.TWILIO_MESSAGING_SID,
to: getSMSNumber(phoneNumber),
from: sender || getDefaultSender(),
statusCallback: getStatusCallbackUrl(userId, teamId, bookingUid),
});
return response;
}
};
const getStatusCallbackUrl = (userId?: number | null, teamId?: number | null, bookingUid?: string | null) => {
@@ -108,6 +129,8 @@ export const scheduleSMS = async ({
userId,
teamId,
isWhatsapp = false,
contentSid,
contentVariables,
}: {
phoneNumber: string;
body: string;
@@ -117,6 +140,8 @@ export const scheduleSMS = async ({
userId?: number | null;
teamId?: number | null;
isWhatsapp?: boolean;
contentSid?: string;
contentVariables?: Record<string, string>;
}) => {
const isSMSSendingLocked = await isLockedForSMSSending(userId, teamId);
@@ -145,17 +170,37 @@ export const scheduleSMS = async ({
rateLimitingType: "smsMonth",
});
}
const response = await twilio.messages.create({
body,
messagingServiceSid: process.env.TWILIO_MESSAGING_SID,
to: getSMSNumber(phoneNumber, isWhatsapp),
scheduleType: "fixed",
sendAt: scheduledDate,
from: isWhatsapp ? getDefaultSender(isWhatsapp) : sender || getDefaultSender(),
statusCallback: getStatusCallbackUrl(userId, teamId, bookingUid),
});
return response;
if (isWhatsapp) {
const messageOptions: any = {
contentSid: contentSid,
to: getSMSNumber(phoneNumber, isWhatsapp),
scheduleType: "fixed",
sendAt: scheduledDate,
from: getDefaultSender(isWhatsapp),
statusCallback: getStatusCallbackUrl(userId, teamId, bookingUid),
messagingServiceSid: process.env.TWILIO_MESSAGING_SID,
};
if (contentVariables) {
messageOptions.contentVariables = JSON.stringify(contentVariables);
}
const response = await twilio.messages.create(messageOptions);
return response;
} else {
const response = await twilio.messages.create({
body,
messagingServiceSid: process.env.TWILIO_MESSAGING_SID,
to: getSMSNumber(phoneNumber),
scheduleType: "fixed",
sendAt: scheduledDate,
from: sender || getDefaultSender(),
statusCallback: getStatusCallbackUrl(userId, teamId, bookingUid),
});
return response;
}
};
export const cancelSMS = async (referenceId: string) => {
@@ -0,0 +1,40 @@
import { WorkflowTemplates } from "@calcom/prisma/enums";
export const getContentSidForTemplate = (template?: WorkflowTemplates): string | undefined => {
if (!template) {
return process.env.TWILIO_WHATSAPP_REMINDER_CONTENT_SID;
}
switch (template) {
case WorkflowTemplates.REMINDER:
return process.env.TWILIO_WHATSAPP_REMINDER_CONTENT_SID;
case WorkflowTemplates.CANCELLED:
return process.env.TWILIO_WHATSAPP_CANCELLED_CONTENT_SID;
case WorkflowTemplates.RESCHEDULED:
return process.env.TWILIO_WHATSAPP_RESCHEDULED_CONTENT_SID;
case WorkflowTemplates.COMPLETED:
return process.env.TWILIO_WHATSAPP_COMPLETED_CONTENT_SID;
default:
return process.env.TWILIO_WHATSAPP_REMINDER_CONTENT_SID;
}
};
export const getContentVariablesForTemplate = (variables: {
name?: string;
attendeeName?: string;
eventName?: string;
eventDate?: string;
startTime?: string;
timeZone?: string;
}): Record<string, string> => {
const { name, attendeeName, eventName, eventDate, startTime, timeZone } = variables;
const commonVariables: Record<string, string> = {
"1": name || "",
"2": attendeeName || "",
"3": eventName || "",
"4": eventDate || "",
"5": `${startTime ?? ""} ${timeZone ?? ""}`,
};
return commonVariables;
};
@@ -10,6 +10,10 @@ import {
} from "@calcom/prisma/enums";
import { isAttendeeAction } from "../actionHelperFunctions";
import {
getContentSidForTemplate,
getContentVariablesForTemplate,
} from "../reminders/templates/whatsapp/ContentSidMapping";
import { scheduleSmsOrFallbackEmail, sendSmsOrFallbackEmail } from "./messageDispatcher";
import type { ScheduleTextReminderArgs, timeUnitLowerCase } from "./smsReminderManager";
import { deleteScheduledSMSReminder } from "./smsReminderManager";
@@ -76,7 +80,21 @@ export const scheduleWhatsappReminder = async (args: ScheduleTextReminderArgs) =
action === WorkflowActions.WHATSAPP_ATTENDEE ? evt.organizer.name : evt.attendees[0].name;
const timeZone =
action === WorkflowActions.WHATSAPP_ATTENDEE ? evt.attendees[0].timeZone : evt.organizer.timeZone;
const locale = evt.organizer.language.locale;
const timeFormat = evt.organizer.timeFormat;
const contentSid = getContentSidForTemplate(template);
const contentVariables = getContentVariablesForTemplate({
name,
attendeeName,
eventName: evt.title,
eventDate: dayjs(startTime).tz(timeZone).locale(locale).format("YYYY MMM D"),
startTime: dayjs(startTime)
.tz(timeZone)
.locale(locale)
.format(timeFormat || "h:mma"),
timeZone,
});
let textMessage = message;
switch (template) {
@@ -84,9 +102,9 @@ export const scheduleWhatsappReminder = async (args: ScheduleTextReminderArgs) =
textMessage =
whatsappReminderTemplate(
false,
evt.organizer.language.locale,
locale,
action,
evt.organizer.timeFormat,
timeFormat,
evt.startTime,
evt.title,
timeZone,
@@ -98,9 +116,9 @@ export const scheduleWhatsappReminder = async (args: ScheduleTextReminderArgs) =
textMessage =
whatsappEventCancelledTemplate(
false,
evt.organizer.language.locale,
locale,
action,
evt.organizer.timeFormat,
timeFormat,
evt.startTime,
evt.title,
timeZone,
@@ -112,9 +130,9 @@ export const scheduleWhatsappReminder = async (args: ScheduleTextReminderArgs) =
textMessage =
whatsappEventRescheduledTemplate(
false,
evt.organizer.language.locale,
locale,
action,
evt.organizer.timeFormat,
timeFormat,
evt.startTime,
evt.title,
timeZone,
@@ -126,9 +144,9 @@ export const scheduleWhatsappReminder = async (args: ScheduleTextReminderArgs) =
textMessage =
whatsappEventCompletedTemplate(
false,
evt.organizer.language.locale,
locale,
action,
evt.organizer.timeFormat,
timeFormat,
evt.startTime,
evt.title,
timeZone,
@@ -140,9 +158,9 @@ export const scheduleWhatsappReminder = async (args: ScheduleTextReminderArgs) =
textMessage =
whatsappReminderTemplate(
false,
evt.organizer.language.locale,
locale,
action,
evt.organizer.timeFormat,
timeFormat,
evt.startTime,
evt.title,
timeZone,
@@ -170,6 +188,8 @@ export const scheduleWhatsappReminder = async (args: ScheduleTextReminderArgs) =
userId,
teamId,
isWhatsapp: true,
contentSid,
contentVariables,
},
fallbackData: isAttendeeAction(action)
? {
@@ -203,6 +223,8 @@ export const scheduleWhatsappReminder = async (args: ScheduleTextReminderArgs) =
userId,
teamId,
isWhatsapp: true,
contentSid,
contentVariables,
},
fallbackData: isAttendeeAction(action)
? {
+4
View File
@@ -418,6 +418,10 @@
"TWILIO_SID",
"TWILIO_TOKEN",
"TWILIO_VERIFY_SID",
"TWILIO_WHATSAPP_REMINDER_CONTENT_SID",
"TWILIO_WHATSAPP_CANCELLED_CONTENT_SID",
"TWILIO_WHATSAPP_RESCHEDULED_CONTENT_SID",
"TWILIO_WHATSAPP_COMPLETED_CONTENT_SID",
"UPSTASH_REDIS_REST_TOKEN",
"UPSTASH_REDIS_REST_URL",
"UNKEY_ROOT_KEY",