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:
parent
6b26dbc6da
commit
3164cd4ae7
|
@ -2,6 +2,7 @@ import type { Prisma, Workflow, WorkflowsOnEventTypes, WorkflowStep } from "@pri
|
|||
|
||||
import type { EventManagerUser } 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 { scheduleWorkflowReminders } from "@calcom/features/ee/workflows/lib/reminders/reminderScheduler";
|
||||
import getWebhooks from "@calcom/features/webhooks/lib/getWebhooks";
|
||||
|
@ -256,27 +257,27 @@ export async function handleConfirmation(args: {
|
|||
//Workflows - set reminders for confirmed events
|
||||
try {
|
||||
for (let index = 0; index < updatedBookings.length; index++) {
|
||||
if (updatedBookings[index].eventType?.workflows) {
|
||||
const evtOfBooking = evt;
|
||||
evtOfBooking.startTime = updatedBookings[index].startTime.toISOString();
|
||||
evtOfBooking.endTime = updatedBookings[index].endTime.toISOString();
|
||||
evtOfBooking.uid = updatedBookings[index].uid;
|
||||
const eventTypeSlug = updatedBookings[index].eventType?.slug || "";
|
||||
|
||||
const isFirstBooking = index === 0;
|
||||
|
||||
await scheduleWorkflowReminders({
|
||||
workflows: updatedBookings[index]?.eventType?.workflows || [],
|
||||
smsReminderNumber: updatedBookings[index].smsReminderNumber,
|
||||
calendarEvent: {
|
||||
...evtOfBooking,
|
||||
...{ metadata: { videoCallUrl }, eventType: { slug: eventTypeSlug } },
|
||||
},
|
||||
isFirstRecurringEvent: isFirstBooking,
|
||||
hideBranding: !!updatedBookings[index].eventType?.owner?.hideBranding,
|
||||
eventTypeRequiresConfirmation: true,
|
||||
});
|
||||
}
|
||||
const eventTypeSlug = updatedBookings[index].eventType?.slug || "";
|
||||
const evtOfBooking = { ...evt, metadata: { videoCallUrl }, eventType: { slug: eventTypeSlug } };
|
||||
evtOfBooking.startTime = updatedBookings[index].startTime.toISOString();
|
||||
evtOfBooking.endTime = updatedBookings[index].endTime.toISOString();
|
||||
evtOfBooking.uid = updatedBookings[index].uid;
|
||||
const isFirstBooking = index === 0;
|
||||
await scheduleMandatoryReminder(
|
||||
evtOfBooking,
|
||||
updatedBookings[index]?.eventType?.workflows || [],
|
||||
false,
|
||||
!!updatedBookings[index].eventType?.owner?.hideBranding,
|
||||
evt.attendeeSeatId
|
||||
);
|
||||
await scheduleWorkflowReminders({
|
||||
workflows: updatedBookings[index]?.eventType?.workflows || [],
|
||||
smsReminderNumber: updatedBookings[index].smsReminderNumber,
|
||||
calendarEvent: evtOfBooking,
|
||||
isFirstRecurringEvent: isFirstBooking,
|
||||
hideBranding: !!updatedBookings[index].eventType?.owner?.hideBranding,
|
||||
eventTypeRequiresConfirmation: true,
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
// Silently fail
|
||||
|
|
|
@ -25,6 +25,7 @@ import { getEventName } from "@calcom/core/event";
|
|||
import { getUserAvailability } from "@calcom/core/getUserAvailability";
|
||||
import { deleteMeeting } from "@calcom/core/videoClient";
|
||||
import dayjs from "@calcom/dayjs";
|
||||
import { scheduleMandatoryReminder } from "@calcom/ee/workflows/lib/reminders/scheduleMandatoryReminder";
|
||||
import {
|
||||
sendAttendeeRequestEmail,
|
||||
sendOrganizerRequestEmail,
|
||||
|
@ -2718,15 +2719,21 @@ async function handler(
|
|||
}
|
||||
|
||||
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 {
|
||||
await scheduleWorkflowReminders({
|
||||
workflows: eventType.workflows,
|
||||
smsReminderNumber: smsReminderNumber || null,
|
||||
calendarEvent: {
|
||||
...evt,
|
||||
...{ metadata: metadataFromEvent, eventType: { slug: eventType.slug } },
|
||||
},
|
||||
calendarEvent: evtWithMetadata,
|
||||
isNotConfirmed: !isConfirmedByDefault,
|
||||
isRescheduleEvent: !!rescheduleUid,
|
||||
isFirstRecurringEvent: true,
|
||||
|
|
|
@ -6,6 +6,7 @@ import { v4 as uuidv4 } from "uuid";
|
|||
|
||||
import dayjs from "@calcom/dayjs";
|
||||
import { getCalEventResponses } from "@calcom/features/bookings/lib/getCalEventResponses";
|
||||
import { SENDER_NAME } from "@calcom/lib/constants";
|
||||
import logger from "@calcom/lib/logger";
|
||||
import { defaultHandler } from "@calcom/lib/server";
|
||||
import { getTimeFormatStringFromUserTimeFormat } from "@calcom/lib/timeFormat";
|
||||
|
@ -121,100 +122,185 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
|
|||
}
|
||||
|
||||
for (const reminder of unscheduledReminders) {
|
||||
if (!reminder.workflowStep || !reminder.booking) {
|
||||
if (!reminder.booking) {
|
||||
continue;
|
||||
}
|
||||
try {
|
||||
let sendTo;
|
||||
if (!reminder.isMandatoryReminder && reminder.workflowStep) {
|
||||
try {
|
||||
let sendTo;
|
||||
|
||||
switch (reminder.workflowStep.action) {
|
||||
case WorkflowActions.EMAIL_HOST:
|
||||
sendTo = reminder.booking.user?.email;
|
||||
break;
|
||||
case WorkflowActions.EMAIL_ATTENDEE:
|
||||
sendTo = reminder.booking.attendees[0].email;
|
||||
break;
|
||||
case WorkflowActions.EMAIL_ADDRESS:
|
||||
sendTo = reminder.workflowStep.sendTo;
|
||||
}
|
||||
switch (reminder.workflowStep.action) {
|
||||
case WorkflowActions.EMAIL_HOST:
|
||||
sendTo = reminder.booking.user?.email;
|
||||
break;
|
||||
case WorkflowActions.EMAIL_ATTENDEE:
|
||||
sendTo = reminder.booking.attendees[0].email;
|
||||
break;
|
||||
case WorkflowActions.EMAIL_ADDRESS:
|
||||
sendTo = reminder.workflowStep.sendTo;
|
||||
}
|
||||
|
||||
const name =
|
||||
reminder.workflowStep.action === WorkflowActions.EMAIL_ATTENDEE
|
||||
? reminder.booking.attendees[0].name
|
||||
: reminder.booking.user?.name;
|
||||
const name =
|
||||
reminder.workflowStep.action === WorkflowActions.EMAIL_ATTENDEE
|
||||
? reminder.booking.attendees[0].name
|
||||
: reminder.booking.user?.name;
|
||||
|
||||
const attendeeName =
|
||||
reminder.workflowStep.action === WorkflowActions.EMAIL_ATTENDEE
|
||||
? reminder.booking.user?.name
|
||||
: reminder.booking.attendees[0].name;
|
||||
const attendeeName =
|
||||
reminder.workflowStep.action === WorkflowActions.EMAIL_ATTENDEE
|
||||
? reminder.booking.user?.name
|
||||
: reminder.booking.attendees[0].name;
|
||||
|
||||
const timeZone =
|
||||
reminder.workflowStep.action === WorkflowActions.EMAIL_ATTENDEE
|
||||
? reminder.booking.attendees[0].timeZone
|
||||
: reminder.booking.user?.timeZone;
|
||||
const timeZone =
|
||||
reminder.workflowStep.action === WorkflowActions.EMAIL_ATTENDEE
|
||||
? reminder.booking.attendees[0].timeZone
|
||||
: reminder.booking.user?.timeZone;
|
||||
|
||||
const locale =
|
||||
reminder.workflowStep.action === WorkflowActions.EMAIL_ATTENDEE ||
|
||||
reminder.workflowStep.action === WorkflowActions.SMS_ATTENDEE
|
||||
? reminder.booking.attendees[0].locale
|
||||
: reminder.booking.user?.locale;
|
||||
const locale =
|
||||
reminder.workflowStep.action === WorkflowActions.EMAIL_ATTENDEE ||
|
||||
reminder.workflowStep.action === WorkflowActions.SMS_ATTENDEE
|
||||
? reminder.booking.attendees[0].locale
|
||||
: reminder.booking.user?.locale;
|
||||
|
||||
let emailContent = {
|
||||
emailSubject: reminder.workflowStep.emailSubject || "",
|
||||
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}`,
|
||||
let emailContent = {
|
||||
emailSubject: reminder.workflowStep.emailSubject || "",
|
||||
emailBody: `<body style="white-space: pre-wrap;">${
|
||||
reminder.workflowStep.reminderBody || ""
|
||||
}</body>`,
|
||||
};
|
||||
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 =
|
||||
customTemplate(
|
||||
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)
|
||||
).text.length === 0;
|
||||
} else if (reminder.workflowStep.template === WorkflowTemplates.REMINDER) {
|
||||
getTimeFormatStringFromUserTimeFormat(reminder.booking.user?.timeFormat),
|
||||
!!reminder.booking.user?.hideBranding
|
||||
).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(
|
||||
false,
|
||||
reminder.workflowStep.action,
|
||||
WorkflowActions.EMAIL_ATTENDEE,
|
||||
getTimeFormatStringFromUserTimeFormat(reminder.booking.user?.timeFormat),
|
||||
reminder.booking.startTime.toISOString() || "",
|
||||
reminder.booking.endTime.toISOString() || "",
|
||||
|
@ -224,23 +310,20 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
|
|||
name || "",
|
||||
!!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 batchIdResponse = await client.request({
|
||||
url: "/v3/mail/batch",
|
||||
method: "POST",
|
||||
});
|
||||
const batchId = batchIdResponse[1].batch_id;
|
||||
|
||||
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 || "Cal.com",
|
||||
name: reminder.workflowStep?.sender || SENDER_NAME,
|
||||
},
|
||||
subject: emailContent.emailSubject,
|
||||
html: emailContent.emailBody,
|
||||
|
@ -252,33 +335,23 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
|
|||
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,
|
||||
attachments: undefined,
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
await prisma.workflowReminder.update({
|
||||
where: {
|
||||
id: reminder.id,
|
||||
},
|
||||
data: {
|
||||
scheduled: true,
|
||||
referenceId: batchId,
|
||||
},
|
||||
});
|
||||
await prisma.workflowReminder.update({
|
||||
where: {
|
||||
id: reminder.id,
|
||||
},
|
||||
data: {
|
||||
scheduled: true,
|
||||
referenceId: batchId,
|
||||
},
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error(`Error scheduling Email with error ${error}`);
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error(`Error scheduling Email with error ${error}`);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -26,7 +26,10 @@ type PartialBooking =
|
|||
> & { eventType: Partial<EventType> | null } & { user: Partial<User> | null })
|
||||
| null;
|
||||
|
||||
export type PartialWorkflowReminder = Pick<WorkflowReminder, "id" | "scheduledDate"> & {
|
||||
export type PartialWorkflowReminder = Pick<
|
||||
WorkflowReminder,
|
||||
"id" | "isMandatoryReminder" | "scheduledDate"
|
||||
> & {
|
||||
booking: PartialBooking | null;
|
||||
} & { workflowStep: PartialWorkflowStep };
|
||||
|
||||
|
@ -113,6 +116,7 @@ export async function getAllUnscheduledReminders(): Promise<PartialWorkflowRemin
|
|||
const select: Prisma.WorkflowReminderSelect = {
|
||||
id: true,
|
||||
scheduledDate: true,
|
||||
isMandatoryReminder: true,
|
||||
workflowStep: {
|
||||
select: {
|
||||
action: true,
|
||||
|
|
|
@ -9,6 +9,7 @@ import { v4 as uuidv4 } from "uuid";
|
|||
|
||||
import dayjs from "@calcom/dayjs";
|
||||
import { preprocessNameFieldDataWithVariant } from "@calcom/features/form-builder/utils";
|
||||
import { SENDER_NAME } from "@calcom/lib/constants";
|
||||
import logger from "@calcom/lib/logger";
|
||||
import prisma from "@calcom/prisma";
|
||||
import type { TimeUnit } from "@calcom/prisma/enums";
|
||||
|
@ -94,24 +95,46 @@ type ScheduleEmailReminderAction = Extract<
|
|||
"EMAIL_HOST" | "EMAIL_ATTENDEE" | "EMAIL_ADDRESS"
|
||||
>;
|
||||
|
||||
export const scheduleEmailReminder = async (
|
||||
evt: BookingInfo,
|
||||
triggerEvent: WorkflowTriggerEvents,
|
||||
action: ScheduleEmailReminderAction,
|
||||
export interface ScheduleReminderArgs {
|
||||
evt: BookingInfo;
|
||||
triggerEvent: WorkflowTriggerEvents;
|
||||
timeSpan: {
|
||||
time: number | null;
|
||||
timeUnit: TimeUnit | null;
|
||||
},
|
||||
sendTo: MailData["to"],
|
||||
emailSubject: string,
|
||||
emailBody: string,
|
||||
workflowStepId: number,
|
||||
template: WorkflowTemplates,
|
||||
sender: string,
|
||||
hideBranding?: boolean,
|
||||
seatReferenceUid?: string,
|
||||
includeCalendarEvent?: boolean
|
||||
) => {
|
||||
};
|
||||
template: WorkflowTemplates;
|
||||
sender?: string | null;
|
||||
workflowStepId?: number;
|
||||
seatReferenceUid?: string;
|
||||
}
|
||||
|
||||
interface scheduleEmailReminderArgs extends ScheduleReminderArgs {
|
||||
sendTo: MailData["to"];
|
||||
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;
|
||||
const { startTime, endTime } = evt;
|
||||
const uid = evt.uid as string;
|
||||
|
@ -251,7 +274,7 @@ export const scheduleEmailReminder = async (
|
|||
to: data.to,
|
||||
from: {
|
||||
email: senderEmail,
|
||||
name: sender,
|
||||
name: sender || SENDER_NAME,
|
||||
},
|
||||
subject: emailContent.emailSubject,
|
||||
html: emailContent.emailBody,
|
||||
|
@ -289,7 +312,7 @@ export const scheduleEmailReminder = async (
|
|||
// TODO: Maybe don't await for this?
|
||||
await Promise.all(promises);
|
||||
} catch (error) {
|
||||
console.log("Error sending Email");
|
||||
log.error("Error sending Email");
|
||||
}
|
||||
} else if (
|
||||
(triggerEvent === WorkflowTriggerEvents.BEFORE_EVENT ||
|
||||
|
@ -311,32 +334,59 @@ export const scheduleEmailReminder = async (
|
|||
},
|
||||
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({
|
||||
data: {
|
||||
bookingUid: uid,
|
||||
workflowStepId: workflowStepId,
|
||||
method: WorkflowMethods.EMAIL,
|
||||
scheduledDate: scheduledDate.toDate(),
|
||||
scheduled: true,
|
||||
referenceId: batchId,
|
||||
scheduled: false,
|
||||
seatReferenceId: seatReferenceUid,
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
console.log(`Error scheduling email with error ${error}`);
|
||||
} else {
|
||||
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) {
|
||||
console.log(`Error canceling reminder with error ${error}`);
|
||||
log.error(`Error canceling reminder with error ${error}`);
|
||||
}
|
||||
};
|
||||
|
|
|
@ -1,14 +1,16 @@
|
|||
import type { Workflow, WorkflowsOnEventTypes, WorkflowStep } from "@prisma/client";
|
||||
|
||||
import {
|
||||
isSMSAction,
|
||||
isTextMessageToAttendeeAction,
|
||||
isWhatsappAction,
|
||||
} 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 type { CalendarEvent } from "@calcom/types/Calendar";
|
||||
|
||||
import { deleteScheduledEmailReminder, scheduleEmailReminder } from "./emailReminderManager";
|
||||
import type { ScheduleTextReminderAction } from "./smsReminderManager";
|
||||
import { deleteScheduledSMSReminder, scheduleSMSReminder } from "./smsReminderManager";
|
||||
import { deleteScheduledWhatsappReminder, scheduleWhatsappReminder } from "./whatsappReminderManager";
|
||||
|
||||
|
@ -51,26 +53,26 @@ const processWorkflowStep = async (
|
|||
) => {
|
||||
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;
|
||||
await scheduleSMSReminder(
|
||||
await scheduleSMSReminder({
|
||||
evt,
|
||||
sendTo,
|
||||
workflow.trigger,
|
||||
step.action,
|
||||
{
|
||||
reminderPhone: sendTo,
|
||||
triggerEvent: workflow.trigger,
|
||||
action: step.action as ScheduleTextReminderAction,
|
||||
timeSpan: {
|
||||
time: workflow.time,
|
||||
timeUnit: workflow.timeUnit,
|
||||
},
|
||||
step.reminderBody || "",
|
||||
step.id,
|
||||
step.template,
|
||||
step.sender || SENDER_ID,
|
||||
workflow.userId,
|
||||
workflow.teamId,
|
||||
step.numberVerificationPending,
|
||||
seatReferenceUid
|
||||
);
|
||||
message: step.reminderBody || "",
|
||||
workflowStepId: step.id,
|
||||
template: step.template,
|
||||
sender: step.sender,
|
||||
userId: workflow.userId,
|
||||
teamId: workflow.teamId,
|
||||
isVerificationPending: step.numberVerificationPending,
|
||||
seatReferenceUid,
|
||||
});
|
||||
} else if (step.action === WorkflowActions.EMAIL_ATTENDEE || step.action === WorkflowActions.EMAIL_HOST) {
|
||||
let sendTo: string[] = [];
|
||||
|
||||
|
@ -88,43 +90,43 @@ const processWorkflowStep = async (
|
|||
break;
|
||||
}
|
||||
|
||||
await scheduleEmailReminder(
|
||||
await scheduleEmailReminder({
|
||||
evt,
|
||||
workflow.trigger,
|
||||
step.action,
|
||||
{
|
||||
triggerEvent: workflow.trigger,
|
||||
action: step.action,
|
||||
timeSpan: {
|
||||
time: workflow.time,
|
||||
timeUnit: workflow.timeUnit,
|
||||
},
|
||||
sendTo,
|
||||
step.emailSubject || "",
|
||||
step.reminderBody || "",
|
||||
step.id,
|
||||
step.template,
|
||||
step.sender || SENDER_NAME,
|
||||
emailSubject: step.emailSubject || "",
|
||||
emailBody: step.reminderBody || "",
|
||||
template: step.template,
|
||||
sender: step.sender || SENDER_NAME,
|
||||
workflowStepId: step.id,
|
||||
hideBranding,
|
||||
seatReferenceUid,
|
||||
step.includeCalendarEvent
|
||||
);
|
||||
includeCalendarEvent: step.includeCalendarEvent,
|
||||
});
|
||||
} else if (isWhatsappAction(step.action)) {
|
||||
const sendTo = step.action === WorkflowActions.WHATSAPP_ATTENDEE ? smsReminderNumber : step.sendTo;
|
||||
await scheduleWhatsappReminder(
|
||||
await scheduleWhatsappReminder({
|
||||
evt,
|
||||
sendTo,
|
||||
workflow.trigger,
|
||||
step.action,
|
||||
{
|
||||
reminderPhone: sendTo,
|
||||
triggerEvent: workflow.trigger,
|
||||
action: step.action as ScheduleTextReminderAction,
|
||||
timeSpan: {
|
||||
time: workflow.time,
|
||||
timeUnit: workflow.timeUnit,
|
||||
},
|
||||
step.reminderBody || "",
|
||||
step.id,
|
||||
step.template,
|
||||
workflow.userId,
|
||||
workflow.teamId,
|
||||
step.numberVerificationPending,
|
||||
seatReferenceUid
|
||||
);
|
||||
message: step.reminderBody || "",
|
||||
workflowStepId: step.id,
|
||||
template: step.template,
|
||||
userId: workflow.userId,
|
||||
teamId: workflow.teamId,
|
||||
isVerificationPending: step.numberVerificationPending,
|
||||
seatReferenceUid,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -164,7 +166,6 @@ export const scheduleWorkflowReminders = async (args: ScheduleWorkflowRemindersA
|
|||
) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (const step of workflow.steps) {
|
||||
await processWorkflowStep(workflow, step, {
|
||||
calendarEvent: evt,
|
||||
|
|
|
@ -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 }));
|
||||
}
|
||||
}
|
|
@ -1,15 +1,16 @@
|
|||
import dayjs from "@calcom/dayjs";
|
||||
import { SENDER_ID } from "@calcom/lib/constants";
|
||||
import logger from "@calcom/lib/logger";
|
||||
import type { TimeFormat } from "@calcom/lib/timeFormat";
|
||||
import prisma from "@calcom/prisma";
|
||||
import type { Prisma } from "@calcom/prisma/client";
|
||||
import type { TimeUnit } from "@calcom/prisma/enums";
|
||||
import { WorkflowTemplates, WorkflowActions, WorkflowMethods } from "@calcom/prisma/enums";
|
||||
import { WorkflowTriggerEvents } from "@calcom/prisma/enums";
|
||||
import { bookingMetadataSchema } from "@calcom/prisma/zod-utils";
|
||||
import type { CalEventResponses, RecurringEvent } from "@calcom/types/Calendar";
|
||||
|
||||
import { getSenderId } from "../alphanumericSenderIdSupport";
|
||||
import type { ScheduleReminderArgs } from "./emailReminderManager";
|
||||
import * as twilio from "./smsProviders/twilioProvider";
|
||||
import type { VariablesType } from "./templates/customTemplate";
|
||||
import customTemplate from "./templates/customTemplate";
|
||||
|
@ -55,33 +56,42 @@ export type BookingInfo = {
|
|||
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 (
|
||||
evt: BookingInfo,
|
||||
reminderPhone: string | null,
|
||||
triggerEvent: WorkflowTriggerEvents,
|
||||
action: ScheduleSMSReminderAction,
|
||||
timeSpan: {
|
||||
time: number | null;
|
||||
timeUnit: TimeUnit | null;
|
||||
},
|
||||
message: string,
|
||||
workflowStepId: number,
|
||||
template: WorkflowTemplates,
|
||||
sender: string,
|
||||
userId?: number | null,
|
||||
teamId?: number | null,
|
||||
isVerificationPending = false,
|
||||
seatReferenceUid?: string
|
||||
) => {
|
||||
export const scheduleSMSReminder = async (args: ScheduleTextReminderArgs) => {
|
||||
const {
|
||||
evt,
|
||||
reminderPhone,
|
||||
triggerEvent,
|
||||
action,
|
||||
timeSpan,
|
||||
message = "",
|
||||
workflowStepId,
|
||||
template,
|
||||
sender,
|
||||
userId,
|
||||
teamId,
|
||||
isVerificationPending = false,
|
||||
seatReferenceUid,
|
||||
} = args;
|
||||
const { startTime, endTime } = evt;
|
||||
const uid = evt.uid as string;
|
||||
const currentDate = dayjs();
|
||||
const timeUnit: timeUnitLowerCase | undefined = timeSpan.timeUnit?.toLocaleLowerCase() as timeUnitLowerCase;
|
||||
let scheduledDate = null;
|
||||
|
||||
const senderID = getSenderId(reminderPhone, sender);
|
||||
const senderID = getSenderId(reminderPhone, sender || SENDER_ID);
|
||||
|
||||
//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)
|
||||
|
@ -126,7 +136,9 @@ export const scheduleSMSReminder = async (
|
|||
? attendeeToBeUsedInSMS.language?.locale
|
||||
: evt.organizer.language.locale;
|
||||
|
||||
if (message) {
|
||||
let smsMessage = message;
|
||||
|
||||
if (smsMessage) {
|
||||
const variables: VariablesType = {
|
||||
eventName: evt.title,
|
||||
organizerName: evt.organizer.name,
|
||||
|
@ -144,10 +156,10 @@ export const scheduleSMSReminder = async (
|
|||
cancelLink: `/booking/${evt.uid}?cancel=true`,
|
||||
rescheduleLink: `/${evt.organizer.username}/${evt.eventType.slug}?rescheduleUid=${evt.uid}`,
|
||||
};
|
||||
const customMessage = customTemplate(message, variables, locale, evt.organizer.timeFormat);
|
||||
message = customMessage.text;
|
||||
const customMessage = customTemplate(smsMessage, variables, locale, evt.organizer.timeFormat);
|
||||
smsMessage = customMessage.text;
|
||||
} else if (template === WorkflowTemplates.REMINDER) {
|
||||
message =
|
||||
smsMessage =
|
||||
smsReminderTemplate(
|
||||
false,
|
||||
action,
|
||||
|
@ -161,9 +173,9 @@ export const scheduleSMSReminder = async (
|
|||
}
|
||||
|
||||
// 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
|
||||
if (
|
||||
triggerEvent === WorkflowTriggerEvents.NEW_EVENT ||
|
||||
|
@ -171,9 +183,9 @@ export const scheduleSMSReminder = async (
|
|||
triggerEvent === WorkflowTriggerEvents.RESCHEDULE_EVENT
|
||||
) {
|
||||
try {
|
||||
await twilio.sendSMS(reminderPhone, message, senderID);
|
||||
await twilio.sendSMS(reminderPhone, smsMessage, senderID);
|
||||
} catch (error) {
|
||||
console.log(`Error sending SMS with error ${error}`);
|
||||
log.error(`Error sending SMS with error ${error}`);
|
||||
}
|
||||
} else if (
|
||||
(triggerEvent === WorkflowTriggerEvents.BEFORE_EVENT ||
|
||||
|
@ -188,7 +200,7 @@ export const scheduleSMSReminder = async (
|
|||
try {
|
||||
const scheduledSMS = await twilio.scheduleSMS(
|
||||
reminderPhone,
|
||||
message,
|
||||
smsMessage,
|
||||
scheduledDate.toDate(),
|
||||
senderID
|
||||
);
|
||||
|
@ -205,7 +217,7 @@ export const scheduleSMSReminder = async (
|
|||
},
|
||||
});
|
||||
} 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"))) {
|
||||
// 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) {
|
||||
console.log(`Error canceling reminder with error ${error}`);
|
||||
log.error(`Error canceling reminder with error ${error}`);
|
||||
}
|
||||
};
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
import type { TimeUnit } from "@prisma/client";
|
||||
|
||||
import dayjs from "@calcom/dayjs";
|
||||
import logger from "@calcom/lib/logger";
|
||||
import prisma from "@calcom/prisma";
|
||||
|
@ -11,7 +9,7 @@ import {
|
|||
} from "@calcom/prisma/enums";
|
||||
|
||||
import * as twilio from "./smsProviders/twilioProvider";
|
||||
import type { BookingInfo, timeUnitLowerCase } from "./smsReminderManager";
|
||||
import type { ScheduleTextReminderArgs, timeUnitLowerCase } from "./smsReminderManager";
|
||||
import { deleteScheduledSMSReminder } from "./smsReminderManager";
|
||||
import {
|
||||
whatsappEventCancelledTemplate,
|
||||
|
@ -22,23 +20,21 @@ import {
|
|||
|
||||
const log = logger.getSubLogger({ prefix: ["[whatsappReminderManager]"] });
|
||||
|
||||
export const scheduleWhatsappReminder = async (
|
||||
evt: BookingInfo,
|
||||
reminderPhone: string | null,
|
||||
triggerEvent: WorkflowTriggerEvents,
|
||||
action: WorkflowActions,
|
||||
timeSpan: {
|
||||
time: number | null;
|
||||
timeUnit: TimeUnit | null;
|
||||
},
|
||||
message: string,
|
||||
workflowStepId: number,
|
||||
template: WorkflowTemplates,
|
||||
userId?: number | null,
|
||||
teamId?: number | null,
|
||||
isVerificationPending = false,
|
||||
seatReferenceUid?: string
|
||||
) => {
|
||||
export const scheduleWhatsappReminder = async (args: ScheduleTextReminderArgs) => {
|
||||
const {
|
||||
evt,
|
||||
reminderPhone,
|
||||
triggerEvent,
|
||||
action,
|
||||
timeSpan,
|
||||
message = "",
|
||||
workflowStepId,
|
||||
template,
|
||||
userId,
|
||||
teamId,
|
||||
isVerificationPending = false,
|
||||
seatReferenceUid,
|
||||
} = args;
|
||||
const { startTime, endTime } = evt;
|
||||
const uid = evt.uid as string;
|
||||
const currentDate = dayjs();
|
||||
|
@ -72,9 +68,11 @@ export const scheduleWhatsappReminder = async (
|
|||
const timeZone =
|
||||
action === WorkflowActions.WHATSAPP_ATTENDEE ? evt.attendees[0].timeZone : evt.organizer.timeZone;
|
||||
|
||||
let textMessage = message;
|
||||
|
||||
switch (template) {
|
||||
case WorkflowTemplates.REMINDER:
|
||||
message =
|
||||
textMessage =
|
||||
whatsappReminderTemplate(
|
||||
false,
|
||||
action,
|
||||
|
@ -87,7 +85,7 @@ export const scheduleWhatsappReminder = async (
|
|||
) || message;
|
||||
break;
|
||||
case WorkflowTemplates.CANCELLED:
|
||||
message =
|
||||
textMessage =
|
||||
whatsappEventCancelledTemplate(
|
||||
false,
|
||||
action,
|
||||
|
@ -100,7 +98,7 @@ export const scheduleWhatsappReminder = async (
|
|||
) || message;
|
||||
break;
|
||||
case WorkflowTemplates.RESCHEDULED:
|
||||
message =
|
||||
textMessage =
|
||||
whatsappEventRescheduledTemplate(
|
||||
false,
|
||||
action,
|
||||
|
@ -113,7 +111,7 @@ export const scheduleWhatsappReminder = async (
|
|||
) || message;
|
||||
break;
|
||||
case WorkflowTemplates.COMPLETED:
|
||||
message =
|
||||
textMessage =
|
||||
whatsappEventCompletedTemplate(
|
||||
false,
|
||||
action,
|
||||
|
@ -126,7 +124,7 @@ export const scheduleWhatsappReminder = async (
|
|||
) || message;
|
||||
break;
|
||||
default:
|
||||
message =
|
||||
textMessage =
|
||||
whatsappReminderTemplate(
|
||||
false,
|
||||
action,
|
||||
|
@ -140,8 +138,8 @@ export const scheduleWhatsappReminder = async (
|
|||
}
|
||||
|
||||
// Allows debugging generated whatsapp content without waiting for twilio to send whatsapp messages
|
||||
log.debug(`Sending Whatsapp for trigger ${triggerEvent}`, message);
|
||||
if (message.length > 0 && reminderPhone && isNumberVerified) {
|
||||
log.debug(`Sending Whatsapp for trigger ${triggerEvent}`, textMessage);
|
||||
if (textMessage.length > 0 && reminderPhone && isNumberVerified) {
|
||||
//send WHATSAPP when event is booked/cancelled/rescheduled
|
||||
if (
|
||||
triggerEvent === WorkflowTriggerEvents.NEW_EVENT ||
|
||||
|
@ -149,7 +147,7 @@ export const scheduleWhatsappReminder = async (
|
|||
triggerEvent === WorkflowTriggerEvents.RESCHEDULE_EVENT
|
||||
) {
|
||||
try {
|
||||
await twilio.sendSMS(reminderPhone, message, "", true);
|
||||
await twilio.sendSMS(reminderPhone, textMessage, "", true);
|
||||
} catch (error) {
|
||||
console.log(`Error sending WHATSAPP with error ${error}`);
|
||||
}
|
||||
|
@ -166,7 +164,7 @@ export const scheduleWhatsappReminder = async (
|
|||
try {
|
||||
const scheduledWHATSAPP = await twilio.scheduleSMS(
|
||||
reminderPhone,
|
||||
message,
|
||||
textMessage,
|
||||
scheduledDate.toDate(),
|
||||
"",
|
||||
true
|
||||
|
|
|
@ -0,0 +1,2 @@
|
|||
-- AlterTable
|
||||
ALTER TABLE "WorkflowReminder" ADD COLUMN "isMandatoryReminder" BOOLEAN DEFAULT false;
|
|
@ -823,17 +823,18 @@ enum TimeUnit {
|
|||
}
|
||||
|
||||
model WorkflowReminder {
|
||||
id Int @id @default(autoincrement())
|
||||
bookingUid String?
|
||||
booking Booking? @relation(fields: [bookingUid], references: [uid])
|
||||
method WorkflowMethods
|
||||
scheduledDate DateTime
|
||||
referenceId String? @unique
|
||||
scheduled Boolean
|
||||
workflowStepId Int?
|
||||
workflowStep WorkflowStep? @relation(fields: [workflowStepId], references: [id])
|
||||
cancelled Boolean?
|
||||
seatReferenceId String?
|
||||
id Int @id @default(autoincrement())
|
||||
bookingUid String?
|
||||
booking Booking? @relation(fields: [bookingUid], references: [uid])
|
||||
method WorkflowMethods
|
||||
scheduledDate DateTime
|
||||
referenceId String? @unique
|
||||
scheduled Boolean
|
||||
workflowStepId Int?
|
||||
workflowStep WorkflowStep? @relation(fields: [workflowStepId], references: [id])
|
||||
cancelled Boolean?
|
||||
seatReferenceId String?
|
||||
isMandatoryReminder Boolean? @default(false)
|
||||
|
||||
@@index([bookingUid])
|
||||
@@index([workflowStepId])
|
||||
|
|
|
@ -10,7 +10,6 @@ import {
|
|||
deleteScheduledWhatsappReminder,
|
||||
scheduleWhatsappReminder,
|
||||
} from "@calcom/features/ee/workflows/lib/reminders/whatsappReminderManager";
|
||||
import { SENDER_ID, SENDER_NAME } from "@calcom/lib/constants";
|
||||
import { getTimeFormatStringFromUserTimeFormat } from "@calcom/lib/timeFormat";
|
||||
import { prisma } from "@calcom/prisma";
|
||||
import { BookingStatus } from "@calcom/prisma/client";
|
||||
|
@ -197,54 +196,54 @@ export const activateEventTypeHandler = async ({ ctx, input }: ActivateEventType
|
|||
break;
|
||||
}
|
||||
|
||||
await scheduleEmailReminder(
|
||||
bookingInfo,
|
||||
eventTypeWorkflow.trigger,
|
||||
step.action,
|
||||
{
|
||||
await scheduleEmailReminder({
|
||||
evt: bookingInfo,
|
||||
triggerEvent: eventTypeWorkflow.trigger,
|
||||
action: step.action,
|
||||
timeSpan: {
|
||||
time: eventTypeWorkflow.time,
|
||||
timeUnit: eventTypeWorkflow.timeUnit,
|
||||
},
|
||||
sendTo,
|
||||
step.emailSubject || "",
|
||||
step.reminderBody || "",
|
||||
step.id,
|
||||
step.template,
|
||||
step.sender || SENDER_NAME
|
||||
);
|
||||
emailSubject: step.emailSubject || "",
|
||||
emailBody: step.reminderBody || "",
|
||||
template: step.template,
|
||||
sender: step.sender,
|
||||
workflowStepId: step.id,
|
||||
});
|
||||
} else if (step.action === WorkflowActions.SMS_NUMBER && step.sendTo) {
|
||||
await scheduleSMSReminder(
|
||||
bookingInfo,
|
||||
step.sendTo,
|
||||
eventTypeWorkflow.trigger,
|
||||
step.action,
|
||||
{
|
||||
await scheduleSMSReminder({
|
||||
evt: bookingInfo,
|
||||
reminderPhone: step.sendTo,
|
||||
triggerEvent: eventTypeWorkflow.trigger,
|
||||
action: step.action,
|
||||
timeSpan: {
|
||||
time: eventTypeWorkflow.time,
|
||||
timeUnit: eventTypeWorkflow.timeUnit,
|
||||
},
|
||||
step.reminderBody || "",
|
||||
step.id,
|
||||
step.template,
|
||||
step.sender || SENDER_ID,
|
||||
booking.userId,
|
||||
eventTypeWorkflow.teamId
|
||||
);
|
||||
message: step.reminderBody || "",
|
||||
workflowStepId: step.id,
|
||||
template: step.template,
|
||||
sender: step.sender,
|
||||
userId: booking.userId,
|
||||
teamId: eventTypeWorkflow.teamId,
|
||||
});
|
||||
} else if (step.action === WorkflowActions.WHATSAPP_NUMBER && step.sendTo) {
|
||||
await scheduleWhatsappReminder(
|
||||
bookingInfo,
|
||||
step.sendTo,
|
||||
eventTypeWorkflow.trigger,
|
||||
step.action,
|
||||
{
|
||||
await scheduleWhatsappReminder({
|
||||
evt: bookingInfo,
|
||||
reminderPhone: step.sendTo,
|
||||
triggerEvent: eventTypeWorkflow.trigger,
|
||||
action: step.action,
|
||||
timeSpan: {
|
||||
time: eventTypeWorkflow.time,
|
||||
timeUnit: eventTypeWorkflow.timeUnit,
|
||||
},
|
||||
step.reminderBody || "",
|
||||
step.id,
|
||||
step.template,
|
||||
booking.userId,
|
||||
eventTypeWorkflow.teamId
|
||||
);
|
||||
message: step.reminderBody || "",
|
||||
workflowStepId: step.id,
|
||||
template: step.template,
|
||||
userId: booking.userId,
|
||||
teamId: eventTypeWorkflow.teamId,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,7 +17,7 @@ import {
|
|||
deleteScheduledWhatsappReminder,
|
||||
scheduleWhatsappReminder,
|
||||
} 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 { getTimeFormatStringFromUserTimeFormat } from "@calcom/lib/timeFormat";
|
||||
import type { PrismaClient } from "@calcom/prisma";
|
||||
|
@ -315,54 +315,54 @@ export const updateHandler = async ({ ctx, input }: UpdateOptions) => {
|
|||
sendTo = step.sendTo || "";*/
|
||||
}
|
||||
|
||||
await scheduleEmailReminder(
|
||||
bookingInfo,
|
||||
trigger,
|
||||
step.action,
|
||||
{
|
||||
await scheduleEmailReminder({
|
||||
evt: bookingInfo,
|
||||
triggerEvent: trigger,
|
||||
action: step.action,
|
||||
timeSpan: {
|
||||
time,
|
||||
timeUnit,
|
||||
},
|
||||
sendTo,
|
||||
step.emailSubject || "",
|
||||
step.reminderBody || "",
|
||||
step.id,
|
||||
step.template,
|
||||
step.senderName || SENDER_NAME
|
||||
);
|
||||
emailSubject: step.emailSubject || "",
|
||||
emailBody: step.reminderBody || "",
|
||||
template: step.template,
|
||||
sender: step.senderName,
|
||||
workflowStepId: step.id,
|
||||
});
|
||||
} else if (step.action === WorkflowActions.SMS_NUMBER) {
|
||||
await scheduleSMSReminder(
|
||||
bookingInfo,
|
||||
step.sendTo || "",
|
||||
trigger,
|
||||
step.action,
|
||||
{
|
||||
await scheduleSMSReminder({
|
||||
evt: bookingInfo,
|
||||
reminderPhone: step.sendTo || "",
|
||||
triggerEvent: trigger,
|
||||
action: step.action,
|
||||
timeSpan: {
|
||||
time,
|
||||
timeUnit,
|
||||
},
|
||||
step.reminderBody || "",
|
||||
step.id,
|
||||
step.template,
|
||||
step.sender || SENDER_ID,
|
||||
user.id,
|
||||
userWorkflow.teamId
|
||||
);
|
||||
message: step.reminderBody || "",
|
||||
workflowStepId: step.id,
|
||||
template: step.template,
|
||||
sender: step.sender,
|
||||
userId: user.id,
|
||||
teamId: userWorkflow.teamId,
|
||||
});
|
||||
} else if (step.action === WorkflowActions.WHATSAPP_NUMBER) {
|
||||
await scheduleWhatsappReminder(
|
||||
bookingInfo,
|
||||
step.sendTo || "",
|
||||
trigger,
|
||||
step.action,
|
||||
{
|
||||
await scheduleWhatsappReminder({
|
||||
evt: bookingInfo,
|
||||
reminderPhone: step.sendTo || "",
|
||||
triggerEvent: trigger,
|
||||
action: step.action,
|
||||
timeSpan: {
|
||||
time,
|
||||
timeUnit,
|
||||
},
|
||||
step.reminderBody || "",
|
||||
step.id || 0,
|
||||
step.template,
|
||||
user.id,
|
||||
userWorkflow.teamId
|
||||
);
|
||||
message: step.reminderBody || "",
|
||||
workflowStepId: step.id || 0,
|
||||
template: step.template,
|
||||
userId: user.id,
|
||||
teamId: userWorkflow.teamId,
|
||||
});
|
||||
}
|
||||
});
|
||||
await Promise.all(promiseScheduleReminders);
|
||||
|
@ -552,54 +552,54 @@ export const updateHandler = async ({ ctx, input }: UpdateOptions) => {
|
|||
sendTo = newStep.sendTo || "";*/
|
||||
}
|
||||
|
||||
await scheduleEmailReminder(
|
||||
bookingInfo,
|
||||
trigger,
|
||||
newStep.action,
|
||||
{
|
||||
await scheduleEmailReminder({
|
||||
evt: bookingInfo,
|
||||
triggerEvent: trigger,
|
||||
action: newStep.action,
|
||||
timeSpan: {
|
||||
time,
|
||||
timeUnit,
|
||||
},
|
||||
sendTo,
|
||||
newStep.emailSubject || "",
|
||||
newStep.reminderBody || "",
|
||||
newStep.id,
|
||||
newStep.template,
|
||||
newStep.senderName || SENDER_NAME
|
||||
);
|
||||
emailSubject: newStep.emailSubject || "",
|
||||
emailBody: newStep.reminderBody || "",
|
||||
template: newStep.template,
|
||||
sender: newStep.senderName,
|
||||
workflowStepId: newStep.id,
|
||||
});
|
||||
} else if (newStep.action === WorkflowActions.SMS_NUMBER) {
|
||||
await scheduleSMSReminder(
|
||||
bookingInfo,
|
||||
newStep.sendTo || "",
|
||||
trigger,
|
||||
newStep.action,
|
||||
{
|
||||
await scheduleSMSReminder({
|
||||
evt: bookingInfo,
|
||||
reminderPhone: newStep.sendTo || "",
|
||||
triggerEvent: trigger,
|
||||
action: newStep.action,
|
||||
timeSpan: {
|
||||
time,
|
||||
timeUnit,
|
||||
},
|
||||
newStep.reminderBody || "",
|
||||
newStep.id || 0,
|
||||
newStep.template,
|
||||
newStep.sender || SENDER_ID,
|
||||
user.id,
|
||||
userWorkflow.teamId
|
||||
);
|
||||
message: newStep.reminderBody || "",
|
||||
workflowStepId: newStep.id || 0,
|
||||
template: newStep.template,
|
||||
sender: newStep.sender,
|
||||
userId: user.id,
|
||||
teamId: userWorkflow.teamId,
|
||||
});
|
||||
} else if (newStep.action === WorkflowActions.WHATSAPP_NUMBER) {
|
||||
await scheduleWhatsappReminder(
|
||||
bookingInfo,
|
||||
newStep.sendTo || "",
|
||||
trigger,
|
||||
newStep.action,
|
||||
{
|
||||
await scheduleWhatsappReminder({
|
||||
evt: bookingInfo,
|
||||
reminderPhone: newStep.sendTo || "",
|
||||
triggerEvent: trigger,
|
||||
action: newStep.action,
|
||||
timeSpan: {
|
||||
time,
|
||||
timeUnit,
|
||||
},
|
||||
newStep.reminderBody || "",
|
||||
newStep.id || 0,
|
||||
newStep.template,
|
||||
user.id,
|
||||
userWorkflow.teamId
|
||||
);
|
||||
message: newStep.reminderBody || "",
|
||||
workflowStepId: newStep.id || 0,
|
||||
template: newStep.template,
|
||||
userId: user.id,
|
||||
teamId: userWorkflow.teamId,
|
||||
});
|
||||
}
|
||||
});
|
||||
await Promise.all(promiseScheduleReminders);
|
||||
|
@ -703,54 +703,54 @@ export const updateHandler = async ({ ctx, input }: UpdateOptions) => {
|
|||
sendTo = step.sendTo || "";*/
|
||||
}
|
||||
|
||||
await scheduleEmailReminder(
|
||||
bookingInfo,
|
||||
trigger,
|
||||
step.action,
|
||||
{
|
||||
await scheduleEmailReminder({
|
||||
evt: bookingInfo,
|
||||
triggerEvent: trigger,
|
||||
action: step.action,
|
||||
timeSpan: {
|
||||
time,
|
||||
timeUnit,
|
||||
},
|
||||
sendTo,
|
||||
step.emailSubject || "",
|
||||
step.reminderBody || "",
|
||||
createdStep.id,
|
||||
step.template,
|
||||
step.senderName || SENDER_NAME
|
||||
);
|
||||
emailSubject: step.emailSubject || "",
|
||||
emailBody: step.reminderBody || "",
|
||||
template: step.template,
|
||||
sender: step.senderName,
|
||||
workflowStepId: createdStep.id,
|
||||
});
|
||||
} else if (step.action === WorkflowActions.SMS_NUMBER && step.sendTo) {
|
||||
await scheduleSMSReminder(
|
||||
bookingInfo,
|
||||
step.sendTo,
|
||||
trigger,
|
||||
step.action,
|
||||
{
|
||||
await scheduleSMSReminder({
|
||||
evt: bookingInfo,
|
||||
reminderPhone: step.sendTo,
|
||||
triggerEvent: trigger,
|
||||
action: step.action,
|
||||
timeSpan: {
|
||||
time,
|
||||
timeUnit,
|
||||
},
|
||||
step.reminderBody || "",
|
||||
createdStep.id,
|
||||
step.template,
|
||||
step.sender || SENDER_ID,
|
||||
user.id,
|
||||
userWorkflow.teamId
|
||||
);
|
||||
message: step.reminderBody || "",
|
||||
workflowStepId: createdStep.id,
|
||||
template: step.template,
|
||||
sender: step.sender,
|
||||
userId: user.id,
|
||||
teamId: userWorkflow.teamId,
|
||||
});
|
||||
} else if (step.action === WorkflowActions.WHATSAPP_NUMBER && step.sendTo) {
|
||||
await scheduleWhatsappReminder(
|
||||
bookingInfo,
|
||||
step.sendTo,
|
||||
trigger,
|
||||
step.action,
|
||||
{
|
||||
await scheduleWhatsappReminder({
|
||||
evt: bookingInfo,
|
||||
reminderPhone: step.sendTo,
|
||||
triggerEvent: trigger,
|
||||
action: step.action,
|
||||
timeSpan: {
|
||||
time,
|
||||
timeUnit,
|
||||
},
|
||||
step.reminderBody || "",
|
||||
createdStep.id,
|
||||
step.template,
|
||||
user.id,
|
||||
userWorkflow.teamId
|
||||
);
|
||||
message: step.reminderBody || "",
|
||||
workflowStepId: createdStep.id,
|
||||
template: step.template,
|
||||
userId: user.id,
|
||||
teamId: userWorkflow.teamId,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue
Block a user