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:
1 parent
25246ef8ff
commit
3e8f0b125d
7 files changed
+172
-31
No files matched your search
@@ -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)
|
||||
? {
|
||||
|
||||
@@ -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",
|
||||
|
||||
Reference in new issue
Block a user