feat: mandatory email reminder for attendees with @gmail.com (#12747)

Co-authored-by: Chiranjeev Vishnoi <somu209e@gmail.com>
Co-authored-by: CarinaWolli <wollencarina@gmail.com>
This commit is contained in:
Carina Wollendorfer 2023-12-12 21:23:48 -05:00 committed by GitHub
parent 6b26dbc6da
commit 3164cd4ae7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 651 additions and 431 deletions

View File

@ -2,6 +2,7 @@ import type { Prisma, Workflow, WorkflowsOnEventTypes, WorkflowStep } from "@pri
import type { EventManagerUser } from "@calcom/core/EventManager"; import type { EventManagerUser } from "@calcom/core/EventManager";
import EventManager from "@calcom/core/EventManager"; import EventManager from "@calcom/core/EventManager";
import { scheduleMandatoryReminder } from "@calcom/ee/workflows/lib/reminders/scheduleMandatoryReminder";
import { sendScheduledEmails } from "@calcom/emails"; import { sendScheduledEmails } from "@calcom/emails";
import { scheduleWorkflowReminders } from "@calcom/features/ee/workflows/lib/reminders/reminderScheduler"; import { scheduleWorkflowReminders } from "@calcom/features/ee/workflows/lib/reminders/reminderScheduler";
import getWebhooks from "@calcom/features/webhooks/lib/getWebhooks"; import getWebhooks from "@calcom/features/webhooks/lib/getWebhooks";
@ -256,27 +257,27 @@ export async function handleConfirmation(args: {
//Workflows - set reminders for confirmed events //Workflows - set reminders for confirmed events
try { try {
for (let index = 0; index < updatedBookings.length; index++) { for (let index = 0; index < updatedBookings.length; index++) {
if (updatedBookings[index].eventType?.workflows) { const eventTypeSlug = updatedBookings[index].eventType?.slug || "";
const evtOfBooking = evt; const evtOfBooking = { ...evt, metadata: { videoCallUrl }, eventType: { slug: eventTypeSlug } };
evtOfBooking.startTime = updatedBookings[index].startTime.toISOString(); evtOfBooking.startTime = updatedBookings[index].startTime.toISOString();
evtOfBooking.endTime = updatedBookings[index].endTime.toISOString(); evtOfBooking.endTime = updatedBookings[index].endTime.toISOString();
evtOfBooking.uid = updatedBookings[index].uid; evtOfBooking.uid = updatedBookings[index].uid;
const eventTypeSlug = updatedBookings[index].eventType?.slug || ""; const isFirstBooking = index === 0;
await scheduleMandatoryReminder(
const isFirstBooking = index === 0; evtOfBooking,
updatedBookings[index]?.eventType?.workflows || [],
await scheduleWorkflowReminders({ false,
workflows: updatedBookings[index]?.eventType?.workflows || [], !!updatedBookings[index].eventType?.owner?.hideBranding,
smsReminderNumber: updatedBookings[index].smsReminderNumber, evt.attendeeSeatId
calendarEvent: { );
...evtOfBooking, await scheduleWorkflowReminders({
...{ metadata: { videoCallUrl }, eventType: { slug: eventTypeSlug } }, workflows: updatedBookings[index]?.eventType?.workflows || [],
}, smsReminderNumber: updatedBookings[index].smsReminderNumber,
isFirstRecurringEvent: isFirstBooking, calendarEvent: evtOfBooking,
hideBranding: !!updatedBookings[index].eventType?.owner?.hideBranding, isFirstRecurringEvent: isFirstBooking,
eventTypeRequiresConfirmation: true, hideBranding: !!updatedBookings[index].eventType?.owner?.hideBranding,
}); eventTypeRequiresConfirmation: true,
} });
} }
} catch (error) { } catch (error) {
// Silently fail // Silently fail

View File

@ -25,6 +25,7 @@ import { getEventName } from "@calcom/core/event";
import { getUserAvailability } from "@calcom/core/getUserAvailability"; import { getUserAvailability } from "@calcom/core/getUserAvailability";
import { deleteMeeting } from "@calcom/core/videoClient"; import { deleteMeeting } from "@calcom/core/videoClient";
import dayjs from "@calcom/dayjs"; import dayjs from "@calcom/dayjs";
import { scheduleMandatoryReminder } from "@calcom/ee/workflows/lib/reminders/scheduleMandatoryReminder";
import { import {
sendAttendeeRequestEmail, sendAttendeeRequestEmail,
sendOrganizerRequestEmail, sendOrganizerRequestEmail,
@ -2718,15 +2719,21 @@ async function handler(
} }
const metadataFromEvent = videoCallUrl ? { videoCallUrl } : undefined; const metadataFromEvent = videoCallUrl ? { videoCallUrl } : undefined;
const evtWithMetadata = { ...evt, metadata: metadataFromEvent, eventType: { slug: eventType.slug } };
await scheduleMandatoryReminder(
evtWithMetadata,
eventType.workflows || [],
!isConfirmedByDefault,
!!eventType.owner?.hideBranding,
evt.attendeeSeatId
);
try { try {
await scheduleWorkflowReminders({ await scheduleWorkflowReminders({
workflows: eventType.workflows, workflows: eventType.workflows,
smsReminderNumber: smsReminderNumber || null, smsReminderNumber: smsReminderNumber || null,
calendarEvent: { calendarEvent: evtWithMetadata,
...evt,
...{ metadata: metadataFromEvent, eventType: { slug: eventType.slug } },
},
isNotConfirmed: !isConfirmedByDefault, isNotConfirmed: !isConfirmedByDefault,
isRescheduleEvent: !!rescheduleUid, isRescheduleEvent: !!rescheduleUid,
isFirstRecurringEvent: true, isFirstRecurringEvent: true,

View File

@ -6,6 +6,7 @@ import { v4 as uuidv4 } from "uuid";
import dayjs from "@calcom/dayjs"; import dayjs from "@calcom/dayjs";
import { getCalEventResponses } from "@calcom/features/bookings/lib/getCalEventResponses"; import { getCalEventResponses } from "@calcom/features/bookings/lib/getCalEventResponses";
import { SENDER_NAME } from "@calcom/lib/constants";
import logger from "@calcom/lib/logger"; import logger from "@calcom/lib/logger";
import { defaultHandler } from "@calcom/lib/server"; import { defaultHandler } from "@calcom/lib/server";
import { getTimeFormatStringFromUserTimeFormat } from "@calcom/lib/timeFormat"; import { getTimeFormatStringFromUserTimeFormat } from "@calcom/lib/timeFormat";
@ -121,100 +122,185 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
} }
for (const reminder of unscheduledReminders) { for (const reminder of unscheduledReminders) {
if (!reminder.workflowStep || !reminder.booking) { if (!reminder.booking) {
continue; continue;
} }
try { if (!reminder.isMandatoryReminder && reminder.workflowStep) {
let sendTo; try {
let sendTo;
switch (reminder.workflowStep.action) { switch (reminder.workflowStep.action) {
case WorkflowActions.EMAIL_HOST: case WorkflowActions.EMAIL_HOST:
sendTo = reminder.booking.user?.email; sendTo = reminder.booking.user?.email;
break; break;
case WorkflowActions.EMAIL_ATTENDEE: case WorkflowActions.EMAIL_ATTENDEE:
sendTo = reminder.booking.attendees[0].email; sendTo = reminder.booking.attendees[0].email;
break; break;
case WorkflowActions.EMAIL_ADDRESS: case WorkflowActions.EMAIL_ADDRESS:
sendTo = reminder.workflowStep.sendTo; sendTo = reminder.workflowStep.sendTo;
} }
const name = const name =
reminder.workflowStep.action === WorkflowActions.EMAIL_ATTENDEE reminder.workflowStep.action === WorkflowActions.EMAIL_ATTENDEE
? reminder.booking.attendees[0].name ? reminder.booking.attendees[0].name
: reminder.booking.user?.name; : reminder.booking.user?.name;
const attendeeName = const attendeeName =
reminder.workflowStep.action === WorkflowActions.EMAIL_ATTENDEE reminder.workflowStep.action === WorkflowActions.EMAIL_ATTENDEE
? reminder.booking.user?.name ? reminder.booking.user?.name
: reminder.booking.attendees[0].name; : reminder.booking.attendees[0].name;
const timeZone = const timeZone =
reminder.workflowStep.action === WorkflowActions.EMAIL_ATTENDEE reminder.workflowStep.action === WorkflowActions.EMAIL_ATTENDEE
? reminder.booking.attendees[0].timeZone ? reminder.booking.attendees[0].timeZone
: reminder.booking.user?.timeZone; : reminder.booking.user?.timeZone;
const locale = const locale =
reminder.workflowStep.action === WorkflowActions.EMAIL_ATTENDEE || reminder.workflowStep.action === WorkflowActions.EMAIL_ATTENDEE ||
reminder.workflowStep.action === WorkflowActions.SMS_ATTENDEE reminder.workflowStep.action === WorkflowActions.SMS_ATTENDEE
? reminder.booking.attendees[0].locale ? reminder.booking.attendees[0].locale
: reminder.booking.user?.locale; : reminder.booking.user?.locale;
let emailContent = { let emailContent = {
emailSubject: reminder.workflowStep.emailSubject || "", emailSubject: reminder.workflowStep.emailSubject || "",
emailBody: `<body style="white-space: pre-wrap;">${reminder.workflowStep.reminderBody || ""}</body>`, emailBody: `<body style="white-space: pre-wrap;">${
}; reminder.workflowStep.reminderBody || ""
}</body>`,
let emailBodyEmpty = false;
if (reminder.workflowStep.reminderBody) {
const { responses } = getCalEventResponses({
bookingFields: reminder.booking.eventType?.bookingFields ?? null,
booking: reminder.booking,
});
const variables: VariablesType = {
eventName: reminder.booking.eventType?.title || "",
organizerName: reminder.booking.user?.name || "",
attendeeName: reminder.booking.attendees[0].name,
attendeeEmail: reminder.booking.attendees[0].email,
eventDate: dayjs(reminder.booking.startTime).tz(timeZone),
eventEndTime: dayjs(reminder.booking?.endTime).tz(timeZone),
timeZone: timeZone,
location: reminder.booking.location || "",
additionalNotes: reminder.booking.description,
responses: responses,
meetingUrl: bookingMetadataSchema.parse(reminder.booking.metadata || {})?.videoCallUrl,
cancelLink: `/booking/${reminder.booking.uid}?cancel=true`,
rescheduleLink: `/${reminder.booking.user?.username}/${reminder.booking.eventType?.slug}?rescheduleUid=${reminder.booking.uid}`,
}; };
const emailLocale = locale || "en";
const emailSubject = customTemplate(
reminder.workflowStep.emailSubject || "",
variables,
emailLocale,
getTimeFormatStringFromUserTimeFormat(reminder.booking.user?.timeFormat),
!!reminder.booking.user?.hideBranding
).text;
emailContent.emailSubject = emailSubject;
emailContent.emailBody = customTemplate(
reminder.workflowStep.reminderBody || "",
variables,
emailLocale,
getTimeFormatStringFromUserTimeFormat(reminder.booking.user?.timeFormat),
!!reminder.booking.user?.hideBranding
).html;
emailBodyEmpty = let emailBodyEmpty = false;
customTemplate(
if (reminder.workflowStep.reminderBody) {
const { responses } = getCalEventResponses({
bookingFields: reminder.booking.eventType?.bookingFields ?? null,
booking: reminder.booking,
});
const variables: VariablesType = {
eventName: reminder.booking.eventType?.title || "",
organizerName: reminder.booking.user?.name || "",
attendeeName: reminder.booking.attendees[0].name,
attendeeEmail: reminder.booking.attendees[0].email,
eventDate: dayjs(reminder.booking.startTime).tz(timeZone),
eventEndTime: dayjs(reminder.booking?.endTime).tz(timeZone),
timeZone: timeZone,
location: reminder.booking.location || "",
additionalNotes: reminder.booking.description,
responses: responses,
meetingUrl: bookingMetadataSchema.parse(reminder.booking.metadata || {})?.videoCallUrl,
cancelLink: `/booking/${reminder.booking.uid}?cancel=true`,
rescheduleLink: `/${reminder.booking.user?.username}/${reminder.booking.eventType?.slug}?rescheduleUid=${reminder.booking.uid}`,
};
const emailLocale = locale || "en";
const emailSubject = customTemplate(
reminder.workflowStep.emailSubject || "",
variables,
emailLocale,
getTimeFormatStringFromUserTimeFormat(reminder.booking.user?.timeFormat),
!!reminder.booking.user?.hideBranding
).text;
emailContent.emailSubject = emailSubject;
emailContent.emailBody = customTemplate(
reminder.workflowStep.reminderBody || "", reminder.workflowStep.reminderBody || "",
variables, variables,
emailLocale, emailLocale,
getTimeFormatStringFromUserTimeFormat(reminder.booking.user?.timeFormat) getTimeFormatStringFromUserTimeFormat(reminder.booking.user?.timeFormat),
).text.length === 0; !!reminder.booking.user?.hideBranding
} else if (reminder.workflowStep.template === WorkflowTemplates.REMINDER) { ).html;
emailBodyEmpty =
customTemplate(
reminder.workflowStep.reminderBody || "",
variables,
emailLocale,
getTimeFormatStringFromUserTimeFormat(reminder.booking.user?.timeFormat)
).text.length === 0;
} else if (reminder.workflowStep.template === WorkflowTemplates.REMINDER) {
emailContent = emailReminderTemplate(
false,
reminder.workflowStep.action,
getTimeFormatStringFromUserTimeFormat(reminder.booking.user?.timeFormat),
reminder.booking.startTime.toISOString() || "",
reminder.booking.endTime.toISOString() || "",
reminder.booking.eventType?.title || "",
timeZone || "",
attendeeName || "",
name || "",
!!reminder.booking.user?.hideBranding
);
}
if (emailContent.emailSubject.length > 0 && !emailBodyEmpty && sendTo) {
const batchIdResponse = await client.request({
url: "/v3/mail/batch",
method: "POST",
});
const batchId = batchIdResponse[1].batch_id;
if (reminder.workflowStep.action !== WorkflowActions.EMAIL_ADDRESS) {
sendEmailPromises.push(
sgMail.send({
to: sendTo,
from: {
email: senderEmail,
name: reminder.workflowStep.sender || SENDER_NAME,
},
subject: emailContent.emailSubject,
html: emailContent.emailBody,
batchId: batchId,
sendAt: dayjs(reminder.scheduledDate).unix(),
replyTo: reminder.booking.user?.email || senderEmail,
mailSettings: {
sandboxMode: {
enable: sandboxMode,
},
},
attachments: reminder.workflowStep.includeCalendarEvent
? [
{
content: Buffer.from(getiCalEventAsString(reminder.booking) || "").toString("base64"),
filename: "event.ics",
type: "text/calendar; method=REQUEST",
disposition: "attachment",
contentId: uuidv4(),
},
]
: undefined,
})
);
}
await prisma.workflowReminder.update({
where: {
id: reminder.id,
},
data: {
scheduled: true,
referenceId: batchId,
},
});
}
} catch (error) {
logger.error(`Error scheduling Email with error ${error}`);
}
} else if (reminder.isMandatoryReminder) {
try {
const sendTo = reminder.booking.attendees[0].email;
const name = reminder.booking.attendees[0].name;
const attendeeName = reminder.booking.user?.name;
const timeZone = reminder.booking.attendees[0].timeZone;
let emailContent = {
emailSubject: "",
emailBody: "",
};
const emailBodyEmpty = false;
emailContent = emailReminderTemplate( emailContent = emailReminderTemplate(
false, false,
reminder.workflowStep.action, WorkflowActions.EMAIL_ATTENDEE,
getTimeFormatStringFromUserTimeFormat(reminder.booking.user?.timeFormat), getTimeFormatStringFromUserTimeFormat(reminder.booking.user?.timeFormat),
reminder.booking.startTime.toISOString() || "", reminder.booking.startTime.toISOString() || "",
reminder.booking.endTime.toISOString() || "", reminder.booking.endTime.toISOString() || "",
@ -224,23 +310,20 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
name || "", name || "",
!!reminder.booking.user?.hideBranding !!reminder.booking.user?.hideBranding
); );
} if (emailContent.emailSubject.length > 0 && !emailBodyEmpty && sendTo) {
const batchIdResponse = await client.request({
url: "/v3/mail/batch",
method: "POST",
});
if (emailContent.emailSubject.length > 0 && !emailBodyEmpty && sendTo) { const batchId = batchIdResponse[1].batch_id;
const batchIdResponse = await client.request({
url: "/v3/mail/batch",
method: "POST",
});
const batchId = batchIdResponse[1].batch_id;
if (reminder.workflowStep.action !== WorkflowActions.EMAIL_ADDRESS) {
sendEmailPromises.push( sendEmailPromises.push(
sgMail.send({ sgMail.send({
to: sendTo, to: sendTo,
from: { from: {
email: senderEmail, email: senderEmail,
name: reminder.workflowStep.sender || "Cal.com", name: reminder.workflowStep?.sender || SENDER_NAME,
}, },
subject: emailContent.emailSubject, subject: emailContent.emailSubject,
html: emailContent.emailBody, html: emailContent.emailBody,
@ -252,33 +335,23 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
enable: sandboxMode, enable: sandboxMode,
}, },
}, },
attachments: reminder.workflowStep.includeCalendarEvent attachments: undefined,
? [
{
content: Buffer.from(getiCalEventAsString(reminder.booking) || "").toString("base64"),
filename: "event.ics",
type: "text/calendar; method=REQUEST",
disposition: "attachment",
contentId: uuidv4(),
},
]
: undefined,
}) })
); );
}
await prisma.workflowReminder.update({ await prisma.workflowReminder.update({
where: { where: {
id: reminder.id, id: reminder.id,
}, },
data: { data: {
scheduled: true, scheduled: true,
referenceId: batchId, referenceId: batchId,
}, },
}); });
}
} catch (error) {
logger.error(`Error scheduling Email with error ${error}`);
} }
} catch (error) {
logger.error(`Error scheduling Email with error ${error}`);
} }
} }

View File

@ -26,7 +26,10 @@ type PartialBooking =
> & { eventType: Partial<EventType> | null } & { user: Partial<User> | null }) > & { eventType: Partial<EventType> | null } & { user: Partial<User> | null })
| null; | null;
export type PartialWorkflowReminder = Pick<WorkflowReminder, "id" | "scheduledDate"> & { export type PartialWorkflowReminder = Pick<
WorkflowReminder,
"id" | "isMandatoryReminder" | "scheduledDate"
> & {
booking: PartialBooking | null; booking: PartialBooking | null;
} & { workflowStep: PartialWorkflowStep }; } & { workflowStep: PartialWorkflowStep };
@ -113,6 +116,7 @@ export async function getAllUnscheduledReminders(): Promise<PartialWorkflowRemin
const select: Prisma.WorkflowReminderSelect = { const select: Prisma.WorkflowReminderSelect = {
id: true, id: true,
scheduledDate: true, scheduledDate: true,
isMandatoryReminder: true,
workflowStep: { workflowStep: {
select: { select: {
action: true, action: true,

View File

@ -9,6 +9,7 @@ import { v4 as uuidv4 } from "uuid";
import dayjs from "@calcom/dayjs"; import dayjs from "@calcom/dayjs";
import { preprocessNameFieldDataWithVariant } from "@calcom/features/form-builder/utils"; import { preprocessNameFieldDataWithVariant } from "@calcom/features/form-builder/utils";
import { SENDER_NAME } from "@calcom/lib/constants";
import logger from "@calcom/lib/logger"; import logger from "@calcom/lib/logger";
import prisma from "@calcom/prisma"; import prisma from "@calcom/prisma";
import type { TimeUnit } from "@calcom/prisma/enums"; import type { TimeUnit } from "@calcom/prisma/enums";
@ -94,24 +95,46 @@ type ScheduleEmailReminderAction = Extract<
"EMAIL_HOST" | "EMAIL_ATTENDEE" | "EMAIL_ADDRESS" "EMAIL_HOST" | "EMAIL_ATTENDEE" | "EMAIL_ADDRESS"
>; >;
export const scheduleEmailReminder = async ( export interface ScheduleReminderArgs {
evt: BookingInfo, evt: BookingInfo;
triggerEvent: WorkflowTriggerEvents, triggerEvent: WorkflowTriggerEvents;
action: ScheduleEmailReminderAction,
timeSpan: { timeSpan: {
time: number | null; time: number | null;
timeUnit: TimeUnit | null; timeUnit: TimeUnit | null;
}, };
sendTo: MailData["to"], template: WorkflowTemplates;
emailSubject: string, sender?: string | null;
emailBody: string, workflowStepId?: number;
workflowStepId: number, seatReferenceUid?: string;
template: WorkflowTemplates, }
sender: string,
hideBranding?: boolean, interface scheduleEmailReminderArgs extends ScheduleReminderArgs {
seatReferenceUid?: string, sendTo: MailData["to"];
includeCalendarEvent?: boolean action: ScheduleEmailReminderAction;
) => { emailSubject?: string;
emailBody?: string;
hideBranding?: boolean;
includeCalendarEvent?: boolean;
isMandatoryReminder?: boolean;
}
export const scheduleEmailReminder = async (args: scheduleEmailReminderArgs) => {
const {
evt,
triggerEvent,
timeSpan,
template,
sender,
workflowStepId,
seatReferenceUid,
sendTo,
emailSubject = "",
emailBody = "",
hideBranding,
includeCalendarEvent,
isMandatoryReminder,
action,
} = args;
if (action === WorkflowActions.EMAIL_ADDRESS) return; if (action === WorkflowActions.EMAIL_ADDRESS) return;
const { startTime, endTime } = evt; const { startTime, endTime } = evt;
const uid = evt.uid as string; const uid = evt.uid as string;
@ -251,7 +274,7 @@ export const scheduleEmailReminder = async (
to: data.to, to: data.to,
from: { from: {
email: senderEmail, email: senderEmail,
name: sender, name: sender || SENDER_NAME,
}, },
subject: emailContent.emailSubject, subject: emailContent.emailSubject,
html: emailContent.emailBody, html: emailContent.emailBody,
@ -289,7 +312,7 @@ export const scheduleEmailReminder = async (
// TODO: Maybe don't await for this? // TODO: Maybe don't await for this?
await Promise.all(promises); await Promise.all(promises);
} catch (error) { } catch (error) {
console.log("Error sending Email"); log.error("Error sending Email");
} }
} else if ( } else if (
(triggerEvent === WorkflowTriggerEvents.BEFORE_EVENT || (triggerEvent === WorkflowTriggerEvents.BEFORE_EVENT ||
@ -311,32 +334,59 @@ export const scheduleEmailReminder = async (
}, },
triggerEvent triggerEvent
); );
if (!isMandatoryReminder) {
await prisma.workflowReminder.create({
data: {
bookingUid: uid,
workflowStepId: workflowStepId,
method: WorkflowMethods.EMAIL,
scheduledDate: scheduledDate.toDate(),
scheduled: true,
referenceId: batchId,
seatReferenceId: seatReferenceUid,
},
});
} else {
await prisma.workflowReminder.create({
data: {
bookingUid: uid,
method: WorkflowMethods.EMAIL,
scheduledDate: scheduledDate.toDate(),
scheduled: true,
referenceId: batchId,
seatReferenceId: seatReferenceUid,
isMandatoryReminder: true,
},
});
}
} catch (error) {
log.error(`Error scheduling email with error ${error}`);
}
} else if (scheduledDate.isAfter(currentDate.add(72, "hour"))) {
// Write to DB and send to CRON if scheduled reminder date is past 72 hours
if (!isMandatoryReminder) {
await prisma.workflowReminder.create({ await prisma.workflowReminder.create({
data: { data: {
bookingUid: uid, bookingUid: uid,
workflowStepId: workflowStepId, workflowStepId: workflowStepId,
method: WorkflowMethods.EMAIL, method: WorkflowMethods.EMAIL,
scheduledDate: scheduledDate.toDate(), scheduledDate: scheduledDate.toDate(),
scheduled: true, scheduled: false,
referenceId: batchId,
seatReferenceId: seatReferenceUid, seatReferenceId: seatReferenceUid,
}, },
}); });
} catch (error) { } else {
console.log(`Error scheduling email with error ${error}`); await prisma.workflowReminder.create({
data: {
bookingUid: uid,
method: WorkflowMethods.EMAIL,
scheduledDate: scheduledDate.toDate(),
scheduled: false,
seatReferenceId: seatReferenceUid,
isMandatoryReminder: true,
},
});
} }
} else if (scheduledDate.isAfter(currentDate.add(72, "hour"))) {
// Write to DB and send to CRON if scheduled reminder date is past 72 hours
await prisma.workflowReminder.create({
data: {
bookingUid: uid,
workflowStepId: workflowStepId,
method: WorkflowMethods.EMAIL,
scheduledDate: scheduledDate.toDate(),
scheduled: false,
seatReferenceId: seatReferenceUid,
},
});
} }
} }
}; };
@ -362,6 +412,6 @@ export const deleteScheduledEmailReminder = async (reminderId: number, reference
}, },
}); });
} catch (error) { } catch (error) {
console.log(`Error canceling reminder with error ${error}`); log.error(`Error canceling reminder with error ${error}`);
} }
}; };

View File

@ -1,14 +1,16 @@
import type { Workflow, WorkflowsOnEventTypes, WorkflowStep } from "@prisma/client"; import type { Workflow, WorkflowsOnEventTypes, WorkflowStep } from "@prisma/client";
import { import {
isSMSAction,
isTextMessageToAttendeeAction, isTextMessageToAttendeeAction,
isWhatsappAction, isWhatsappAction,
} from "@calcom/features/ee/workflows/lib/actionHelperFunctions"; } from "@calcom/features/ee/workflows/lib/actionHelperFunctions";
import { SENDER_ID, SENDER_NAME } from "@calcom/lib/constants"; import { SENDER_NAME } from "@calcom/lib/constants";
import { WorkflowActions, WorkflowMethods, WorkflowTriggerEvents } from "@calcom/prisma/enums"; import { WorkflowActions, WorkflowMethods, WorkflowTriggerEvents } from "@calcom/prisma/enums";
import type { CalendarEvent } from "@calcom/types/Calendar"; import type { CalendarEvent } from "@calcom/types/Calendar";
import { deleteScheduledEmailReminder, scheduleEmailReminder } from "./emailReminderManager"; import { deleteScheduledEmailReminder, scheduleEmailReminder } from "./emailReminderManager";
import type { ScheduleTextReminderAction } from "./smsReminderManager";
import { deleteScheduledSMSReminder, scheduleSMSReminder } from "./smsReminderManager"; import { deleteScheduledSMSReminder, scheduleSMSReminder } from "./smsReminderManager";
import { deleteScheduledWhatsappReminder, scheduleWhatsappReminder } from "./whatsappReminderManager"; import { deleteScheduledWhatsappReminder, scheduleWhatsappReminder } from "./whatsappReminderManager";
@ -51,26 +53,26 @@ const processWorkflowStep = async (
) => { ) => {
if (isTextMessageToAttendeeAction(step.action) && !eventTypeRequiresConfirmation) return; if (isTextMessageToAttendeeAction(step.action) && !eventTypeRequiresConfirmation) return;
if (step.action === WorkflowActions.SMS_ATTENDEE || step.action === WorkflowActions.SMS_NUMBER) { if (isSMSAction(step.action)) {
const sendTo = step.action === WorkflowActions.SMS_ATTENDEE ? smsReminderNumber : step.sendTo; const sendTo = step.action === WorkflowActions.SMS_ATTENDEE ? smsReminderNumber : step.sendTo;
await scheduleSMSReminder( await scheduleSMSReminder({
evt, evt,
sendTo, reminderPhone: sendTo,
workflow.trigger, triggerEvent: workflow.trigger,
step.action, action: step.action as ScheduleTextReminderAction,
{ timeSpan: {
time: workflow.time, time: workflow.time,
timeUnit: workflow.timeUnit, timeUnit: workflow.timeUnit,
}, },
step.reminderBody || "", message: step.reminderBody || "",
step.id, workflowStepId: step.id,
step.template, template: step.template,
step.sender || SENDER_ID, sender: step.sender,
workflow.userId, userId: workflow.userId,
workflow.teamId, teamId: workflow.teamId,
step.numberVerificationPending, isVerificationPending: step.numberVerificationPending,
seatReferenceUid seatReferenceUid,
); });
} else if (step.action === WorkflowActions.EMAIL_ATTENDEE || step.action === WorkflowActions.EMAIL_HOST) { } else if (step.action === WorkflowActions.EMAIL_ATTENDEE || step.action === WorkflowActions.EMAIL_HOST) {
let sendTo: string[] = []; let sendTo: string[] = [];
@ -88,43 +90,43 @@ const processWorkflowStep = async (
break; break;
} }
await scheduleEmailReminder( await scheduleEmailReminder({
evt, evt,
workflow.trigger, triggerEvent: workflow.trigger,
step.action, action: step.action,
{ timeSpan: {
time: workflow.time, time: workflow.time,
timeUnit: workflow.timeUnit, timeUnit: workflow.timeUnit,
}, },
sendTo, sendTo,
step.emailSubject || "", emailSubject: step.emailSubject || "",
step.reminderBody || "", emailBody: step.reminderBody || "",
step.id, template: step.template,
step.template, sender: step.sender || SENDER_NAME,
step.sender || SENDER_NAME, workflowStepId: step.id,
hideBranding, hideBranding,
seatReferenceUid, seatReferenceUid,
step.includeCalendarEvent includeCalendarEvent: step.includeCalendarEvent,
); });
} else if (isWhatsappAction(step.action)) { } else if (isWhatsappAction(step.action)) {
const sendTo = step.action === WorkflowActions.WHATSAPP_ATTENDEE ? smsReminderNumber : step.sendTo; const sendTo = step.action === WorkflowActions.WHATSAPP_ATTENDEE ? smsReminderNumber : step.sendTo;
await scheduleWhatsappReminder( await scheduleWhatsappReminder({
evt, evt,
sendTo, reminderPhone: sendTo,
workflow.trigger, triggerEvent: workflow.trigger,
step.action, action: step.action as ScheduleTextReminderAction,
{ timeSpan: {
time: workflow.time, time: workflow.time,
timeUnit: workflow.timeUnit, timeUnit: workflow.timeUnit,
}, },
step.reminderBody || "", message: step.reminderBody || "",
step.id, workflowStepId: step.id,
step.template, template: step.template,
workflow.userId, userId: workflow.userId,
workflow.teamId, teamId: workflow.teamId,
step.numberVerificationPending, isVerificationPending: step.numberVerificationPending,
seatReferenceUid seatReferenceUid,
); });
} }
}; };
@ -164,7 +166,6 @@ export const scheduleWorkflowReminders = async (args: ScheduleWorkflowRemindersA
) { ) {
continue; continue;
} }
for (const step of workflow.steps) { for (const step of workflow.steps) {
await processWorkflowStep(workflow, step, { await processWorkflowStep(workflow, step, {
calendarEvent: evt, calendarEvent: evt,

View File

@ -0,0 +1,72 @@
import type { Workflow, WorkflowsOnEventTypes, WorkflowStep } from "@prisma/client";
import type { getEventTypesFromDB } from "@calcom/features/bookings/lib/handleNewBooking";
import { scheduleEmailReminder } from "@calcom/features/ee/workflows/lib/reminders/emailReminderManager";
import type { BookingInfo } from "@calcom/features/ee/workflows/lib/reminders/smsReminderManager";
import type { getDefaultEvent } from "@calcom/lib/defaultEvents";
import logger from "@calcom/lib/logger";
import { WorkflowTriggerEvents, TimeUnit, WorkflowActions, WorkflowTemplates } from "@calcom/prisma/enums";
const log = logger.getSubLogger({ prefix: ["[scheduleMandatoryReminder]"] });
export type NewBookingEventType =
| Awaited<ReturnType<typeof getDefaultEvent>>
| Awaited<ReturnType<typeof getEventTypesFromDB>>;
export async function scheduleMandatoryReminder(
evt: BookingInfo,
workflows: (WorkflowsOnEventTypes & {
workflow: Workflow & {
steps: WorkflowStep[];
};
})[],
requiresConfirmation: boolean,
hideBranding: boolean,
seatReferenceUid: string | undefined
) {
try {
const hasExistingWorkflow = workflows.some((workflow) => {
return (
workflow.workflow?.trigger === WorkflowTriggerEvents.BEFORE_EVENT &&
((workflow.workflow.time !== null &&
workflow.workflow.time <= 12 &&
workflow.workflow?.timeUnit === TimeUnit.HOUR) ||
(workflow.workflow.time !== null &&
workflow.workflow.time <= 720 &&
workflow.workflow?.timeUnit === TimeUnit.MINUTE)) &&
workflow.workflow?.steps.some((step) => step?.action === WorkflowActions.EMAIL_ATTENDEE)
);
});
if (
!hasExistingWorkflow &&
evt.attendees.some((attendee) => attendee.email.includes("@gmail.com")) &&
!requiresConfirmation
) {
try {
const filteredAttendees =
evt.attendees?.filter((attendee) => attendee.email.includes("@gmail.com")) || [];
await scheduleEmailReminder({
evt,
triggerEvent: WorkflowTriggerEvents.BEFORE_EVENT,
action: WorkflowActions.EMAIL_ATTENDEE,
timeSpan: {
time: 1,
timeUnit: TimeUnit.HOUR,
},
sendTo: filteredAttendees,
template: WorkflowTemplates.REMINDER,
hideBranding,
seatReferenceUid,
includeCalendarEvent: false,
isMandatoryReminder: true,
});
} catch (error) {
log.error("Error while scheduling mandatory reminders", JSON.stringify({ error }));
}
}
} catch (error) {
log.error("Error while scheduling mandatory reminders", JSON.stringify({ error }));
}
}

View File

@ -1,15 +1,16 @@
import dayjs from "@calcom/dayjs"; import dayjs from "@calcom/dayjs";
import { SENDER_ID } from "@calcom/lib/constants";
import logger from "@calcom/lib/logger"; import logger from "@calcom/lib/logger";
import type { TimeFormat } from "@calcom/lib/timeFormat"; import type { TimeFormat } from "@calcom/lib/timeFormat";
import prisma from "@calcom/prisma"; import prisma from "@calcom/prisma";
import type { Prisma } from "@calcom/prisma/client"; import type { Prisma } from "@calcom/prisma/client";
import type { TimeUnit } from "@calcom/prisma/enums";
import { WorkflowTemplates, WorkflowActions, WorkflowMethods } from "@calcom/prisma/enums"; import { WorkflowTemplates, WorkflowActions, WorkflowMethods } from "@calcom/prisma/enums";
import { WorkflowTriggerEvents } from "@calcom/prisma/enums"; import { WorkflowTriggerEvents } from "@calcom/prisma/enums";
import { bookingMetadataSchema } from "@calcom/prisma/zod-utils"; import { bookingMetadataSchema } from "@calcom/prisma/zod-utils";
import type { CalEventResponses, RecurringEvent } from "@calcom/types/Calendar"; import type { CalEventResponses, RecurringEvent } from "@calcom/types/Calendar";
import { getSenderId } from "../alphanumericSenderIdSupport"; import { getSenderId } from "../alphanumericSenderIdSupport";
import type { ScheduleReminderArgs } from "./emailReminderManager";
import * as twilio from "./smsProviders/twilioProvider"; import * as twilio from "./smsProviders/twilioProvider";
import type { VariablesType } from "./templates/customTemplate"; import type { VariablesType } from "./templates/customTemplate";
import customTemplate from "./templates/customTemplate"; import customTemplate from "./templates/customTemplate";
@ -55,33 +56,42 @@ export type BookingInfo = {
metadata?: Prisma.JsonValue; metadata?: Prisma.JsonValue;
}; };
type ScheduleSMSReminderAction = Extract<WorkflowActions, "SMS_ATTENDEE" | "SMS_NUMBER">; export type ScheduleTextReminderAction = Extract<
WorkflowActions,
"SMS_ATTENDEE" | "SMS_NUMBER" | "WHATSAPP_ATTENDEE" | "WHATSAPP_NUMBER"
>;
export interface ScheduleTextReminderArgs extends ScheduleReminderArgs {
reminderPhone: string | null;
message: string;
action: ScheduleTextReminderAction;
userId?: number | null;
teamId?: number | null;
isVerificationPending?: boolean;
}
export const scheduleSMSReminder = async ( export const scheduleSMSReminder = async (args: ScheduleTextReminderArgs) => {
evt: BookingInfo, const {
reminderPhone: string | null, evt,
triggerEvent: WorkflowTriggerEvents, reminderPhone,
action: ScheduleSMSReminderAction, triggerEvent,
timeSpan: { action,
time: number | null; timeSpan,
timeUnit: TimeUnit | null; message = "",
}, workflowStepId,
message: string, template,
workflowStepId: number, sender,
template: WorkflowTemplates, userId,
sender: string, teamId,
userId?: number | null, isVerificationPending = false,
teamId?: number | null, seatReferenceUid,
isVerificationPending = false, } = args;
seatReferenceUid?: string
) => {
const { startTime, endTime } = evt; const { startTime, endTime } = evt;
const uid = evt.uid as string; const uid = evt.uid as string;
const currentDate = dayjs(); const currentDate = dayjs();
const timeUnit: timeUnitLowerCase | undefined = timeSpan.timeUnit?.toLocaleLowerCase() as timeUnitLowerCase; const timeUnit: timeUnitLowerCase | undefined = timeSpan.timeUnit?.toLocaleLowerCase() as timeUnitLowerCase;
let scheduledDate = null; let scheduledDate = null;
const senderID = getSenderId(reminderPhone, sender); const senderID = getSenderId(reminderPhone, sender || SENDER_ID);
//SMS_ATTENDEE action does not need to be verified //SMS_ATTENDEE action does not need to be verified
//isVerificationPending is from all already existing workflows (once they edit their workflow, they will also have to verify the number) //isVerificationPending is from all already existing workflows (once they edit their workflow, they will also have to verify the number)
@ -126,7 +136,9 @@ export const scheduleSMSReminder = async (
? attendeeToBeUsedInSMS.language?.locale ? attendeeToBeUsedInSMS.language?.locale
: evt.organizer.language.locale; : evt.organizer.language.locale;
if (message) { let smsMessage = message;
if (smsMessage) {
const variables: VariablesType = { const variables: VariablesType = {
eventName: evt.title, eventName: evt.title,
organizerName: evt.organizer.name, organizerName: evt.organizer.name,
@ -144,10 +156,10 @@ export const scheduleSMSReminder = async (
cancelLink: `/booking/${evt.uid}?cancel=true`, cancelLink: `/booking/${evt.uid}?cancel=true`,
rescheduleLink: `/${evt.organizer.username}/${evt.eventType.slug}?rescheduleUid=${evt.uid}`, rescheduleLink: `/${evt.organizer.username}/${evt.eventType.slug}?rescheduleUid=${evt.uid}`,
}; };
const customMessage = customTemplate(message, variables, locale, evt.organizer.timeFormat); const customMessage = customTemplate(smsMessage, variables, locale, evt.organizer.timeFormat);
message = customMessage.text; smsMessage = customMessage.text;
} else if (template === WorkflowTemplates.REMINDER) { } else if (template === WorkflowTemplates.REMINDER) {
message = smsMessage =
smsReminderTemplate( smsReminderTemplate(
false, false,
action, action,
@ -161,9 +173,9 @@ export const scheduleSMSReminder = async (
} }
// Allows debugging generated email content without waiting for sendgrid to send emails // Allows debugging generated email content without waiting for sendgrid to send emails
log.debug(`Sending sms for trigger ${triggerEvent}`, message); log.debug(`Sending sms for trigger ${triggerEvent}`, smsMessage);
if (message.length > 0 && reminderPhone && isNumberVerified) { if (smsMessage.length > 0 && reminderPhone && isNumberVerified) {
//send SMS when event is booked/cancelled/rescheduled //send SMS when event is booked/cancelled/rescheduled
if ( if (
triggerEvent === WorkflowTriggerEvents.NEW_EVENT || triggerEvent === WorkflowTriggerEvents.NEW_EVENT ||
@ -171,9 +183,9 @@ export const scheduleSMSReminder = async (
triggerEvent === WorkflowTriggerEvents.RESCHEDULE_EVENT triggerEvent === WorkflowTriggerEvents.RESCHEDULE_EVENT
) { ) {
try { try {
await twilio.sendSMS(reminderPhone, message, senderID); await twilio.sendSMS(reminderPhone, smsMessage, senderID);
} catch (error) { } catch (error) {
console.log(`Error sending SMS with error ${error}`); log.error(`Error sending SMS with error ${error}`);
} }
} else if ( } else if (
(triggerEvent === WorkflowTriggerEvents.BEFORE_EVENT || (triggerEvent === WorkflowTriggerEvents.BEFORE_EVENT ||
@ -188,7 +200,7 @@ export const scheduleSMSReminder = async (
try { try {
const scheduledSMS = await twilio.scheduleSMS( const scheduledSMS = await twilio.scheduleSMS(
reminderPhone, reminderPhone,
message, smsMessage,
scheduledDate.toDate(), scheduledDate.toDate(),
senderID senderID
); );
@ -205,7 +217,7 @@ export const scheduleSMSReminder = async (
}, },
}); });
} catch (error) { } catch (error) {
console.log(`Error scheduling SMS with error ${error}`); log.error(`Error scheduling SMS with error ${error}`);
} }
} else if (scheduledDate.isAfter(currentDate.add(7, "day"))) { } else if (scheduledDate.isAfter(currentDate.add(7, "day"))) {
// Write to DB and send to CRON if scheduled reminder date is past 7 days // Write to DB and send to CRON if scheduled reminder date is past 7 days
@ -236,6 +248,6 @@ export const deleteScheduledSMSReminder = async (reminderId: number, referenceId
}, },
}); });
} catch (error) { } catch (error) {
console.log(`Error canceling reminder with error ${error}`); log.error(`Error canceling reminder with error ${error}`);
} }
}; };

View File

@ -1,5 +1,3 @@
import type { TimeUnit } from "@prisma/client";
import dayjs from "@calcom/dayjs"; import dayjs from "@calcom/dayjs";
import logger from "@calcom/lib/logger"; import logger from "@calcom/lib/logger";
import prisma from "@calcom/prisma"; import prisma from "@calcom/prisma";
@ -11,7 +9,7 @@ import {
} from "@calcom/prisma/enums"; } from "@calcom/prisma/enums";
import * as twilio from "./smsProviders/twilioProvider"; import * as twilio from "./smsProviders/twilioProvider";
import type { BookingInfo, timeUnitLowerCase } from "./smsReminderManager"; import type { ScheduleTextReminderArgs, timeUnitLowerCase } from "./smsReminderManager";
import { deleteScheduledSMSReminder } from "./smsReminderManager"; import { deleteScheduledSMSReminder } from "./smsReminderManager";
import { import {
whatsappEventCancelledTemplate, whatsappEventCancelledTemplate,
@ -22,23 +20,21 @@ import {
const log = logger.getSubLogger({ prefix: ["[whatsappReminderManager]"] }); const log = logger.getSubLogger({ prefix: ["[whatsappReminderManager]"] });
export const scheduleWhatsappReminder = async ( export const scheduleWhatsappReminder = async (args: ScheduleTextReminderArgs) => {
evt: BookingInfo, const {
reminderPhone: string | null, evt,
triggerEvent: WorkflowTriggerEvents, reminderPhone,
action: WorkflowActions, triggerEvent,
timeSpan: { action,
time: number | null; timeSpan,
timeUnit: TimeUnit | null; message = "",
}, workflowStepId,
message: string, template,
workflowStepId: number, userId,
template: WorkflowTemplates, teamId,
userId?: number | null, isVerificationPending = false,
teamId?: number | null, seatReferenceUid,
isVerificationPending = false, } = args;
seatReferenceUid?: string
) => {
const { startTime, endTime } = evt; const { startTime, endTime } = evt;
const uid = evt.uid as string; const uid = evt.uid as string;
const currentDate = dayjs(); const currentDate = dayjs();
@ -72,9 +68,11 @@ export const scheduleWhatsappReminder = async (
const timeZone = const timeZone =
action === WorkflowActions.WHATSAPP_ATTENDEE ? evt.attendees[0].timeZone : evt.organizer.timeZone; action === WorkflowActions.WHATSAPP_ATTENDEE ? evt.attendees[0].timeZone : evt.organizer.timeZone;
let textMessage = message;
switch (template) { switch (template) {
case WorkflowTemplates.REMINDER: case WorkflowTemplates.REMINDER:
message = textMessage =
whatsappReminderTemplate( whatsappReminderTemplate(
false, false,
action, action,
@ -87,7 +85,7 @@ export const scheduleWhatsappReminder = async (
) || message; ) || message;
break; break;
case WorkflowTemplates.CANCELLED: case WorkflowTemplates.CANCELLED:
message = textMessage =
whatsappEventCancelledTemplate( whatsappEventCancelledTemplate(
false, false,
action, action,
@ -100,7 +98,7 @@ export const scheduleWhatsappReminder = async (
) || message; ) || message;
break; break;
case WorkflowTemplates.RESCHEDULED: case WorkflowTemplates.RESCHEDULED:
message = textMessage =
whatsappEventRescheduledTemplate( whatsappEventRescheduledTemplate(
false, false,
action, action,
@ -113,7 +111,7 @@ export const scheduleWhatsappReminder = async (
) || message; ) || message;
break; break;
case WorkflowTemplates.COMPLETED: case WorkflowTemplates.COMPLETED:
message = textMessage =
whatsappEventCompletedTemplate( whatsappEventCompletedTemplate(
false, false,
action, action,
@ -126,7 +124,7 @@ export const scheduleWhatsappReminder = async (
) || message; ) || message;
break; break;
default: default:
message = textMessage =
whatsappReminderTemplate( whatsappReminderTemplate(
false, false,
action, action,
@ -140,8 +138,8 @@ export const scheduleWhatsappReminder = async (
} }
// Allows debugging generated whatsapp content without waiting for twilio to send whatsapp messages // Allows debugging generated whatsapp content without waiting for twilio to send whatsapp messages
log.debug(`Sending Whatsapp for trigger ${triggerEvent}`, message); log.debug(`Sending Whatsapp for trigger ${triggerEvent}`, textMessage);
if (message.length > 0 && reminderPhone && isNumberVerified) { if (textMessage.length > 0 && reminderPhone && isNumberVerified) {
//send WHATSAPP when event is booked/cancelled/rescheduled //send WHATSAPP when event is booked/cancelled/rescheduled
if ( if (
triggerEvent === WorkflowTriggerEvents.NEW_EVENT || triggerEvent === WorkflowTriggerEvents.NEW_EVENT ||
@ -149,7 +147,7 @@ export const scheduleWhatsappReminder = async (
triggerEvent === WorkflowTriggerEvents.RESCHEDULE_EVENT triggerEvent === WorkflowTriggerEvents.RESCHEDULE_EVENT
) { ) {
try { try {
await twilio.sendSMS(reminderPhone, message, "", true); await twilio.sendSMS(reminderPhone, textMessage, "", true);
} catch (error) { } catch (error) {
console.log(`Error sending WHATSAPP with error ${error}`); console.log(`Error sending WHATSAPP with error ${error}`);
} }
@ -166,7 +164,7 @@ export const scheduleWhatsappReminder = async (
try { try {
const scheduledWHATSAPP = await twilio.scheduleSMS( const scheduledWHATSAPP = await twilio.scheduleSMS(
reminderPhone, reminderPhone,
message, textMessage,
scheduledDate.toDate(), scheduledDate.toDate(),
"", "",
true true

View File

@ -0,0 +1,2 @@
-- AlterTable
ALTER TABLE "WorkflowReminder" ADD COLUMN "isMandatoryReminder" BOOLEAN DEFAULT false;

View File

@ -823,17 +823,18 @@ enum TimeUnit {
} }
model WorkflowReminder { model WorkflowReminder {
id Int @id @default(autoincrement()) id Int @id @default(autoincrement())
bookingUid String? bookingUid String?
booking Booking? @relation(fields: [bookingUid], references: [uid]) booking Booking? @relation(fields: [bookingUid], references: [uid])
method WorkflowMethods method WorkflowMethods
scheduledDate DateTime scheduledDate DateTime
referenceId String? @unique referenceId String? @unique
scheduled Boolean scheduled Boolean
workflowStepId Int? workflowStepId Int?
workflowStep WorkflowStep? @relation(fields: [workflowStepId], references: [id]) workflowStep WorkflowStep? @relation(fields: [workflowStepId], references: [id])
cancelled Boolean? cancelled Boolean?
seatReferenceId String? seatReferenceId String?
isMandatoryReminder Boolean? @default(false)
@@index([bookingUid]) @@index([bookingUid])
@@index([workflowStepId]) @@index([workflowStepId])

View File

@ -10,7 +10,6 @@ import {
deleteScheduledWhatsappReminder, deleteScheduledWhatsappReminder,
scheduleWhatsappReminder, scheduleWhatsappReminder,
} from "@calcom/features/ee/workflows/lib/reminders/whatsappReminderManager"; } from "@calcom/features/ee/workflows/lib/reminders/whatsappReminderManager";
import { SENDER_ID, SENDER_NAME } from "@calcom/lib/constants";
import { getTimeFormatStringFromUserTimeFormat } from "@calcom/lib/timeFormat"; import { getTimeFormatStringFromUserTimeFormat } from "@calcom/lib/timeFormat";
import { prisma } from "@calcom/prisma"; import { prisma } from "@calcom/prisma";
import { BookingStatus } from "@calcom/prisma/client"; import { BookingStatus } from "@calcom/prisma/client";
@ -197,54 +196,54 @@ export const activateEventTypeHandler = async ({ ctx, input }: ActivateEventType
break; break;
} }
await scheduleEmailReminder( await scheduleEmailReminder({
bookingInfo, evt: bookingInfo,
eventTypeWorkflow.trigger, triggerEvent: eventTypeWorkflow.trigger,
step.action, action: step.action,
{ timeSpan: {
time: eventTypeWorkflow.time, time: eventTypeWorkflow.time,
timeUnit: eventTypeWorkflow.timeUnit, timeUnit: eventTypeWorkflow.timeUnit,
}, },
sendTo, sendTo,
step.emailSubject || "", emailSubject: step.emailSubject || "",
step.reminderBody || "", emailBody: step.reminderBody || "",
step.id, template: step.template,
step.template, sender: step.sender,
step.sender || SENDER_NAME workflowStepId: step.id,
); });
} else if (step.action === WorkflowActions.SMS_NUMBER && step.sendTo) { } else if (step.action === WorkflowActions.SMS_NUMBER && step.sendTo) {
await scheduleSMSReminder( await scheduleSMSReminder({
bookingInfo, evt: bookingInfo,
step.sendTo, reminderPhone: step.sendTo,
eventTypeWorkflow.trigger, triggerEvent: eventTypeWorkflow.trigger,
step.action, action: step.action,
{ timeSpan: {
time: eventTypeWorkflow.time, time: eventTypeWorkflow.time,
timeUnit: eventTypeWorkflow.timeUnit, timeUnit: eventTypeWorkflow.timeUnit,
}, },
step.reminderBody || "", message: step.reminderBody || "",
step.id, workflowStepId: step.id,
step.template, template: step.template,
step.sender || SENDER_ID, sender: step.sender,
booking.userId, userId: booking.userId,
eventTypeWorkflow.teamId teamId: eventTypeWorkflow.teamId,
); });
} else if (step.action === WorkflowActions.WHATSAPP_NUMBER && step.sendTo) { } else if (step.action === WorkflowActions.WHATSAPP_NUMBER && step.sendTo) {
await scheduleWhatsappReminder( await scheduleWhatsappReminder({
bookingInfo, evt: bookingInfo,
step.sendTo, reminderPhone: step.sendTo,
eventTypeWorkflow.trigger, triggerEvent: eventTypeWorkflow.trigger,
step.action, action: step.action,
{ timeSpan: {
time: eventTypeWorkflow.time, time: eventTypeWorkflow.time,
timeUnit: eventTypeWorkflow.timeUnit, timeUnit: eventTypeWorkflow.timeUnit,
}, },
step.reminderBody || "", message: step.reminderBody || "",
step.id, workflowStepId: step.id,
step.template, template: step.template,
booking.userId, userId: booking.userId,
eventTypeWorkflow.teamId teamId: eventTypeWorkflow.teamId,
); });
} }
} }
} }

View File

@ -17,7 +17,7 @@ import {
deleteScheduledWhatsappReminder, deleteScheduledWhatsappReminder,
scheduleWhatsappReminder, scheduleWhatsappReminder,
} from "@calcom/features/ee/workflows/lib/reminders/whatsappReminderManager"; } from "@calcom/features/ee/workflows/lib/reminders/whatsappReminderManager";
import { IS_SELF_HOSTED, SENDER_ID, SENDER_NAME } from "@calcom/lib/constants"; import { IS_SELF_HOSTED } from "@calcom/lib/constants";
import hasKeyInMetadata from "@calcom/lib/hasKeyInMetadata"; import hasKeyInMetadata from "@calcom/lib/hasKeyInMetadata";
import { getTimeFormatStringFromUserTimeFormat } from "@calcom/lib/timeFormat"; import { getTimeFormatStringFromUserTimeFormat } from "@calcom/lib/timeFormat";
import type { PrismaClient } from "@calcom/prisma"; import type { PrismaClient } from "@calcom/prisma";
@ -315,54 +315,54 @@ export const updateHandler = async ({ ctx, input }: UpdateOptions) => {
sendTo = step.sendTo || "";*/ sendTo = step.sendTo || "";*/
} }
await scheduleEmailReminder( await scheduleEmailReminder({
bookingInfo, evt: bookingInfo,
trigger, triggerEvent: trigger,
step.action, action: step.action,
{ timeSpan: {
time, time,
timeUnit, timeUnit,
}, },
sendTo, sendTo,
step.emailSubject || "", emailSubject: step.emailSubject || "",
step.reminderBody || "", emailBody: step.reminderBody || "",
step.id, template: step.template,
step.template, sender: step.senderName,
step.senderName || SENDER_NAME workflowStepId: step.id,
); });
} else if (step.action === WorkflowActions.SMS_NUMBER) { } else if (step.action === WorkflowActions.SMS_NUMBER) {
await scheduleSMSReminder( await scheduleSMSReminder({
bookingInfo, evt: bookingInfo,
step.sendTo || "", reminderPhone: step.sendTo || "",
trigger, triggerEvent: trigger,
step.action, action: step.action,
{ timeSpan: {
time, time,
timeUnit, timeUnit,
}, },
step.reminderBody || "", message: step.reminderBody || "",
step.id, workflowStepId: step.id,
step.template, template: step.template,
step.sender || SENDER_ID, sender: step.sender,
user.id, userId: user.id,
userWorkflow.teamId teamId: userWorkflow.teamId,
); });
} else if (step.action === WorkflowActions.WHATSAPP_NUMBER) { } else if (step.action === WorkflowActions.WHATSAPP_NUMBER) {
await scheduleWhatsappReminder( await scheduleWhatsappReminder({
bookingInfo, evt: bookingInfo,
step.sendTo || "", reminderPhone: step.sendTo || "",
trigger, triggerEvent: trigger,
step.action, action: step.action,
{ timeSpan: {
time, time,
timeUnit, timeUnit,
}, },
step.reminderBody || "", message: step.reminderBody || "",
step.id || 0, workflowStepId: step.id || 0,
step.template, template: step.template,
user.id, userId: user.id,
userWorkflow.teamId teamId: userWorkflow.teamId,
); });
} }
}); });
await Promise.all(promiseScheduleReminders); await Promise.all(promiseScheduleReminders);
@ -552,54 +552,54 @@ export const updateHandler = async ({ ctx, input }: UpdateOptions) => {
sendTo = newStep.sendTo || "";*/ sendTo = newStep.sendTo || "";*/
} }
await scheduleEmailReminder( await scheduleEmailReminder({
bookingInfo, evt: bookingInfo,
trigger, triggerEvent: trigger,
newStep.action, action: newStep.action,
{ timeSpan: {
time, time,
timeUnit, timeUnit,
}, },
sendTo, sendTo,
newStep.emailSubject || "", emailSubject: newStep.emailSubject || "",
newStep.reminderBody || "", emailBody: newStep.reminderBody || "",
newStep.id, template: newStep.template,
newStep.template, sender: newStep.senderName,
newStep.senderName || SENDER_NAME workflowStepId: newStep.id,
); });
} else if (newStep.action === WorkflowActions.SMS_NUMBER) { } else if (newStep.action === WorkflowActions.SMS_NUMBER) {
await scheduleSMSReminder( await scheduleSMSReminder({
bookingInfo, evt: bookingInfo,
newStep.sendTo || "", reminderPhone: newStep.sendTo || "",
trigger, triggerEvent: trigger,
newStep.action, action: newStep.action,
{ timeSpan: {
time, time,
timeUnit, timeUnit,
}, },
newStep.reminderBody || "", message: newStep.reminderBody || "",
newStep.id || 0, workflowStepId: newStep.id || 0,
newStep.template, template: newStep.template,
newStep.sender || SENDER_ID, sender: newStep.sender,
user.id, userId: user.id,
userWorkflow.teamId teamId: userWorkflow.teamId,
); });
} else if (newStep.action === WorkflowActions.WHATSAPP_NUMBER) { } else if (newStep.action === WorkflowActions.WHATSAPP_NUMBER) {
await scheduleWhatsappReminder( await scheduleWhatsappReminder({
bookingInfo, evt: bookingInfo,
newStep.sendTo || "", reminderPhone: newStep.sendTo || "",
trigger, triggerEvent: trigger,
newStep.action, action: newStep.action,
{ timeSpan: {
time, time,
timeUnit, timeUnit,
}, },
newStep.reminderBody || "", message: newStep.reminderBody || "",
newStep.id || 0, workflowStepId: newStep.id || 0,
newStep.template, template: newStep.template,
user.id, userId: user.id,
userWorkflow.teamId teamId: userWorkflow.teamId,
); });
} }
}); });
await Promise.all(promiseScheduleReminders); await Promise.all(promiseScheduleReminders);
@ -703,54 +703,54 @@ export const updateHandler = async ({ ctx, input }: UpdateOptions) => {
sendTo = step.sendTo || "";*/ sendTo = step.sendTo || "";*/
} }
await scheduleEmailReminder( await scheduleEmailReminder({
bookingInfo, evt: bookingInfo,
trigger, triggerEvent: trigger,
step.action, action: step.action,
{ timeSpan: {
time, time,
timeUnit, timeUnit,
}, },
sendTo, sendTo,
step.emailSubject || "", emailSubject: step.emailSubject || "",
step.reminderBody || "", emailBody: step.reminderBody || "",
createdStep.id, template: step.template,
step.template, sender: step.senderName,
step.senderName || SENDER_NAME workflowStepId: createdStep.id,
); });
} else if (step.action === WorkflowActions.SMS_NUMBER && step.sendTo) { } else if (step.action === WorkflowActions.SMS_NUMBER && step.sendTo) {
await scheduleSMSReminder( await scheduleSMSReminder({
bookingInfo, evt: bookingInfo,
step.sendTo, reminderPhone: step.sendTo,
trigger, triggerEvent: trigger,
step.action, action: step.action,
{ timeSpan: {
time, time,
timeUnit, timeUnit,
}, },
step.reminderBody || "", message: step.reminderBody || "",
createdStep.id, workflowStepId: createdStep.id,
step.template, template: step.template,
step.sender || SENDER_ID, sender: step.sender,
user.id, userId: user.id,
userWorkflow.teamId teamId: userWorkflow.teamId,
); });
} else if (step.action === WorkflowActions.WHATSAPP_NUMBER && step.sendTo) { } else if (step.action === WorkflowActions.WHATSAPP_NUMBER && step.sendTo) {
await scheduleWhatsappReminder( await scheduleWhatsappReminder({
bookingInfo, evt: bookingInfo,
step.sendTo, reminderPhone: step.sendTo,
trigger, triggerEvent: trigger,
step.action, action: step.action,
{ timeSpan: {
time, time,
timeUnit, timeUnit,
}, },
step.reminderBody || "", message: step.reminderBody || "",
createdStep.id, workflowStepId: createdStep.id,
step.template, template: step.template,
user.id, userId: user.id,
userWorkflow.teamId teamId: userWorkflow.teamId,
); });
} }
} }
} }