Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -502,8 +502,6 @@ export async function handleWorkflowsUpdate({
includeCalendarEvent: workflowStep.includeCalendarEvent,
workflowStepId: workflowStep.id,
verifiedAt: workflowStep.verifiedAt,
userId: workflow.userId,
teamId: workflow.teamId,
});
}

Expand Down
148 changes: 17 additions & 131 deletions packages/features/ee/workflows/lib/reminders/emailReminderManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import { v4 as uuidv4 } from "uuid";

import dayjs from "@calcom/dayjs";
import generateIcsString from "@calcom/emails/lib/generateIcsString";
import { FeaturesRepository } from "@calcom/features/flags/features.repository";
import { preprocessNameFieldDataWithVariant } from "@calcom/features/form-builder/utils";
import tasker from "@calcom/features/tasker";
import { SENDER_NAME, WEBSITE_URL } from "@calcom/lib/constants";
Expand All @@ -19,10 +18,8 @@ import {
} from "@calcom/prisma/enums";
import { bookingMetadataSchema } from "@calcom/prisma/zod-utils";

import { IMMEDIATE_WORKFLOW_TRIGGER_EVENTS } from "../constants";
import { getWorkflowRecipientEmail } from "../getWorkflowReminders";
import { sendOrScheduleWorkflowEmails } from "./providers/emailProvider";
import { getBatchId, sendSendgridMail } from "./providers/sendgridProvider";
import type { AttendeeInBookingInfo, BookingInfo, timeUnitLowerCase } from "./smsReminderManager";
import type { VariablesType } from "./templates/customTemplate";
import customTemplate from "./templates/customTemplate";
Expand Down Expand Up @@ -53,13 +50,10 @@ interface scheduleEmailReminderArgs extends ScheduleReminderArgs {
evt: BookingInfo;
sendTo: string[];
action: ScheduleEmailReminderAction;
userId?: number | null;
teamId?: number | null;
emailSubject?: string;
emailBody?: string;
hideBranding?: boolean;
includeCalendarEvent?: boolean;
isMandatoryReminder?: boolean;
verifiedAt: Date | null;
}

Expand All @@ -77,11 +71,8 @@ export const scheduleEmailReminder = async (args: scheduleEmailReminderArgs) =>
emailBody = "",
hideBranding,
includeCalendarEvent,
isMandatoryReminder,
action,
verifiedAt,
userId,
teamId,
} = args;

if (!verifiedAt) {
Expand All @@ -91,7 +82,6 @@ export const scheduleEmailReminder = async (args: scheduleEmailReminderArgs) =>

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;
Expand Down Expand Up @@ -286,130 +276,26 @@ export const scheduleEmailReminder = async (args: scheduleEmailReminderArgs) =>

const mailData = await prepareEmailData();

const isSendgridEnabled = !!(process.env.SENDGRID_API_KEY && process.env.SENDGRID_EMAIL);

const featureRepo = new FeaturesRepository(prisma);

const isWorkflowSmtpEmailsEnabled = teamId
? await featureRepo.checkIfTeamHasFeature(teamId, "workflow-smtp-emails")
: userId
? await featureRepo.checkIfUserHasFeature(userId, "workflow-smtp-emails")
: false;

if (isWorkflowSmtpEmailsEnabled || !isSendgridEnabled) {
let reminderUid;
if (scheduledDate) {
const reminder = await prisma.workflowReminder.create({
data: {
bookingUid: uid,
workflowStepId,
method: WorkflowMethods.EMAIL,
scheduledDate: scheduledDate.toDate(),
scheduled: true,
},
});
reminderUid = reminder.uuid;
}

await sendOrScheduleWorkflowEmails({
...mailData,
to: sendTo,
sendAt: scheduledDate?.toDate(),
referenceUid: reminderUid ?? undefined,
let reminderUid = undefined;
if (scheduledDate) {
const reminder = await prisma.workflowReminder.create({
data: {
bookingUid: uid,
workflowStepId,
method: WorkflowMethods.EMAIL,
scheduledDate: scheduledDate.toDate(),
scheduled: true,
},
});

return;
reminderUid = reminder.uuid;
}

/**
* @deprecated only needed for SendGrid, use SMTP with tasker instead
*/
if (IMMEDIATE_WORKFLOW_TRIGGER_EVENTS.includes(triggerEvent)) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

try {
const promises = sendTo.map((email) => sendSendgridMail({ ...mailData, to: email }));
// TODO: Maybe don't await for this?
await Promise.all(promises);
} catch (error) {
log.error("Error sending Email");
}
} else if (
(triggerEvent === WorkflowTriggerEvents.BEFORE_EVENT ||
triggerEvent === WorkflowTriggerEvents.AFTER_EVENT) &&
scheduledDate
) {
// Sendgrid to schedule emails
// Can only schedule at least 60 minutes and at most 72 hours in advance
// To limit the amount of canceled sends we schedule at most 2 hours in advance
if (
currentDate.isBefore(scheduledDate.subtract(1, "hour")) &&
!scheduledDate.isAfter(currentDate.add(2, "hour"))
) {
try {
const sendgridBatchId = await getBatchId();

// If sendEmail failed then workflowReminer will not be created, failing E2E tests
await sendSendgridMail({
...mailData,
to: sendTo,
sendAt: scheduledDate.unix(),
batchId: sendgridBatchId,
});

if (!isMandatoryReminder) {
await prisma.workflowReminder.create({
data: {
bookingUid: uid,
workflowStepId: workflowStepId,
method: WorkflowMethods.EMAIL,
scheduledDate: scheduledDate.toDate(),
scheduled: true,
referenceId: sendgridBatchId,
seatReferenceId: seatReferenceUid,
},
});
} else {
await prisma.workflowReminder.create({
data: {
bookingUid: uid,
method: WorkflowMethods.EMAIL,
scheduledDate: scheduledDate.toDate(),
scheduled: true,
referenceId: sendgridBatchId,
seatReferenceId: seatReferenceUid,
isMandatoryReminder: true,
},
});
}
} catch (error) {
log.error(`Error scheduling email with error ${error}`);
}
} else if (scheduledDate.isAfter(currentDate.add(2, "hour"))) {
// Write to DB and send to CRON if scheduled reminder date is past 2 hours
if (!isMandatoryReminder) {
await prisma.workflowReminder.create({
data: {
bookingUid: uid,
workflowStepId: workflowStepId,
method: WorkflowMethods.EMAIL,
scheduledDate: scheduledDate.toDate(),
scheduled: false,
seatReferenceId: seatReferenceUid,
},
});
} else {
await prisma.workflowReminder.create({
data: {
bookingUid: uid,
method: WorkflowMethods.EMAIL,
scheduledDate: scheduledDate.toDate(),
scheduled: false,
seatReferenceId: seatReferenceUid,
isMandatoryReminder: true,
},
});
}
}
}
await sendOrScheduleWorkflowEmails({
...mailData,
to: sendTo,
sendAt: scheduledDate?.toDate(),
referenceUid: reminderUid ?? undefined,
});
};

export const deleteScheduledEmailReminder = async (reminderId: number) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -156,8 +156,6 @@ const processWorkflowStep = async (
seatReferenceUid,
includeCalendarEvent: step.includeCalendarEvent,
verifiedAt: step.verifiedAt,
userId: workflow.userId,
teamId: workflow.teamId,
});
} else if (isWhatsappAction(step.action)) {
const sendTo = step.action === WorkflowActions.WHATSAPP_ATTENDEE ? smsReminderNumber : step.sendTo;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,10 +63,8 @@ async function _scheduleMandatoryReminder({
hideBranding,
seatReferenceUid,
includeCalendarEvent: false,
isMandatoryReminder: true,
// Template is fixed so we don't have to verify
verifiedAt: new Date(),
userId: evt.organizer.id,
});
} catch (error) {
log.error("Error while scheduling mandatory reminders", JSON.stringify({ error }));
Expand Down
69 changes: 2 additions & 67 deletions packages/features/ee/workflows/lib/test/workflows.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,6 @@ import { FeaturesRepository } from "../../../../flags/features.repository";
import { deleteWorkfowRemindersOfRemovedMember } from "../../../teams/lib/deleteWorkflowRemindersOfRemovedMember";
import { scheduleEmailReminder } from "../reminders/emailReminderManager";
import * as emailProvider from "../reminders/providers/emailProvider";
import * as sendgridProvider from "../reminders/providers/sendgridProvider";

const workflowSelect = {
id: true,
Expand Down Expand Up @@ -709,7 +708,6 @@ describe("scheduleBookingReminders", () => {
},
});

const allVerified = await prismock.verifiedNumber.findMany();
await scheduleBookingReminders(
bookings,
workflow.steps,
Expand Down Expand Up @@ -1061,7 +1059,6 @@ describe("deleteWorkfowRemindersOfRemovedMember", () => {
});

describe("Workflow SMTP Emails Feature Flag", () => {
vi.spyOn(sendgridProvider, "sendSendgridMail");
vi.spyOn(emailProvider, "sendOrScheduleWorkflowEmails");

const mockEvt = {
Expand Down Expand Up @@ -1097,72 +1094,10 @@ describe("Workflow SMTP Emails Feature Flag", () => {

beforeEach(() => {
vi.clearAllMocks();
// Mock SendGrid environment variables
process.env.SENDGRID_API_KEY = "test-key";
process.env.SENDGRID_EMAIL = "test@example.com";
});

test("should use SMTP when team has workflow-smtp-emails feature", async () => {
vi.spyOn(FeaturesRepository.prototype, "checkIfTeamHasFeature").mockResolvedValue(true);

await scheduleEmailReminder({
...baseArgs,
teamId: 123,
});
expect(sendgridProvider.sendSendgridMail).not.toHaveBeenCalled();
expect(emailProvider.sendOrScheduleWorkflowEmails).toHaveBeenCalled();
});

test("should use SMTP when user has workflow-smtp-emails feature", async () => {
vi.spyOn(FeaturesRepository.prototype, "checkIfUserHasFeature").mockResolvedValue(true);

await scheduleEmailReminder({
...baseArgs,
userId: 123,
});
expect(sendgridProvider.sendSendgridMail).not.toHaveBeenCalled();
expect(emailProvider.sendOrScheduleWorkflowEmails).toHaveBeenCalled();
});

test("should use SendGrid when workflow-smtp-emails feature is not enabled for team", async () => {
vi.spyOn(FeaturesRepository.prototype, "checkIfTeamHasFeature").mockResolvedValue(false);

await scheduleEmailReminder({
...baseArgs,
teamId: 123,
});

expect(sendgridProvider.sendSendgridMail).toHaveBeenCalled();
expect(emailProvider.sendOrScheduleWorkflowEmails).not.toHaveBeenCalled();
});

test("should use SendGrid when workflow-smtp-emails feature is not enabled for user", async () => {
vi.spyOn(FeaturesRepository.prototype, "checkIfUserHasFeature").mockResolvedValue(false);

await scheduleEmailReminder({
...baseArgs,
userId: 123,
});

expect(sendgridProvider.sendSendgridMail).toHaveBeenCalled();
expect(emailProvider.sendOrScheduleWorkflowEmails).not.toHaveBeenCalled();
});

test("should use SMTP when SendGrid is not configured", async () => {
vi.spyOn(FeaturesRepository.prototype, "checkIfTeamHasFeature").mockResolvedValue(false);

vi.spyOn(FeaturesRepository.prototype, "checkIfUserHasFeature").mockResolvedValue(false);

delete process.env.SENDGRID_API_KEY;
delete process.env.SENDGRID_EMAIL;

await scheduleEmailReminder({
...baseArgs,
teamId: 123,
userId: 456,
});

expect(sendgridProvider.sendSendgridMail).not.toHaveBeenCalled();
test("should use SMTP", async () => {
await scheduleEmailReminder(baseArgs);
expect(emailProvider.sendOrScheduleWorkflowEmails).toHaveBeenCalled();
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -388,8 +388,6 @@ export const activateEventTypeHandler = async ({ ctx, input }: ActivateEventType
sender: step.sender,
workflowStepId: step.id,
verifiedAt: step.verifiedAt,
userId: eventTypeWorkflow.userId,
teamId: eventTypeWorkflow.teamId,
});
} else if (step.action === WorkflowActions.SMS_NUMBER && step.sendTo) {
await scheduleSMSReminder({
Expand Down
2 changes: 0 additions & 2 deletions packages/trpc/server/routers/viewer/workflows/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -794,8 +794,6 @@ export async function scheduleBookingReminders(
sender: step.sender,
workflowStepId: step.id,
verifiedAt: step?.verifiedAt ?? null,
userId,
teamId,
});
} else if (step.action === WorkflowActions.SMS_NUMBER && step.sendTo) {
await scheduleSMSReminder({
Expand Down
Loading