From a73b804d48a2095058c59679ee98a509b62abfff Mon Sep 17 00:00:00 2001 From: Morgan <33722304+ThyMinimalDev@users.noreply.github.com> Date: Tue, 11 Nov 2025 14:21:10 +0200 Subject: [PATCH] refactor: Split EmailManager into focused service files (#24997) * refactor: Split EmailManager into focused service files - Created separate service files for different email categories: - auth-email-service.ts: Authentication and verification emails - organization-email-service.ts: Organization and team emails - billing-email-service.ts: Payment and credit-related emails - integration-email-service.ts: Integration and app-related emails - workflow-email-service.ts: Workflow and custom emails - recording-email-service.ts: Recording and transcript emails - Refactored email-manager.ts to keep only core booking lifecycle functions - Removed unused imports from email-manager.ts - Updated index.ts to export from all new service files - Updated all imports across the codebase to use package root (@calcom/emails) - Fixed lint warnings in handleChildrenEventTypes.ts This reduces the import cost of EmailManager by allowing consumers to import only the specific email services they need. Co-Authored-By: morgan@cal.com * refactor: Update all imports to use direct service file paths - Update 49 files to import directly from service files instead of barrel file - Update packages/emails/index.ts to keep only email-manager and renderEmail exports - Fix dynamic import in passwordResetRequest.ts - Update renderEmail imports to use direct path - Update test file to import from specific service module - Fix ESLint warnings in modified files (unused variables, unused expressions) This ensures consumers only import the specific email services they need, reducing import cost by avoiding the barrel file pattern for service files. Co-Authored-By: morgan@cal.com * fix: Use default import for renderEmail renderEmail is exported as a default export, not a named export. Changed from 'import { renderEmail }' to 'import renderEmail'. Co-Authored-By: morgan@cal.com * fix: Update test mocks to use direct service file imports - Update handleNoShowFee.test.ts to mock @calcom/emails/billing-email-service - Update credit-service.test.ts to mock @calcom/emails/billing-email-service - These tests were failing because they were mocking the barrel file @calcom/emails which no longer exports service functions after the refactoring Co-Authored-By: morgan@cal.com * fix: unit test spy * fix: unit test mock * address cubic comments * fix: type error sendMonthlyDigestEmail * remove barrel file and sendEmail unused task * fixup! remove barrel file and sendEmail unused task * fixup! fixup! remove barrel file and sendEmail unused task * fix: integration test mock emails --------- Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Co-authored-by: hbjORbj --- .../web/app/api/cron/bookingReminder/route.ts | 2 +- .../app/api/cron/monthlyDigestEmail/route.ts | 4 +- apps/web/app/api/email/route.ts | 2 +- .../test/lib/handleChildrenEventTypes.test.ts | 4 +- .../bookingScenario/MockPaymentService.ts | 2 +- .../_utils/payments/handlePaymentSuccess.ts | 4 +- .../emails/templates/response-email.ts | 2 +- .../stripepayment/lib/PaymentService.ts | 2 +- packages/app-store/vital/lib/reschedule.ts | 2 +- .../wipemycalother/lib/reschedule.ts | 2 +- packages/emails/README.md | 2 +- packages/emails/auth-email-service.ts | 37 +++ packages/emails/billing-email-service.ts | 129 ++++++++ packages/emails/email-manager.ts | 286 ------------------ packages/emails/index.ts | 2 - packages/emails/integration-email-service.ts | 82 +++++ packages/emails/organization-email-service.ts | 45 +++ packages/emails/recording-email-service.ts | 48 +++ packages/emails/workflow-email-service.ts | 37 +++ .../features/auth/lib/passwordResetRequest.ts | 2 +- packages/features/auth/lib/verifyEmail.ts | 2 +- .../bookings/lib/BookingEmailSmsHandler.ts | 2 +- .../bookings/lib/handleBookingRequested.ts | 2 +- .../bookings/lib/handleCancelBooking.ts | 2 +- .../bookings/lib/handleConfirmation.ts | 4 +- .../handleSeats/cancel/cancelAttendeeSeat.ts | 4 +- .../lib/handleSeats/create/createNewSeat.ts | 4 +- .../attendeeRescheduleSeatedBooking.ts | 4 +- .../owner/combineTwoSeatedBookings.ts | 4 +- .../owner/moveSeatedBookingToNewTimeSlot.ts | 4 +- .../lib/payment/handleNoShowFee.test.ts | 4 +- .../bookings/lib/payment/handleNoShowFee.ts | 2 +- .../bookings/lib/payment/handleRefundError.ts | 2 +- .../features/conferencing/lib/videoClient.ts | 20 +- .../credentials/handleDeleteCredential.ts | 2 +- .../ee/billing/credit-service.test.ts | 4 +- .../features/ee/billing/credit-service.ts | 2 +- .../helpers/handleInsufficientCredits.ts | 2 +- .../lib/handleChildrenEventTypes.ts | 8 +- .../lib/server/orgCreationUtils.ts | 2 +- .../onboarding/BaseOnboardingService.ts | 2 +- packages/features/ee/payments/api/webhook.ts | 4 +- .../roundRobinManualReassignment.test.ts | 2 +- .../roundRobinManualReassignment.ts | 18 +- .../ee/round-robin/roundRobinReassignment.ts | 6 +- .../lib/reminders/providers/emailProvider.ts | 2 +- packages/features/tasker/tasker.ts | 1 - packages/features/tasker/tasks/index.ts | 1 - packages/features/tasker/tasks/sendEmail.ts | 25 -- .../tasker/tasks/sendWorkflowEmails.ts | 2 +- .../loggedInViewer/connectAndJoin.handler.ts | 4 +- .../viewer/admin/sendPasswordReset.handler.ts | 2 +- .../routers/viewer/apps/toggle.handler.ts | 2 +- .../viewer/bookings/confirm.handler.ts | 2 +- .../viewer/bookings/editLocation.handler.ts | 2 +- .../bookings/requestReschedule.handler.ts | 6 +- .../toggleEnabled.handler.ts | 2 +- .../ooo/outOfOfficeCreateOrUpdate.handler.ts | 4 +- .../ooo/outOfOfficeEntryDelete.handler.ts | 2 +- .../viewer/organizations/create.handler.ts | 2 +- .../handleNotificationWhenNoSlots.test.ts | 2 +- .../slots/handleNotificationWhenNoSlots.ts | 2 +- .../inviteMember.handler.integration-test.ts | 11 +- .../viewer/teams/inviteMember/utils.ts | 2 +- .../viewer/teams/resendInvitation.handler.ts | 2 +- setupVitest.ts | 11 +- 66 files changed, 477 insertions(+), 420 deletions(-) create mode 100644 packages/emails/auth-email-service.ts create mode 100644 packages/emails/billing-email-service.ts delete mode 100644 packages/emails/index.ts create mode 100644 packages/emails/integration-email-service.ts create mode 100644 packages/emails/organization-email-service.ts create mode 100644 packages/emails/recording-email-service.ts create mode 100644 packages/emails/workflow-email-service.ts delete mode 100644 packages/features/tasker/tasks/sendEmail.ts diff --git a/apps/web/app/api/cron/bookingReminder/route.ts b/apps/web/app/api/cron/bookingReminder/route.ts index a70199956d..373e3abd41 100644 --- a/apps/web/app/api/cron/bookingReminder/route.ts +++ b/apps/web/app/api/cron/bookingReminder/route.ts @@ -3,7 +3,7 @@ import type { NextRequest } from "next/server"; import { NextResponse } from "next/server"; import dayjs from "@calcom/dayjs"; -import { sendOrganizerRequestReminderEmail } from "@calcom/emails"; +import { sendOrganizerRequestReminderEmail } from "@calcom/emails/email-manager"; import { getCalEventResponses } from "@calcom/features/bookings/lib/getCalEventResponses"; import { isPrismaObjOrUndefined } from "@calcom/lib/isPrismaObj"; import { parseRecurringEvent } from "@calcom/lib/isRecurringEvent"; diff --git a/apps/web/app/api/cron/monthlyDigestEmail/route.ts b/apps/web/app/api/cron/monthlyDigestEmail/route.ts index 80e55f60f6..ce20c55262 100644 --- a/apps/web/app/api/cron/monthlyDigestEmail/route.ts +++ b/apps/web/app/api/cron/monthlyDigestEmail/route.ts @@ -4,7 +4,7 @@ import { NextResponse } from "next/server"; import { z } from "zod"; import dayjs from "@calcom/dayjs"; -import { sendMonthlyDigestEmails } from "@calcom/emails/email-manager"; +import { sendMonthlyDigestEmail } from "@calcom/emails/workflow-email-service"; import { EventsInsights } from "@calcom/features/insights/server/events"; import { getTranslation } from "@calcom/lib/server/i18n"; import prisma from "@calcom/prisma"; @@ -295,7 +295,7 @@ async function postHandler(request: NextRequest) { // Only send email if user has allowed to receive monthly digest emails if (owner.receiveMonthlyDigestEmail) { - await sendMonthlyDigestEmails({ + await sendMonthlyDigestEmail({ ...EventData, admin: { email: owner?.email ?? "", name: owner?.name ?? "" }, language: t, diff --git a/apps/web/app/api/email/route.ts b/apps/web/app/api/email/route.ts index 90d266e117..6e48c5f859 100644 --- a/apps/web/app/api/email/route.ts +++ b/apps/web/app/api/email/route.ts @@ -1,7 +1,7 @@ import { defaultResponderForAppDir } from "app/api/defaultResponderForAppDir"; import { NextResponse } from "next/server"; -import { renderEmail } from "@calcom/emails"; +import renderEmail from "@calcom/emails/src/renderEmail"; import { IS_PRODUCTION } from "@calcom/lib/constants"; import { getTranslation } from "@calcom/lib/server/i18n"; diff --git a/apps/web/test/lib/handleChildrenEventTypes.test.ts b/apps/web/test/lib/handleChildrenEventTypes.test.ts index e63fc1089b..e6c968a208 100644 --- a/apps/web/test/lib/handleChildrenEventTypes.test.ts +++ b/apps/web/test/lib/handleChildrenEventTypes.test.ts @@ -23,7 +23,7 @@ const mockFindFirstEventType = ( return eventType; }; -vi.mock("@calcom/emails/email-manager", () => { +vi.mock("@calcom/emails/integration-email-service", () => { return { sendSlugReplacementEmail: () => ({}), }; @@ -419,7 +419,7 @@ describe("handleChildrenEventTypes", () => { timeZone: _timeZone, parentId: _parentId, userId: _userId, - + requiresBookerEmailVerification, lockTimeZoneToggleOnBookingPage, useEventTypeDestinationCalendarEmail, diff --git a/apps/web/test/utils/bookingScenario/MockPaymentService.ts b/apps/web/test/utils/bookingScenario/MockPaymentService.ts index a10087696e..4ae9633afa 100644 --- a/apps/web/test/utils/bookingScenario/MockPaymentService.ts +++ b/apps/web/test/utils/bookingScenario/MockPaymentService.ts @@ -2,7 +2,7 @@ import prismaMock from "../../../../../tests/libs/__mocks__/prisma"; import "vitest-fetch-mock"; -import { sendAwaitingPaymentEmailAndSMS } from "@calcom/emails"; +import { sendAwaitingPaymentEmailAndSMS } from "@calcom/emails/email-manager"; import logger from "@calcom/lib/logger"; import type { Payment, Prisma, PaymentOption, Booking } from "@calcom/prisma/client"; import type { CalendarEvent } from "@calcom/types/Calendar"; diff --git a/packages/app-store/_utils/payments/handlePaymentSuccess.ts b/packages/app-store/_utils/payments/handlePaymentSuccess.ts index dda7a8c9af..4a5c3000e3 100644 --- a/packages/app-store/_utils/payments/handlePaymentSuccess.ts +++ b/packages/app-store/_utils/payments/handlePaymentSuccess.ts @@ -1,5 +1,6 @@ import { eventTypeAppMetadataOptionalSchema } from "@calcom/app-store/zod-utils"; -import { sendScheduledEmailsAndSMS } from "@calcom/emails"; +import { sendScheduledEmailsAndSMS } from "@calcom/emails/email-manager"; +import EventManager, { placeholderCreatedEvent } from "@calcom/features/bookings/lib/EventManager"; import { doesBookingRequireConfirmation } from "@calcom/features/bookings/lib/doesBookingRequireConfirmation"; import { getAllCredentialsIncludeServiceAccountKey } from "@calcom/features/bookings/lib/getAllCredentialsForUsersOnEvent/getAllCredentials"; import { handleBookingRequested } from "@calcom/features/bookings/lib/handleBookingRequested"; @@ -7,7 +8,6 @@ import { handleConfirmation } from "@calcom/features/bookings/lib/handleConfirma import { getBooking } from "@calcom/features/bookings/lib/payment/getBooking"; import { getPlatformParams } from "@calcom/features/platform-oauth-client/get-platform-params"; import { PlatformOAuthClientRepository } from "@calcom/features/platform-oauth-client/platform-oauth-client.repository"; -import EventManager, { placeholderCreatedEvent } from "@calcom/features/bookings/lib/EventManager"; import { HttpError as HttpCode } from "@calcom/lib/http-error"; import logger from "@calcom/lib/logger"; import prisma from "@calcom/prisma"; diff --git a/packages/app-store/routing-forms/emails/templates/response-email.ts b/packages/app-store/routing-forms/emails/templates/response-email.ts index 1c3c9a9435..f0d8a75859 100644 --- a/packages/app-store/routing-forms/emails/templates/response-email.ts +++ b/packages/app-store/routing-forms/emails/templates/response-email.ts @@ -1,4 +1,4 @@ -import { renderEmail } from "@calcom/emails"; +import renderEmail from "@calcom/emails/src/renderEmail"; import BaseEmail from "@calcom/emails/templates/_base-email"; import type { App_RoutingForms_Form } from "@calcom/prisma/client"; diff --git a/packages/app-store/stripepayment/lib/PaymentService.ts b/packages/app-store/stripepayment/lib/PaymentService.ts index 0e48dc560a..8045d27fb9 100644 --- a/packages/app-store/stripepayment/lib/PaymentService.ts +++ b/packages/app-store/stripepayment/lib/PaymentService.ts @@ -2,7 +2,7 @@ import Stripe from "stripe"; import { v4 as uuidv4 } from "uuid"; import z from "zod"; -import { sendAwaitingPaymentEmailAndSMS } from "@calcom/emails"; +import { sendAwaitingPaymentEmailAndSMS } from "@calcom/emails/email-manager"; import { BookingRepository } from "@calcom/features/bookings/repositories/BookingRepository"; import { ErrorCode } from "@calcom/lib/errorCodes"; import { getErrorFromUnknown } from "@calcom/lib/errors"; diff --git a/packages/app-store/vital/lib/reschedule.ts b/packages/app-store/vital/lib/reschedule.ts index f29ceab2cf..86c7a69f97 100644 --- a/packages/app-store/vital/lib/reschedule.ts +++ b/packages/app-store/vital/lib/reschedule.ts @@ -1,7 +1,7 @@ import type { TFunction } from "i18next"; import dayjs from "@calcom/dayjs"; -import { sendRequestRescheduleEmailAndSMS } from "@calcom/emails"; +import { sendRequestRescheduleEmailAndSMS } from "@calcom/emails/email-manager"; import { deleteMeeting } from "@calcom/features/conferencing/lib/videoClient"; import { CalendarEventBuilder } from "@calcom/lib/builders/CalendarEvent/builder"; import { CalendarEventDirector } from "@calcom/lib/builders/CalendarEvent/director"; diff --git a/packages/app-store/wipemycalother/lib/reschedule.ts b/packages/app-store/wipemycalother/lib/reschedule.ts index 95303b8925..9b9fc96eb5 100644 --- a/packages/app-store/wipemycalother/lib/reschedule.ts +++ b/packages/app-store/wipemycalother/lib/reschedule.ts @@ -1,7 +1,7 @@ import type { TFunction } from "i18next"; import dayjs from "@calcom/dayjs"; -import { sendRequestRescheduleEmailAndSMS } from "@calcom/emails"; +import { sendRequestRescheduleEmailAndSMS } from "@calcom/emails/email-manager"; import { deleteMeeting } from "@calcom/features/conferencing/lib/videoClient"; import { CalendarEventBuilder } from "@calcom/lib/builders/CalendarEvent/builder"; import { CalendarEventDirector } from "@calcom/lib/builders/CalendarEvent/director"; diff --git a/packages/emails/README.md b/packages/emails/README.md index bd5439926a..26750463f7 100644 --- a/packages/emails/README.md +++ b/packages/emails/README.md @@ -6,7 +6,7 @@ ## Usage ```ts -import { renderEmail } from "@calcom/emails"; +import renderEmail from "@calcom/emails/renderEmail"; await renderEmail("TeamInviteEmail", { language: t, diff --git a/packages/emails/auth-email-service.ts b/packages/emails/auth-email-service.ts new file mode 100644 index 0000000000..3fb50adc94 --- /dev/null +++ b/packages/emails/auth-email-service.ts @@ -0,0 +1,37 @@ +import type BaseEmail from "@calcom/emails/templates/_base-email"; + +import type { PasswordReset } from "./templates/forgot-password-email"; +import ForgotPasswordEmail from "./templates/forgot-password-email"; +import type { EmailVerifyLink } from "./templates/account-verify-email"; +import AccountVerifyEmail from "./templates/account-verify-email"; +import type { EmailVerifyCode } from "./templates/attendee-verify-email"; +import AttendeeVerifyEmail from "./templates/attendee-verify-email"; +import type { ChangeOfEmailVerifyLink } from "./templates/change-account-email-verify"; +import ChangeOfEmailVerifyEmail from "./templates/change-account-email-verify"; + +const sendEmail = (prepare: () => BaseEmail) => { + return new Promise((resolve, reject) => { + try { + const email = prepare(); + resolve(email.sendEmail()); + } catch (e) { + reject(console.error(`${prepare.constructor.name}.sendEmail failed`, e)); + } + }); +}; + +export const sendPasswordResetEmail = async (passwordResetEvent: PasswordReset) => { + await sendEmail(() => new ForgotPasswordEmail(passwordResetEvent)); +}; + +export const sendEmailVerificationLink = async (verificationInput: EmailVerifyLink) => { + await sendEmail(() => new AccountVerifyEmail(verificationInput)); +}; + +export const sendEmailVerificationCode = async (verificationInput: EmailVerifyCode) => { + await sendEmail(() => new AttendeeVerifyEmail(verificationInput)); +}; + +export const sendChangeOfEmailVerificationLink = async (verificationInput: ChangeOfEmailVerifyLink) => { + await sendEmail(() => new ChangeOfEmailVerifyEmail(verificationInput)); +}; diff --git a/packages/emails/billing-email-service.ts b/packages/emails/billing-email-service.ts new file mode 100644 index 0000000000..4660482489 --- /dev/null +++ b/packages/emails/billing-email-service.ts @@ -0,0 +1,129 @@ +import type { TFunction } from "i18next"; + +import type BaseEmail from "@calcom/emails/templates/_base-email"; +import type { CalendarEvent, Person } from "@calcom/types/Calendar"; +import type { CreditUsageType } from "@calcom/prisma/enums"; +import type { EventTypeMetadata } from "@calcom/prisma/zod-utils"; + +import OrganizerPaymentRefundFailedEmail from "./templates/organizer-payment-refund-failed-email"; +import NoShowFeeChargedEmail from "./templates/no-show-fee-charged-email"; +import CreditBalanceLowWarningEmail from "./templates/credit-balance-low-warning-email"; +import CreditBalanceLimitReachedEmail from "./templates/credit-balance-limit-reached-email"; + +const sendEmail = (prepare: () => BaseEmail) => { + return new Promise((resolve, reject) => { + try { + const email = prepare(); + resolve(email.sendEmail()); + } catch (e) { + reject(console.error(`${prepare.constructor.name}.sendEmail failed`, e)); + } + }); +}; + +const eventTypeDisableAttendeeEmail = (metadata?: EventTypeMetadata) => { + return !!metadata?.disableStandardEmails?.all?.attendee; +}; + +export const sendOrganizerPaymentRefundFailedEmail = async (calEvent: CalendarEvent) => { + const emailsToSend: Promise[] = []; + emailsToSend.push(sendEmail(() => new OrganizerPaymentRefundFailedEmail({ calEvent }))); + + if (calEvent.team?.members) { + for (const teamMember of calEvent.team.members) { + emailsToSend.push(sendEmail(() => new OrganizerPaymentRefundFailedEmail({ calEvent, teamMember }))); + } + } + + await Promise.all(emailsToSend); +}; + +export const sendNoShowFeeChargedEmail = async ( + attendee: Person, + evt: CalendarEvent, + eventTypeMetadata?: EventTypeMetadata +) => { + if (eventTypeDisableAttendeeEmail(eventTypeMetadata)) return; + await sendEmail(() => new NoShowFeeChargedEmail(evt, attendee)); +}; + +export const sendCreditBalanceLowWarningEmails = async (input: { + team?: { + name: string | null; + id: number; + adminAndOwners: { + id: number; + name: string | null; + email: string; + t: TFunction; + }[]; + }; + user?: { + id: number; + name: string | null; + email: string; + t: TFunction; + }; + balance: number; + creditFor?: CreditUsageType; +}) => { + const { team, balance, user, creditFor } = input; + if ((!team || !team.adminAndOwners.length) && !user) return; + + if (team) { + const emailsToSend: Promise[] = []; + + for (const admin of team.adminAndOwners) { + emailsToSend.push( + sendEmail(() => new CreditBalanceLowWarningEmail({ user: admin, balance, team, creditFor })) + ); + } + + await Promise.all(emailsToSend); + } + + if (user) { + await sendEmail(() => new CreditBalanceLowWarningEmail({ user, balance, creditFor })); + } +}; + +export const sendCreditBalanceLimitReachedEmails = async ({ + team, + user, + creditFor, +}: { + team?: { + name: string; + id: number; + adminAndOwners: { + id: number; + name: string | null; + email: string; + t: TFunction; + }[]; + }; + user?: { + id: number; + name: string | null; + email: string; + t: TFunction; + }; + creditFor?: CreditUsageType; +}) => { + if ((!team || !team.adminAndOwners.length) && !user) return; + + if (team) { + const emailsToSend: Promise[] = []; + + for (const admin of team.adminAndOwners) { + emailsToSend.push( + sendEmail(() => new CreditBalanceLimitReachedEmail({ user: admin, team, creditFor })) + ); + } + await Promise.all(emailsToSend); + } + + if (user) { + await sendEmail(() => new CreditBalanceLimitReachedEmail({ user, creditFor })); + } +}; diff --git a/packages/emails/email-manager.ts b/packages/emails/email-manager.ts index c8f639cb3e..36b7086fb3 100644 --- a/packages/emails/email-manager.ts +++ b/packages/emails/email-manager.ts @@ -1,4 +1,3 @@ -import type { TFunction } from "i18next"; import { default as cloneDeep } from "lodash/cloneDeep"; import type { z } from "zod"; @@ -10,7 +9,6 @@ import { formatCalEvent } from "@calcom/lib/formatCalendarEvent"; import logger from "@calcom/lib/logger"; import { safeStringify } from "@calcom/lib/safeStringify"; import { withReporting } from "@calcom/lib/sentryWrapper"; -import type { CreditUsageType } from "@calcom/prisma/enums"; import type { EventTypeMetaDataSchema } from "@calcom/prisma/zod-utils"; import type { CalendarEvent, Person } from "@calcom/types/Calendar"; @@ -23,65 +21,27 @@ import EventRequestSMS from "../sms/attendee/event-request-sms"; import EventRequestToRescheduleSMS from "../sms/attendee/event-request-to-reschedule-sms"; import EventSuccessfullyReScheduledSMS from "../sms/attendee/event-rescheduled-sms"; import EventSuccessfullyScheduledSMS from "../sms/attendee/event-scheduled-sms"; -import type { MonthlyDigestEmailData } from "./src/templates/MonthlyDigestEmail"; -import type { OrganizationAdminNoSlotsEmailInput } from "./src/templates/OrganizationAdminNoSlots"; -import type { EmailVerifyLink } from "./templates/account-verify-email"; -import AccountVerifyEmail from "./templates/account-verify-email"; -import type { OrganizationNotification } from "./templates/admin-organization-notification"; -import AdminOrganizationNotification from "./templates/admin-organization-notification"; import AttendeeAddGuestsEmail from "./templates/attendee-add-guests-email"; import AttendeeAwaitingPaymentEmail from "./templates/attendee-awaiting-payment-email"; import AttendeeCancelledEmail from "./templates/attendee-cancelled-email"; import AttendeeCancelledSeatEmail from "./templates/attendee-cancelled-seat-email"; -import AttendeeDailyVideoDownloadRecordingEmail from "./templates/attendee-daily-video-download-recording-email"; -import AttendeeDailyVideoDownloadTranscriptEmail from "./templates/attendee-daily-video-download-transcript-email"; import AttendeeDeclinedEmail from "./templates/attendee-declined-email"; import AttendeeLocationChangeEmail from "./templates/attendee-location-change-email"; import AttendeeRequestEmail from "./templates/attendee-request-email"; import AttendeeRescheduledEmail from "./templates/attendee-rescheduled-email"; import AttendeeScheduledEmail from "./templates/attendee-scheduled-email"; import AttendeeUpdatedEmail from "./templates/attendee-updated-email"; -import type { EmailVerifyCode } from "./templates/attendee-verify-email"; -import AttendeeVerifyEmail from "./templates/attendee-verify-email"; import AttendeeWasRequestedToRescheduleEmail from "./templates/attendee-was-requested-to-reschedule-email"; -import BookingRedirectEmailNotification from "./templates/booking-redirect-notification"; -import type { IBookingRedirect } from "./templates/booking-redirect-notification"; -import BrokenIntegrationEmail from "./templates/broken-integration-email"; -import type { ChangeOfEmailVerifyLink } from "./templates/change-account-email-verify"; -import ChangeOfEmailVerifyEmail from "./templates/change-account-email-verify"; -import CreditBalanceLimitReachedEmail from "./templates/credit-balance-limit-reached-email"; -import CreditBalanceLowWarningEmail from "./templates/credit-balance-low-warning-email"; -import DelegationCredentialDisabledEmail from "./templates/delegation-credential-disabled-email"; -import DisabledAppEmail from "./templates/disabled-app-email"; -import type { Feedback } from "./templates/feedback-email"; -import FeedbackEmail from "./templates/feedback-email"; -import type { PasswordReset } from "./templates/forgot-password-email"; -import ForgotPasswordEmail from "./templates/forgot-password-email"; -import MonthlyDigestEmail from "./templates/monthly-digest-email"; -import NoShowFeeChargedEmail from "./templates/no-show-fee-charged-email"; -import OrganizationAdminNoSlotsEmail from "./templates/organization-admin-no-slots-email"; -import type { OrganizationCreation } from "./templates/organization-creation-email"; -import OrganizationCreationEmail from "./templates/organization-creation-email"; -import type { OrganizationEmailVerify } from "./templates/organization-email-verification"; -import OrganizationEmailVerification from "./templates/organization-email-verification"; import OrganizerAddGuestsEmail from "./templates/organizer-add-guests-email"; import OrganizerAttendeeCancelledSeatEmail from "./templates/organizer-attendee-cancelled-seat-email"; import OrganizerCancelledEmail from "./templates/organizer-cancelled-email"; -import OrganizerDailyVideoDownloadRecordingEmail from "./templates/organizer-daily-video-download-recording-email"; -import OrganizerDailyVideoDownloadTranscriptEmail from "./templates/organizer-daily-video-download-transcript-email"; import OrganizerLocationChangeEmail from "./templates/organizer-location-change-email"; -import OrganizerPaymentRefundFailedEmail from "./templates/organizer-payment-refund-failed-email"; import OrganizerReassignedEmail from "./templates/organizer-reassigned-email"; import OrganizerRequestEmail from "./templates/organizer-request-email"; import OrganizerRequestReminderEmail from "./templates/organizer-request-reminder-email"; import OrganizerRequestedToRescheduleEmail from "./templates/organizer-requested-to-reschedule-email"; import OrganizerRescheduledEmail from "./templates/organizer-rescheduled-email"; import OrganizerScheduledEmail from "./templates/organizer-scheduled-email"; -import SlugReplacementEmail from "./templates/slug-replacement-email"; -import type { TeamInvite } from "./templates/team-invite-email"; -import TeamInviteEmail from "./templates/team-invite-email"; -import type { WorkflowEmailData } from "./templates/workflow-email"; -import WorkflowEmail from "./templates/workflow-email"; type EventTypeMetadata = z.infer; @@ -571,52 +531,6 @@ export const sendAwaitingPaymentEmailAndSMS = async ( await awaitingPaymentSMS.sendSMSToAttendees(); }; -export const sendOrganizerPaymentRefundFailedEmail = async (calEvent: CalendarEvent) => { - const emailsToSend: Promise[] = []; - emailsToSend.push(sendEmail(() => new OrganizerPaymentRefundFailedEmail({ calEvent }))); - - if (calEvent.team?.members) { - for (const teamMember of calEvent.team.members) { - emailsToSend.push(sendEmail(() => new OrganizerPaymentRefundFailedEmail({ calEvent, teamMember }))); - } - } - - await Promise.all(emailsToSend); -}; - -export const sendPasswordResetEmail = async (passwordResetEvent: PasswordReset) => { - await sendEmail(() => new ForgotPasswordEmail(passwordResetEvent)); -}; - -export const sendTeamInviteEmail = async (teamInviteEvent: TeamInvite) => { - await sendEmail(() => new TeamInviteEmail(teamInviteEvent)); -}; - -export const sendCustomWorkflowEmail = async (emailData: WorkflowEmailData) => { - await sendEmail(() => new WorkflowEmail(emailData)); -}; - -export const sendOrganizationCreationEmail = async (organizationCreationEvent: OrganizationCreation) => { - await sendEmail(() => new OrganizationCreationEmail(organizationCreationEvent)); -}; - -export const sendOrganizationAdminNoSlotsNotification = async ( - orgInviteEvent: OrganizationAdminNoSlotsEmailInput -) => { - await sendEmail(() => new OrganizationAdminNoSlotsEmail(orgInviteEvent)); -}; - -export const sendEmailVerificationLink = async (verificationInput: EmailVerifyLink) => { - await sendEmail(() => new AccountVerifyEmail(verificationInput)); -}; - -export const sendEmailVerificationCode = async (verificationInput: EmailVerifyCode) => { - await sendEmail(() => new AttendeeVerifyEmail(verificationInput)); -}; - -export const sendChangeOfEmailVerificationLink = async (verificationInput: ChangeOfEmailVerifyLink) => { - await sendEmail(() => new ChangeOfEmailVerifyEmail(verificationInput)); -}; export const sendRequestRescheduleEmailAndSMS = async ( calEvent: CalendarEvent, @@ -737,203 +651,3 @@ export const sendAddGuestsEmailsAndSMS = async (args: { await Promise.all(emailsAndSMSToSend); }; - -export const sendFeedbackEmail = async (feedback: Feedback) => { - await sendEmail(() => new FeedbackEmail(feedback)); -}; - -export const sendBrokenIntegrationEmail = async (evt: CalendarEvent, type: "video" | "calendar") => { - const calendarEvent = formatCalEvent(evt); - await sendEmail(() => new BrokenIntegrationEmail(calendarEvent, type)); -}; - -export const sendDisabledAppEmail = async ({ - email, - appName, - appType, - t, - title = undefined, - eventTypeId = undefined, -}: { - email: string; - appName: string; - appType: string[]; - t: TFunction; - title?: string; - eventTypeId?: number; -}) => { - await sendEmail(() => new DisabledAppEmail(email, appName, appType, t, title, eventTypeId)); -}; - -export const sendSlugReplacementEmail = async ({ - email, - name, - teamName, - t, - slug, -}: { - email: string; - name: string; - teamName: string | null; - t: TFunction; - slug: string; -}) => { - await sendEmail(() => new SlugReplacementEmail(email, name, teamName, slug, t)); -}; - -export const sendNoShowFeeChargedEmail = async ( - attendee: Person, - evt: CalendarEvent, - eventTypeMetadata?: EventTypeMetadata -) => { - if (eventTypeDisableAttendeeEmail(eventTypeMetadata)) return; - await sendEmail(() => new NoShowFeeChargedEmail(evt, attendee)); -}; - -export const sendDailyVideoRecordingEmails = async (calEvent: CalendarEvent, downloadLink: string) => { - const calendarEvent = formatCalEvent(calEvent); - const emailsToSend: Promise[] = []; - - emailsToSend.push( - sendEmail(() => new OrganizerDailyVideoDownloadRecordingEmail(calendarEvent, downloadLink)) - ); - - for (const attendee of calendarEvent.attendees) { - emailsToSend.push( - sendEmail(() => new AttendeeDailyVideoDownloadRecordingEmail(calendarEvent, attendee, downloadLink)) - ); - } - await Promise.all(emailsToSend); -}; - -export const sendDailyVideoTranscriptEmails = async (calEvent: CalendarEvent, transcripts: string[]) => { - const emailsToSend: Promise[] = []; - - emailsToSend.push(sendEmail(() => new OrganizerDailyVideoDownloadTranscriptEmail(calEvent, transcripts))); - - for (const attendee of calEvent.attendees) { - emailsToSend.push( - sendEmail(() => new AttendeeDailyVideoDownloadTranscriptEmail(calEvent, attendee, transcripts)) - ); - } - await Promise.all(emailsToSend); -}; - -export const sendOrganizationEmailVerification = async (sendOrgInput: OrganizationEmailVerify) => { - await sendEmail(() => new OrganizationEmailVerification(sendOrgInput)); -}; - -export const sendMonthlyDigestEmails = async (eventData: MonthlyDigestEmailData) => { - await sendEmail(() => new MonthlyDigestEmail(eventData)); -}; - -export const sendAdminOrganizationNotification = async (input: OrganizationNotification) => { - await sendEmail(() => new AdminOrganizationNotification(input)); -}; - -export const sendBookingRedirectNotification = async (bookingRedirect: IBookingRedirect) => { - await sendEmail(() => new BookingRedirectEmailNotification(bookingRedirect)); -}; - -export const sendCreditBalanceLowWarningEmails = async (input: { - team?: { - name: string | null; - id: number; - adminAndOwners: { - id: number; - name: string | null; - email: string; - t: TFunction; - }[]; - }; - user?: { - id: number; - name: string | null; - email: string; - t: TFunction; - }; - balance: number; - creditFor?: CreditUsageType; -}) => { - const { team, balance, user, creditFor } = input; - if ((!team || !team.adminAndOwners.length) && !user) return; - - if (team) { - const emailsToSend: Promise[] = []; - - for (const admin of team.adminAndOwners) { - emailsToSend.push( - sendEmail(() => new CreditBalanceLowWarningEmail({ user: admin, balance, team, creditFor })) - ); - } - - await Promise.all(emailsToSend); - } - - if (user) { - await sendEmail(() => new CreditBalanceLowWarningEmail({ user, balance, creditFor })); - } -}; - -export const sendCreditBalanceLimitReachedEmails = async ({ - team, - user, - creditFor, -}: { - team?: { - name: string; - id: number; - adminAndOwners: { - id: number; - name: string | null; - email: string; - t: TFunction; - }[]; - }; - user?: { - id: number; - name: string | null; - email: string; - t: TFunction; - }; - creditFor?: CreditUsageType; -}) => { - if ((!team || !team.adminAndOwners.length) && !user) return; - - if (team) { - const emailsToSend: Promise[] = []; - - for (const admin of team.adminAndOwners) { - emailsToSend.push( - sendEmail(() => new CreditBalanceLimitReachedEmail({ user: admin, team, creditFor })) - ); - } - await Promise.all(emailsToSend); - } - - if (user) { - await sendEmail(() => new CreditBalanceLimitReachedEmail({ user, creditFor })); - } -}; - -export const sendDelegationCredentialDisabledEmail = async ({ - recipientEmail, - recipientName, - calendarAppName, - conferencingAppName, -}: { - recipientEmail: string; - recipientName?: string; - calendarAppName: string; - conferencingAppName: string; -}) => { - await sendEmail( - () => - new DelegationCredentialDisabledEmail({ - recipientEmail, - recipientName, - calendarAppName, - conferencingAppName, - }) - ); -}; diff --git a/packages/emails/index.ts b/packages/emails/index.ts deleted file mode 100644 index bf927192b9..0000000000 --- a/packages/emails/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from "./email-manager"; -export { default as renderEmail } from "./src/renderEmail"; diff --git a/packages/emails/integration-email-service.ts b/packages/emails/integration-email-service.ts new file mode 100644 index 0000000000..83bb0d39e5 --- /dev/null +++ b/packages/emails/integration-email-service.ts @@ -0,0 +1,82 @@ +import type { TFunction } from "i18next"; + +import type BaseEmail from "@calcom/emails/templates/_base-email"; +import type { CalendarEvent } from "@calcom/types/Calendar"; +import { formatCalEvent } from "@calcom/lib/formatCalendarEvent"; + +import BrokenIntegrationEmail from "./templates/broken-integration-email"; +import DisabledAppEmail from "./templates/disabled-app-email"; +import SlugReplacementEmail from "./templates/slug-replacement-email"; +import DelegationCredentialDisabledEmail from "./templates/delegation-credential-disabled-email"; + +const sendEmail = (prepare: () => BaseEmail) => { + return new Promise((resolve, reject) => { + try { + const email = prepare(); + resolve(email.sendEmail()); + } catch (e) { + reject(console.error(`${prepare.constructor.name}.sendEmail failed`, e)); + } + }); +}; + +export const sendBrokenIntegrationEmail = async (evt: CalendarEvent, type: "video" | "calendar") => { + const calendarEvent = formatCalEvent(evt); + await sendEmail(() => new BrokenIntegrationEmail(calendarEvent, type)); +}; + +export const sendDisabledAppEmail = async ({ + email, + appName, + appType, + t, + title = undefined, + eventTypeId = undefined, +}: { + email: string; + appName: string; + appType: string[]; + t: TFunction; + title?: string; + eventTypeId?: number; +}) => { + await sendEmail(() => new DisabledAppEmail(email, appName, appType, t, title, eventTypeId)); +}; + +export const sendSlugReplacementEmail = async ({ + email, + name, + teamName, + t, + slug, +}: { + email: string; + name: string; + teamName: string | null; + t: TFunction; + slug: string; +}) => { + await sendEmail(() => new SlugReplacementEmail(email, name, teamName, slug, t)); +}; + +export const sendDelegationCredentialDisabledEmail = async ({ + recipientEmail, + recipientName, + calendarAppName, + conferencingAppName, +}: { + recipientEmail: string; + recipientName?: string; + calendarAppName: string; + conferencingAppName: string; +}) => { + await sendEmail( + () => + new DelegationCredentialDisabledEmail({ + recipientEmail, + recipientName, + calendarAppName, + conferencingAppName, + }) + ); +}; diff --git a/packages/emails/organization-email-service.ts b/packages/emails/organization-email-service.ts new file mode 100644 index 0000000000..7fbd4c229c --- /dev/null +++ b/packages/emails/organization-email-service.ts @@ -0,0 +1,45 @@ +import type BaseEmail from "@calcom/emails/templates/_base-email"; + +import type { TeamInvite } from "./templates/team-invite-email"; +import TeamInviteEmail from "./templates/team-invite-email"; +import type { OrganizationCreation } from "./templates/organization-creation-email"; +import OrganizationCreationEmail from "./templates/organization-creation-email"; +import type { OrganizationAdminNoSlotsEmailInput } from "./src/templates/OrganizationAdminNoSlots"; +import OrganizationAdminNoSlotsEmail from "./templates/organization-admin-no-slots-email"; +import type { OrganizationEmailVerify } from "./templates/organization-email-verification"; +import OrganizationEmailVerification from "./templates/organization-email-verification"; +import type { OrganizationNotification } from "./templates/admin-organization-notification"; +import AdminOrganizationNotification from "./templates/admin-organization-notification"; + +const sendEmail = (prepare: () => BaseEmail) => { + return new Promise((resolve, reject) => { + try { + const email = prepare(); + resolve(email.sendEmail()); + } catch (e) { + reject(console.error(`${prepare.constructor.name}.sendEmail failed`, e)); + } + }); +}; + +export const sendTeamInviteEmail = async (teamInviteEvent: TeamInvite) => { + await sendEmail(() => new TeamInviteEmail(teamInviteEvent)); +}; + +export const sendOrganizationCreationEmail = async (organizationCreationEvent: OrganizationCreation) => { + await sendEmail(() => new OrganizationCreationEmail(organizationCreationEvent)); +}; + +export const sendOrganizationAdminNoSlotsNotification = async ( + orgInviteEvent: OrganizationAdminNoSlotsEmailInput +) => { + await sendEmail(() => new OrganizationAdminNoSlotsEmail(orgInviteEvent)); +}; + +export const sendOrganizationEmailVerification = async (sendOrgInput: OrganizationEmailVerify) => { + await sendEmail(() => new OrganizationEmailVerification(sendOrgInput)); +}; + +export const sendAdminOrganizationNotification = async (input: OrganizationNotification) => { + await sendEmail(() => new AdminOrganizationNotification(input)); +}; diff --git a/packages/emails/recording-email-service.ts b/packages/emails/recording-email-service.ts new file mode 100644 index 0000000000..78f4674451 --- /dev/null +++ b/packages/emails/recording-email-service.ts @@ -0,0 +1,48 @@ +import type BaseEmail from "@calcom/emails/templates/_base-email"; +import type { CalendarEvent } from "@calcom/types/Calendar"; +import { formatCalEvent } from "@calcom/lib/formatCalendarEvent"; + +import OrganizerDailyVideoDownloadRecordingEmail from "./templates/organizer-daily-video-download-recording-email"; +import AttendeeDailyVideoDownloadRecordingEmail from "./templates/attendee-daily-video-download-recording-email"; +import OrganizerDailyVideoDownloadTranscriptEmail from "./templates/organizer-daily-video-download-transcript-email"; +import AttendeeDailyVideoDownloadTranscriptEmail from "./templates/attendee-daily-video-download-transcript-email"; + +const sendEmail = (prepare: () => BaseEmail) => { + return new Promise((resolve, reject) => { + try { + const email = prepare(); + resolve(email.sendEmail()); + } catch (e) { + reject(console.error(`${prepare.constructor.name}.sendEmail failed`, e)); + } + }); +}; + +export const sendDailyVideoRecordingEmails = async (calEvent: CalendarEvent, downloadLink: string) => { + const calendarEvent = formatCalEvent(calEvent); + const emailsToSend: Promise[] = []; + + emailsToSend.push( + sendEmail(() => new OrganizerDailyVideoDownloadRecordingEmail(calendarEvent, downloadLink)) + ); + + for (const attendee of calendarEvent.attendees) { + emailsToSend.push( + sendEmail(() => new AttendeeDailyVideoDownloadRecordingEmail(calendarEvent, attendee, downloadLink)) + ); + } + await Promise.all(emailsToSend); +}; + +export const sendDailyVideoTranscriptEmails = async (calEvent: CalendarEvent, transcripts: string[]) => { + const emailsToSend: Promise[] = []; + + emailsToSend.push(sendEmail(() => new OrganizerDailyVideoDownloadTranscriptEmail(calEvent, transcripts))); + + for (const attendee of calEvent.attendees) { + emailsToSend.push( + sendEmail(() => new AttendeeDailyVideoDownloadTranscriptEmail(calEvent, attendee, transcripts)) + ); + } + await Promise.all(emailsToSend); +}; diff --git a/packages/emails/workflow-email-service.ts b/packages/emails/workflow-email-service.ts new file mode 100644 index 0000000000..837ef95d1d --- /dev/null +++ b/packages/emails/workflow-email-service.ts @@ -0,0 +1,37 @@ +import type BaseEmail from "@calcom/emails/templates/_base-email"; + +import type { MonthlyDigestEmailData } from "./src/templates/MonthlyDigestEmail"; +import type { IBookingRedirect } from "./templates/booking-redirect-notification"; +import BookingRedirectEmailNotification from "./templates/booking-redirect-notification"; +import type { Feedback } from "./templates/feedback-email"; +import FeedbackEmail from "./templates/feedback-email"; +import MonthlyDigestEmail from "./templates/monthly-digest-email"; +import type { WorkflowEmailData } from "./templates/workflow-email"; +import WorkflowEmail from "./templates/workflow-email"; + +const sendEmail = (prepare: () => BaseEmail) => { + return new Promise((resolve, reject) => { + try { + const email = prepare(); + resolve(email.sendEmail()); + } catch (e) { + reject(console.error(`${prepare.constructor.name}.sendEmail failed`, e)); + } + }); +}; + +export const sendFeedbackEmail = async (feedback: Feedback) => { + await sendEmail(() => new FeedbackEmail(feedback)); +}; + +export const sendCustomWorkflowEmail = async (emailData: WorkflowEmailData) => { + await sendEmail(() => new WorkflowEmail(emailData)); +}; + +export const sendMonthlyDigestEmail = async (eventData: MonthlyDigestEmailData) => { + await sendEmail(() => new MonthlyDigestEmail(eventData)); +}; + +export const sendBookingRedirectNotification = async (bookingRedirect: IBookingRedirect) => { + await sendEmail(() => new BookingRedirectEmailNotification(bookingRedirect)); +}; diff --git a/packages/features/auth/lib/passwordResetRequest.ts b/packages/features/auth/lib/passwordResetRequest.ts index 5e502d6ef9..74b069023a 100644 --- a/packages/features/auth/lib/passwordResetRequest.ts +++ b/packages/features/auth/lib/passwordResetRequest.ts @@ -51,7 +51,7 @@ const passwordResetRequest = async (user: Pick ({ }, })); -vi.mock("@calcom/emails", () => ({ +vi.mock("@calcom/emails/billing-email-service", () => ({ sendNoShowFeeChargedEmail: vi.fn(), })); diff --git a/packages/features/bookings/lib/payment/handleNoShowFee.ts b/packages/features/bookings/lib/payment/handleNoShowFee.ts index bae0636869..4d84a3c051 100644 --- a/packages/features/bookings/lib/payment/handleNoShowFee.ts +++ b/packages/features/bookings/lib/payment/handleNoShowFee.ts @@ -1,7 +1,7 @@ import { PaymentServiceMap } from "@calcom/app-store/payment.services.generated"; import { eventTypeMetaDataSchemaWithTypedApps } from "@calcom/app-store/zod-utils"; import dayjs from "@calcom/dayjs"; -import { sendNoShowFeeChargedEmail } from "@calcom/emails"; +import { sendNoShowFeeChargedEmail } from "@calcom/emails/billing-email-service"; import { CredentialRepository } from "@calcom/features/credentials/repositories/CredentialRepository"; import { TeamRepository } from "@calcom/features/ee/teams/repositories/TeamRepository"; import { MembershipRepository } from "@calcom/features/membership/repositories/MembershipRepository"; diff --git a/packages/features/bookings/lib/payment/handleRefundError.ts b/packages/features/bookings/lib/payment/handleRefundError.ts index 9332a5aa77..d3bedc29e7 100644 --- a/packages/features/bookings/lib/payment/handleRefundError.ts +++ b/packages/features/bookings/lib/payment/handleRefundError.ts @@ -1,4 +1,4 @@ -import { sendOrganizerPaymentRefundFailedEmail } from "@calcom/emails"; +import { sendOrganizerPaymentRefundFailedEmail } from "@calcom/emails/billing-email-service"; import type { CalendarEvent } from "@calcom/types/Calendar"; const handleRefundError = async (opts: { event: CalendarEvent; reason: string; paymentId: string }) => { diff --git a/packages/features/conferencing/lib/videoClient.ts b/packages/features/conferencing/lib/videoClient.ts index ed4e8e5469..8c0b93e4dd 100644 --- a/packages/features/conferencing/lib/videoClient.ts +++ b/packages/features/conferencing/lib/videoClient.ts @@ -4,7 +4,7 @@ import { v5 as uuidv5 } from "uuid"; import { DailyLocationType } from "@calcom/app-store/constants"; import { getDailyAppKeys } from "@calcom/app-store/dailyvideo/lib/getDailyAppKeys"; import { getVideoAdapters } from "@calcom/app-store/getVideoAdapters"; -import { sendBrokenIntegrationEmail } from "@calcom/emails"; +import { sendBrokenIntegrationEmail } from "@calcom/emails/integration-email-service"; import { getUid } from "@calcom/lib/CalEventParser"; import logger from "@calcom/lib/logger"; import { getPiiFreeCalendarEvent, getPiiFreeCredential } from "@calcom/lib/piiFreeData"; @@ -161,7 +161,7 @@ const createMeetingWithCalVideo = async (calEvent: CalendarEvent) => { let dailyAppKeys: Awaited>; try { dailyAppKeys = await getDailyAppKeys(); - } catch (e) { + } catch { return; } const [videoAdapter] = await getVideoAdapters([ @@ -184,7 +184,7 @@ export const createInstantMeetingWithCalVideo = async (endTime: string) => { let dailyAppKeys: Awaited>; try { dailyAppKeys = await getDailyAppKeys(); - } catch (e) { + } catch { return; } const [videoAdapter] = await getVideoAdapters([ @@ -209,7 +209,7 @@ const getRecordingsOfCalVideoByRoomName = async ( let dailyAppKeys: Awaited>; try { dailyAppKeys = await getDailyAppKeys(); - } catch (e) { + } catch { console.error("Error: Cal video provider is not installed."); return; } @@ -235,7 +235,7 @@ const getDownloadLinkOfCalVideoByRecordingId = async ( let dailyAppKeys: Awaited>; try { dailyAppKeys = await getDailyAppKeys(); - } catch (e) { + } catch { console.error("Error: Cal video provider is not installed."); return; } @@ -259,7 +259,7 @@ const getAllTranscriptsAccessLinkFromRoomName = async (roomName: string) => { let dailyAppKeys: Awaited>; try { dailyAppKeys = await getDailyAppKeys(); - } catch (e) { + } catch { console.error("Error: Cal video provider is not installed."); return; } @@ -283,7 +283,7 @@ const getAllTranscriptsAccessLinkFromMeetingId = async (meetingId: string) => { let dailyAppKeys: Awaited>; try { dailyAppKeys = await getDailyAppKeys(); - } catch (e) { + } catch { console.error("Error: Cal video provider is not installed."); return; } @@ -307,7 +307,7 @@ const submitBatchProcessorTranscriptionJob = async (recordingId: string) => { let dailyAppKeys: Awaited>; try { dailyAppKeys = await getDailyAppKeys(); - } catch (e) { + } catch { console.error("Error: Cal video provider is not installed."); return; } @@ -343,7 +343,7 @@ const getTranscriptsAccessLinkFromRecordingId = async (recordingId: string) => { let dailyAppKeys: Awaited>; try { dailyAppKeys = await getDailyAppKeys(); - } catch (e) { + } catch { console.error("Error: Cal video provider is not installed."); return; } @@ -368,7 +368,7 @@ const checkIfRoomNameMatchesInRecording = async (roomName: string, recordingId: let dailyAppKeys: Awaited>; try { dailyAppKeys = await getDailyAppKeys(); - } catch (e) { + } catch { console.error("Error: Cal video provider is not installed."); return; } diff --git a/packages/features/credentials/handleDeleteCredential.ts b/packages/features/credentials/handleDeleteCredential.ts index b8787fd758..b3f7c3e131 100644 --- a/packages/features/credentials/handleDeleteCredential.ts +++ b/packages/features/credentials/handleDeleteCredential.ts @@ -8,7 +8,7 @@ import { eventTypeAppMetadataOptionalSchema, } from "@calcom/app-store/zod-utils"; import { eventTypeMetaDataSchemaWithTypedApps } from "@calcom/app-store/zod-utils"; -import { sendCancelledEmailsAndSMS } from "@calcom/emails"; +import { sendCancelledEmailsAndSMS } from "@calcom/emails/email-manager"; import { getCalEventResponses } from "@calcom/features/bookings/lib/getCalEventResponses"; import { deletePayment } from "@calcom/features/bookings/lib/payment/deletePayment"; import { deleteWebhookScheduledTriggers } from "@calcom/features/webhooks/lib/scheduleTrigger"; diff --git a/packages/features/ee/billing/credit-service.test.ts b/packages/features/ee/billing/credit-service.test.ts index 251aa2c66b..1bac738d44 100644 --- a/packages/features/ee/billing/credit-service.test.ts +++ b/packages/features/ee/billing/credit-service.test.ts @@ -2,7 +2,7 @@ import Stripe from "stripe"; import { describe, it, expect, beforeEach, vi } from "vitest"; import dayjs from "@calcom/dayjs"; -import * as EmailManager from "@calcom/emails/email-manager"; +import * as EmailManager from "@calcom/emails/billing-email-service"; import getOrgIdFromMemberOrTeamId from "@calcom/lib/getOrgIdFromMemberOrTeamId"; import { CreditsRepository } from "@calcom/lib/server/repository/credits"; import { MembershipRepository } from "@calcom/features/membership/repositories/MembershipRepository"; @@ -70,7 +70,7 @@ vi.mock("@calcom/prisma/enums", async (importOriginal) => { vi.mock("@calcom/lib/server/repository/credits"); vi.mock("@calcom/features/membership/repositories/MembershipRepository"); vi.mock("@calcom/features/ee/teams/repositories/TeamRepository"); -vi.mock("@calcom/emails/email-manager", () => ({ +vi.mock("@calcom/emails/billing-email-service", () => ({ sendCreditBalanceLimitReachedEmails: vi.fn().mockResolvedValue(undefined), sendCreditBalanceLowWarningEmails: vi.fn().mockResolvedValue(undefined), })); diff --git a/packages/features/ee/billing/credit-service.ts b/packages/features/ee/billing/credit-service.ts index da34190248..b362009233 100644 --- a/packages/features/ee/billing/credit-service.ts +++ b/packages/features/ee/billing/credit-service.ts @@ -4,7 +4,7 @@ import dayjs from "@calcom/dayjs"; import { sendCreditBalanceLimitReachedEmails, sendCreditBalanceLowWarningEmails, -} from "@calcom/emails/email-manager"; +} from "@calcom/emails/billing-email-service"; import { StripeBillingService } from "@calcom/features/ee/billing/stripe-billing-service"; import { InternalTeamBilling } from "@calcom/features/ee/billing/teams/internal-team-billing"; import { TeamRepository } from "@calcom/features/ee/teams/repositories/TeamRepository"; diff --git a/packages/features/ee/billing/helpers/handleInsufficientCredits.ts b/packages/features/ee/billing/helpers/handleInsufficientCredits.ts index 9df24f67fe..fd72461fe5 100644 --- a/packages/features/ee/billing/helpers/handleInsufficientCredits.ts +++ b/packages/features/ee/billing/helpers/handleInsufficientCredits.ts @@ -1,4 +1,4 @@ -import { sendCreditBalanceLimitReachedEmails } from "@calcom/emails/email-manager"; +import { sendCreditBalanceLimitReachedEmails } from "@calcom/emails/billing-email-service"; import logger from "@calcom/lib/logger"; import type { PrismaClient } from "@calcom/prisma"; import type { CreditUsageType } from "@calcom/prisma/enums"; diff --git a/packages/features/ee/managed-event-types/lib/handleChildrenEventTypes.ts b/packages/features/ee/managed-event-types/lib/handleChildrenEventTypes.ts index d6b227b830..f21c455a3d 100644 --- a/packages/features/ee/managed-event-types/lib/handleChildrenEventTypes.ts +++ b/packages/features/ee/managed-event-types/lib/handleChildrenEventTypes.ts @@ -1,8 +1,8 @@ -// eslint-disable-next-line no-restricted-imports + import type { DeepMockProxy } from "vitest-mock-extended"; import { eventTypeMetaDataSchemaWithTypedApps } from "@calcom/app-store/zod-utils"; -import { sendSlugReplacementEmail } from "@calcom/emails/email-manager"; +import { sendSlugReplacementEmail } from "@calcom/emails/integration-email-service"; import { getTranslation } from "@calcom/lib/server/i18n"; import type { PrismaClient } from "@calcom/prisma"; import type { Prisma } from "@calcom/prisma/client"; @@ -93,7 +93,7 @@ export default async function handleChildrenEventTypes({ children, prisma, profileId, - updatedValues, + updatedValues: _updatedValues, }: handleChildrenEventTypesProps) { // Check we are dealing with a managed event type if (updatedEventType?.schedulingType !== SchedulingType.MANAGED) @@ -222,7 +222,7 @@ export default async function handleChildrenEventTypes({ teamName: oldEventType.team?.name || null, }); - const { unlockedFields } = managedEventTypeValues.metadata?.managedEventConfig; + const { unlockedFields } = managedEventTypeValues.metadata?.managedEventConfig ?? {}; const unlockedFieldProps = !unlockedFields ? {} : Object.keys(unlockedFields).reduce((acc, key) => { diff --git a/packages/features/ee/organizations/lib/server/orgCreationUtils.ts b/packages/features/ee/organizations/lib/server/orgCreationUtils.ts index 5e17c3e00c..80ec76348e 100644 --- a/packages/features/ee/organizations/lib/server/orgCreationUtils.ts +++ b/packages/features/ee/organizations/lib/server/orgCreationUtils.ts @@ -1,7 +1,7 @@ import { lookup } from "dns"; import { type TFunction } from "i18next"; -import { sendAdminOrganizationNotification } from "@calcom/emails"; +import { sendAdminOrganizationNotification } from "@calcom/emails/organization-email-service"; import { FeaturesRepository } from "@calcom/features/flags/features.repository"; import { UserRepository } from "@calcom/features/users/repositories/UserRepository"; import { diff --git a/packages/features/ee/organizations/lib/service/onboarding/BaseOnboardingService.ts b/packages/features/ee/organizations/lib/service/onboarding/BaseOnboardingService.ts index ea90348212..362f39fae5 100644 --- a/packages/features/ee/organizations/lib/service/onboarding/BaseOnboardingService.ts +++ b/packages/features/ee/organizations/lib/service/onboarding/BaseOnboardingService.ts @@ -1,6 +1,6 @@ import type { TFunction } from "i18next"; -import { sendOrganizationCreationEmail } from "@calcom/emails/email-manager"; +import { sendOrganizationCreationEmail } from "@calcom/emails/organization-email-service"; import { sendEmailVerification } from "@calcom/features/auth/lib/verifyEmail"; import { getOrgFullOrigin } from "@calcom/features/ee/organizations/lib/orgDomains"; import { diff --git a/packages/features/ee/payments/api/webhook.ts b/packages/features/ee/payments/api/webhook.ts index dbf145bbbc..28b9d6e1cf 100644 --- a/packages/features/ee/payments/api/webhook.ts +++ b/packages/features/ee/payments/api/webhook.ts @@ -4,7 +4,8 @@ import type Stripe from "stripe"; import { handlePaymentSuccess } from "@calcom/app-store/_utils/payments/handlePaymentSuccess"; import { eventTypeMetaDataSchemaWithTypedApps } from "@calcom/app-store/zod-utils"; -import { sendAttendeeRequestEmailAndSMS, sendOrganizerRequestEmail } from "@calcom/emails"; +import { sendAttendeeRequestEmailAndSMS, sendOrganizerRequestEmail } from "@calcom/emails/email-manager"; +import EventManager, { placeholderCreatedEvent } from "@calcom/features/bookings/lib/EventManager"; import { doesBookingRequireConfirmation } from "@calcom/features/bookings/lib/doesBookingRequireConfirmation"; import { getAllCredentialsIncludeServiceAccountKey } from "@calcom/features/bookings/lib/getAllCredentialsForUsersOnEvent/getAllCredentials"; import { handleConfirmation } from "@calcom/features/bookings/lib/handleConfirmation"; @@ -12,7 +13,6 @@ import { getBooking } from "@calcom/features/bookings/lib/payment/getBooking"; import stripe from "@calcom/features/ee/payments/server/stripe"; import { getPlatformParams } from "@calcom/features/platform-oauth-client/get-platform-params"; import { PlatformOAuthClientRepository } from "@calcom/features/platform-oauth-client/platform-oauth-client.repository"; -import EventManager, { placeholderCreatedEvent } from "@calcom/features/bookings/lib/EventManager"; import { IS_PRODUCTION } from "@calcom/lib/constants"; import { getErrorFromUnknown } from "@calcom/lib/errors"; import { HttpError as HttpCode } from "@calcom/lib/http-error"; diff --git a/packages/features/ee/round-robin/roundRobinManualReassignment.test.ts b/packages/features/ee/round-robin/roundRobinManualReassignment.test.ts index b31d2d8e90..295a9124ef 100644 --- a/packages/features/ee/round-robin/roundRobinManualReassignment.test.ts +++ b/packages/features/ee/round-robin/roundRobinManualReassignment.test.ts @@ -466,7 +466,7 @@ describe("roundRobinManualReassignment test", () => { await mockEventManagerReschedule(); const sendRoundRobinReassignedEmailsAndSMSSpy = vi.spyOn( - await import("@calcom/emails"), + await import("@calcom/emails/email-manager"), "sendRoundRobinReassignedEmailsAndSMS" ); diff --git a/packages/features/ee/round-robin/roundRobinManualReassignment.ts b/packages/features/ee/round-robin/roundRobinManualReassignment.ts index 99e96fd541..e7f9d3f74e 100644 --- a/packages/features/ee/round-robin/roundRobinManualReassignment.ts +++ b/packages/features/ee/round-robin/roundRobinManualReassignment.ts @@ -1,7 +1,5 @@ import { cloneDeep } from "lodash"; - - import { enrichUserWithDelegationCredentialsIncludeServiceAccountKey } from "@calcom/app-store/delegationCredential"; import { eventTypeAppMetadataOptionalSchema } from "@calcom/app-store/zod-utils"; import dayjs from "@calcom/dayjs"; @@ -9,16 +7,21 @@ import { sendRoundRobinReassignedEmailsAndSMS, sendRoundRobinScheduledEmailsAndSMS, sendRoundRobinUpdatedEmailsAndSMS, -} from "@calcom/emails"; +} from "@calcom/emails/email-manager"; import EventManager from "@calcom/features/bookings/lib/EventManager"; import { getAllCredentialsIncludeServiceAccountKey } from "@calcom/features/bookings/lib/getAllCredentialsForUsersOnEvent/getAllCredentials"; import { getBookingResponsesPartialSchema } from "@calcom/features/bookings/lib/getBookingResponsesSchema"; import { getCalEventResponses } from "@calcom/features/bookings/lib/getCalEventResponses"; import { getEventTypesFromDB } from "@calcom/features/bookings/lib/handleNewBooking/getEventTypesFromDB"; import { getBookerBaseUrl } from "@calcom/features/ee/organizations/lib/getBookerUrlServer"; -import AssignmentReasonRecorder, { RRReassignmentType } from "@calcom/features/ee/round-robin/assignmentReason/AssignmentReasonRecorder"; +import AssignmentReasonRecorder, { + RRReassignmentType, +} from "@calcom/features/ee/round-robin/assignmentReason/AssignmentReasonRecorder"; import { BookingLocationService } from "@calcom/features/ee/round-robin/lib/bookingLocationService"; -import { scheduleEmailReminder, deleteScheduledEmailReminder } from "@calcom/features/ee/workflows/lib/reminders/emailReminderManager"; +import { + scheduleEmailReminder, + deleteScheduledEmailReminder, +} from "@calcom/features/ee/workflows/lib/reminders/emailReminderManager"; import { scheduleWorkflowReminders } from "@calcom/features/ee/workflows/lib/reminders/reminderScheduler"; import { getEventName } from "@calcom/features/eventtypes/lib/eventNaming"; import { getVideoCallUrlFromCalEvent } from "@calcom/lib/CalEventParser"; @@ -33,15 +36,12 @@ import { WorkflowActions, WorkflowMethods, WorkflowTriggerEvents } from "@calcom import type { EventTypeMetadata, PlatformClientParams } from "@calcom/prisma/zod-utils"; import type { CalendarEvent } from "@calcom/types/Calendar"; - - import { handleRescheduleEventManager } from "./handleRescheduleEventManager"; import type { BookingSelectResult } from "./utils/bookingSelect"; import { bookingSelect } from "./utils/bookingSelect"; import { getDestinationCalendar } from "./utils/getDestinationCalendar"; import { getTeamMembers } from "./utils/getTeamMembers"; - enum ErrorCode { InvalidRoundRobinHost = "invalid_round_robin_host", UserIsFixed = "user_is_round_robin_fixed", @@ -615,4 +615,4 @@ export async function handleWorkflowsUpdate({ }); } -export default roundRobinManualReassignment; \ No newline at end of file +export default roundRobinManualReassignment; diff --git a/packages/features/ee/round-robin/roundRobinReassignment.ts b/packages/features/ee/round-robin/roundRobinReassignment.ts index 1c60f8a95f..5b97357ab9 100644 --- a/packages/features/ee/round-robin/roundRobinReassignment.ts +++ b/packages/features/ee/round-robin/roundRobinReassignment.ts @@ -1,7 +1,5 @@ import { cloneDeep } from "lodash"; - - import { enrichHostsWithDelegationCredentials, enrichUserWithDelegationCredentialsIncludeServiceAccountKey, @@ -13,7 +11,7 @@ import { sendRoundRobinReassignedEmailsAndSMS, sendRoundRobinScheduledEmailsAndSMS, sendRoundRobinUpdatedEmailsAndSMS, -} from "@calcom/emails"; +} from "@calcom/emails/email-manager"; import EventManager from "@calcom/features/bookings/lib/EventManager"; import { getAllCredentialsIncludeServiceAccountKey } from "@calcom/features/bookings/lib/getAllCredentialsForUsersOnEvent/getAllCredentials"; import { getBookingResponsesPartialSchema } from "@calcom/features/bookings/lib/getBookingResponsesSchema"; @@ -519,4 +517,4 @@ export const roundRobinReassignment = async ({ }; }; -export default roundRobinReassignment; \ No newline at end of file +export default roundRobinReassignment; diff --git a/packages/features/ee/workflows/lib/reminders/providers/emailProvider.ts b/packages/features/ee/workflows/lib/reminders/providers/emailProvider.ts index a1766bae1b..d306ae236b 100644 --- a/packages/features/ee/workflows/lib/reminders/providers/emailProvider.ts +++ b/packages/features/ee/workflows/lib/reminders/providers/emailProvider.ts @@ -1,4 +1,4 @@ -import { sendCustomWorkflowEmail } from "@calcom/emails"; +import { sendCustomWorkflowEmail } from "@calcom/emails/workflow-email-service"; import type { WorkflowEmailData } from "@calcom/emails/templates/workflow-email"; import tasker from "@calcom/features/tasker"; diff --git a/packages/features/tasker/tasker.ts b/packages/features/tasker/tasker.ts index 4808c17cfd..12f57e930c 100644 --- a/packages/features/tasker/tasker.ts +++ b/packages/features/tasker/tasker.ts @@ -2,7 +2,6 @@ import type { z } from "zod"; export type TaskerTypes = "internal" | "redis"; type TaskPayloads = { - sendEmail: string; sendWebhook: string; sendSms: string; triggerHostNoShowWebhook: z.infer< diff --git a/packages/features/tasker/tasks/index.ts b/packages/features/tasker/tasks/index.ts index 640caa3dee..9ae6b79788 100644 --- a/packages/features/tasker/tasks/index.ts +++ b/packages/features/tasker/tasks/index.ts @@ -8,7 +8,6 @@ import type { TaskHandler, TaskTypes } from "../tasker"; * The task handlers are imported dynamically to avoid circular dependencies. */ const tasks: Record Promise> = { - sendEmail: () => import("./sendEmail").then((module) => module.sendEmail), sendWebhook: () => import("./sendWebook").then((module) => module.sendWebhook), triggerHostNoShowWebhook: () => import("./triggerNoShow/triggerHostNoShow").then((module) => module.triggerHostNoShow), diff --git a/packages/features/tasker/tasks/sendEmail.ts b/packages/features/tasker/tasks/sendEmail.ts deleted file mode 100644 index 1634411d47..0000000000 --- a/packages/features/tasker/tasks/sendEmail.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { z } from "zod"; - -const sendEmailPayloadSchema = z.object({ - /** */ - to: z.string(), - /** The email template to send */ - template: z.string(), - payload: z.string(), -}); - -export async function sendEmail(payload: string): Promise { - try { - const parsedPayload = sendEmailPayloadSchema.parse(JSON.parse(payload)); - console.log(parsedPayload); - const emails = await import("@calcom/emails"); - const email = emails[parsedPayload.template as keyof typeof emails]; - if (!email) throw new Error("Invalid email template"); - // @ts-expect-error - TODO bring back email type safety - await email(parsedPayload.to); - } catch (error) { - // ... handle error - console.error(error); - throw error; - } -} diff --git a/packages/features/tasker/tasks/sendWorkflowEmails.ts b/packages/features/tasker/tasks/sendWorkflowEmails.ts index 62cbf93427..11c54889a0 100644 --- a/packages/features/tasker/tasks/sendWorkflowEmails.ts +++ b/packages/features/tasker/tasks/sendWorkflowEmails.ts @@ -1,6 +1,6 @@ import { z } from "zod"; -import { sendCustomWorkflowEmail } from "@calcom/emails"; +import { sendCustomWorkflowEmail } from "@calcom/emails/workflow-email-service"; export const ZSendWorkflowEmailsSchema = z.object({ to: z.array(z.string()), diff --git a/packages/trpc/server/routers/loggedInViewer/connectAndJoin.handler.ts b/packages/trpc/server/routers/loggedInViewer/connectAndJoin.handler.ts index 2f12e57a1a..7f79f4a6de 100644 --- a/packages/trpc/server/routers/loggedInViewer/connectAndJoin.handler.ts +++ b/packages/trpc/server/routers/loggedInViewer/connectAndJoin.handler.ts @@ -1,4 +1,4 @@ -import { sendScheduledEmailsAndSMS } from "@calcom/emails"; +import { sendScheduledEmailsAndSMS } from "@calcom/emails/email-manager"; import { getCalEventResponses } from "@calcom/features/bookings/lib/getCalEventResponses"; import { scheduleNoShowTriggers } from "@calcom/features/bookings/lib/handleNewBooking/scheduleNoShowTriggers"; import { isPrismaObjOrUndefined } from "@calcom/lib/isPrismaObj"; @@ -213,7 +213,7 @@ export const Handler = async ({ ctx, input }: Options) => { eventTypeId: eventType?.id, videoCallData, customReplyToEmail: eventType?.customReplyToEmail, - team: !!updatedBooking.eventType?.team + team: updatedBooking.eventType?.team ? { name: updatedBooking.eventType.team.name, id: updatedBooking.eventType.team.id, diff --git a/packages/trpc/server/routers/viewer/admin/sendPasswordReset.handler.ts b/packages/trpc/server/routers/viewer/admin/sendPasswordReset.handler.ts index 52ca019396..705a344d50 100644 --- a/packages/trpc/server/routers/viewer/admin/sendPasswordReset.handler.ts +++ b/packages/trpc/server/routers/viewer/admin/sendPasswordReset.handler.ts @@ -1,5 +1,5 @@ import dayjs from "@calcom/dayjs"; -import { sendPasswordResetEmail } from "@calcom/emails"; +import { sendPasswordResetEmail } from "@calcom/emails/auth-email-service"; import { PASSWORD_RESET_EXPIRY_HOURS } from "@calcom/features/auth/lib/passwordResetRequest"; import { getTranslation } from "@calcom/lib/server/i18n"; import { prisma } from "@calcom/prisma"; diff --git a/packages/trpc/server/routers/viewer/apps/toggle.handler.ts b/packages/trpc/server/routers/viewer/apps/toggle.handler.ts index 52c6388843..310e7da6fd 100644 --- a/packages/trpc/server/routers/viewer/apps/toggle.handler.ts +++ b/packages/trpc/server/routers/viewer/apps/toggle.handler.ts @@ -1,5 +1,5 @@ import { getLocalAppMetadata } from "@calcom/app-store/utils"; -import { sendDisabledAppEmail } from "@calcom/emails"; +import { sendDisabledAppEmail } from "@calcom/emails/integration-email-service"; import { getTranslation } from "@calcom/lib/server/i18n"; import type { PrismaClient } from "@calcom/prisma"; import { AppCategories } from "@calcom/prisma/enums"; diff --git a/packages/trpc/server/routers/viewer/bookings/confirm.handler.ts b/packages/trpc/server/routers/viewer/bookings/confirm.handler.ts index 2dadd50a4b..8fd25f63e9 100644 --- a/packages/trpc/server/routers/viewer/bookings/confirm.handler.ts +++ b/packages/trpc/server/routers/viewer/bookings/confirm.handler.ts @@ -1,7 +1,7 @@ import { getUsersCredentialsIncludeServiceAccountKey } from "@calcom/app-store/delegationCredential"; import type { LocationObject } from "@calcom/app-store/locations"; import { getLocationValueForDB } from "@calcom/app-store/locations"; -import { sendDeclinedEmailsAndSMS } from "@calcom/emails"; +import { sendDeclinedEmailsAndSMS } from "@calcom/emails/email-manager"; import { getAllCredentialsIncludeServiceAccountKey } from "@calcom/features/bookings/lib/getAllCredentialsForUsersOnEvent/getAllCredentials"; import { getCalEventResponses } from "@calcom/features/bookings/lib/getCalEventResponses"; import { handleConfirmation } from "@calcom/features/bookings/lib/handleConfirmation"; diff --git a/packages/trpc/server/routers/viewer/bookings/editLocation.handler.ts b/packages/trpc/server/routers/viewer/bookings/editLocation.handler.ts index 94a61f29d3..52f840d095 100644 --- a/packages/trpc/server/routers/viewer/bookings/editLocation.handler.ts +++ b/packages/trpc/server/routers/viewer/bookings/editLocation.handler.ts @@ -3,7 +3,7 @@ import type { z } from "zod"; import { getUsersCredentialsIncludeServiceAccountKey } from "@calcom/app-store/delegationCredential"; import { getEventLocationType, OrganizerDefaultConferencingAppType } from "@calcom/app-store/locations"; import { getAppFromSlug } from "@calcom/app-store/utils"; -import { sendLocationChangeEmailsAndSMS } from "@calcom/emails"; +import { sendLocationChangeEmailsAndSMS } from "@calcom/emails/email-manager"; import EventManager from "@calcom/features/bookings/lib/EventManager"; import { BookingRepository } from "@calcom/features/bookings/repositories/BookingRepository"; import { CredentialRepository } from "@calcom/features/credentials/repositories/CredentialRepository"; diff --git a/packages/trpc/server/routers/viewer/bookings/requestReschedule.handler.ts b/packages/trpc/server/routers/viewer/bookings/requestReschedule.handler.ts index 0168c20931..53e082b7c5 100644 --- a/packages/trpc/server/routers/viewer/bookings/requestReschedule.handler.ts +++ b/packages/trpc/server/routers/viewer/bookings/requestReschedule.handler.ts @@ -4,7 +4,7 @@ import { getCalendar } from "@calcom/app-store/_utils/getCalendar"; import { getDelegationCredentialOrRegularCredential } from "@calcom/app-store/delegationCredential"; import { getUsersCredentialsIncludeServiceAccountKey } from "@calcom/app-store/delegationCredential"; import dayjs from "@calcom/dayjs"; -import { sendRequestRescheduleEmailAndSMS } from "@calcom/emails"; +import { sendRequestRescheduleEmailAndSMS } from "@calcom/emails/email-manager"; import { getCalEventResponses } from "@calcom/features/bookings/lib/getCalEventResponses"; import { deleteMeeting } from "@calcom/features/conferencing/lib/videoClient"; import { getBookerBaseUrl } from "@calcom/features/ee/organizations/lib/getBookerUrlServer"; @@ -220,7 +220,9 @@ export const requestRescheduleHandler = async ({ ctx, input }: RequestReschedule const director = new CalendarEventDirector(); director.setBuilder(builder); director.setExistingBooking(bookingToReschedule); - cancellationReason && director.setCancellationReason(cancellationReason); + if (cancellationReason) { + director.setCancellationReason(cancellationReason); + } if (Object.keys(event).length) { // Request Reschedule flow first cancels the booking and then reschedule email is sent. So, we need to allow reschedule for cancelled booking await director.buildForRescheduleEmail({ allowRescheduleForCancelledBooking: true }); diff --git a/packages/trpc/server/routers/viewer/delegationCredential/toggleEnabled.handler.ts b/packages/trpc/server/routers/viewer/delegationCredential/toggleEnabled.handler.ts index 9dd32a3c03..a66712d493 100644 --- a/packages/trpc/server/routers/viewer/delegationCredential/toggleEnabled.handler.ts +++ b/packages/trpc/server/routers/viewer/delegationCredential/toggleEnabled.handler.ts @@ -1,7 +1,7 @@ import type { z } from "zod"; import { checkIfSuccessfullyConfiguredInWorkspace } from "@calcom/app-store/delegationCredential"; -import { sendDelegationCredentialDisabledEmail } from "@calcom/emails/email-manager"; +import { sendDelegationCredentialDisabledEmail } from "@calcom/emails/integration-email-service"; import { DelegationCredentialRepository } from "@calcom/features/delegation-credentials/repositories/DelegationCredentialRepository"; import type { ServiceAccountKey } from "@calcom/features/delegation-credentials/repositories/DelegationCredentialRepository"; import logger from "@calcom/lib/logger"; diff --git a/packages/trpc/server/routers/viewer/ooo/outOfOfficeCreateOrUpdate.handler.ts b/packages/trpc/server/routers/viewer/ooo/outOfOfficeCreateOrUpdate.handler.ts index 480d1d7976..996ee39f29 100644 --- a/packages/trpc/server/routers/viewer/ooo/outOfOfficeCreateOrUpdate.handler.ts +++ b/packages/trpc/server/routers/viewer/ooo/outOfOfficeCreateOrUpdate.handler.ts @@ -2,7 +2,7 @@ import { v4 as uuidv4 } from "uuid"; import { selectOOOEntries } from "@calcom/app-store/zapier/api/subscriptions/listOOOEntries"; import dayjs from "@calcom/dayjs"; -import { sendBookingRedirectNotification } from "@calcom/emails"; +import { sendBookingRedirectNotification } from "@calcom/emails/workflow-email-service"; import type { GetSubscriberOptions } from "@calcom/features/webhooks/lib/getWebhooks"; import getWebhooks from "@calcom/features/webhooks/lib/getWebhooks"; import type { OOOEntryPayloadType } from "@calcom/features/webhooks/lib/sendPayload"; @@ -47,7 +47,7 @@ export const outOfOfficeCreateOrUpdate = async ({ ctx, input }: TBookingRedirect let oooUserFullName = ctx.user.name; let isAdmin; - if (!!input.forUserId) { + if (input.forUserId) { isAdmin = await isAdminForUser(ctx.user.id, input.forUserId); if (!isAdmin) { throw new TRPCError({ code: "NOT_FOUND", message: "only_admin_can_create_ooo" }); diff --git a/packages/trpc/server/routers/viewer/ooo/outOfOfficeEntryDelete.handler.ts b/packages/trpc/server/routers/viewer/ooo/outOfOfficeEntryDelete.handler.ts index 202c6793ff..22ad598ad8 100644 --- a/packages/trpc/server/routers/viewer/ooo/outOfOfficeEntryDelete.handler.ts +++ b/packages/trpc/server/routers/viewer/ooo/outOfOfficeEntryDelete.handler.ts @@ -1,4 +1,4 @@ -import { sendBookingRedirectNotification } from "@calcom/emails"; +import { sendBookingRedirectNotification } from "@calcom/emails/workflow-email-service"; import { getTranslation } from "@calcom/lib/server/i18n"; import prisma from "@calcom/prisma"; import type { TrpcSessionUser } from "@calcom/trpc/server/types"; diff --git a/packages/trpc/server/routers/viewer/organizations/create.handler.ts b/packages/trpc/server/routers/viewer/organizations/create.handler.ts index 062c18181a..5bf471e76c 100644 --- a/packages/trpc/server/routers/viewer/organizations/create.handler.ts +++ b/packages/trpc/server/routers/viewer/organizations/create.handler.ts @@ -2,7 +2,7 @@ import { lookup } from "dns"; import { getOrgFullOrigin } from "@calcom/ee/organizations/lib/orgDomains"; import { isNotACompanyEmail } from "@calcom/ee/organizations/lib/server/orgCreationUtils"; -import { sendAdminOrganizationNotification, sendOrganizationCreationEmail } from "@calcom/emails"; +import { sendAdminOrganizationNotification, sendOrganizationCreationEmail } from "@calcom/emails/organization-email-service"; import { sendEmailVerification } from "@calcom/features/auth/lib/verifyEmail"; import { getOrganizationRepository } from "@calcom/features/ee/organizations/di/OrganizationRepository.container"; import { UserRepository } from "@calcom/features/users/repositories/UserRepository"; diff --git a/packages/trpc/server/routers/viewer/slots/handleNotificationWhenNoSlots.test.ts b/packages/trpc/server/routers/viewer/slots/handleNotificationWhenNoSlots.test.ts index 3531c2a38c..41943baa83 100644 --- a/packages/trpc/server/routers/viewer/slots/handleNotificationWhenNoSlots.test.ts +++ b/packages/trpc/server/routers/viewer/slots/handleNotificationWhenNoSlots.test.ts @@ -4,7 +4,7 @@ import prismaMock from "../../../../../../tests/libs/__mocks__/prismaMock"; import { vi, describe, it, beforeAll, afterAll, expect, beforeEach, afterEach } from "vitest"; import dayjs from "@calcom/dayjs"; -import * as CalcomEmails from "@calcom/emails"; +import * as CalcomEmails from "@calcom/emails/organization-email-service"; import { getNoSlotsNotificationService } from "@calcom/features/di/containers/NoSlotsNotification"; import { RedisService } from "@calcom/features/redis/RedisService"; diff --git a/packages/trpc/server/routers/viewer/slots/handleNotificationWhenNoSlots.ts b/packages/trpc/server/routers/viewer/slots/handleNotificationWhenNoSlots.ts index c94c6324f5..be23930e21 100644 --- a/packages/trpc/server/routers/viewer/slots/handleNotificationWhenNoSlots.ts +++ b/packages/trpc/server/routers/viewer/slots/handleNotificationWhenNoSlots.ts @@ -1,5 +1,5 @@ import type { Dayjs } from "@calcom/dayjs"; -import { sendOrganizationAdminNoSlotsNotification } from "@calcom/emails"; +import { sendOrganizationAdminNoSlotsNotification } from "@calcom/emails/organization-email-service"; import type { TeamRepository } from "@calcom/features/ee/teams/repositories/TeamRepository"; import type { MembershipRepository } from "@calcom/features/membership/repositories/MembershipRepository"; import type { IRedisService } from "@calcom/features/redis/IRedisService"; diff --git a/packages/trpc/server/routers/viewer/teams/inviteMember/inviteMember.handler.integration-test.ts b/packages/trpc/server/routers/viewer/teams/inviteMember/inviteMember.handler.integration-test.ts index ddfdebf7f8..dd5262f5a4 100644 --- a/packages/trpc/server/routers/viewer/teams/inviteMember/inviteMember.handler.integration-test.ts +++ b/packages/trpc/server/routers/viewer/teams/inviteMember/inviteMember.handler.integration-test.ts @@ -30,8 +30,6 @@ async function verifyMembershipExists(userId: number, teamId: number): Promise ({ })); // Mock email sending -vi.mock("@calcom/emails", () => ({ +vi.mock("@calcom/emails/organization-email-service", () => ({ sendTeamInviteEmail: vi.fn(() => Promise.resolve()), sendOrganizationAutoJoinEmail: vi.fn(() => Promise.resolve()), })); @@ -401,10 +399,7 @@ describe("inviteMember.handler Integration Tests", () => { ], }); - await verifyMembershipExists( - unverifiedUserWithUnacceptedMembership.id, - organization.id - ); + await verifyMembershipExists(unverifiedUserWithUnacceptedMembership.id, organization.id); const profile = await verifyProfileExists(unverifiedUserWithUnacceptedMembership.id, organization.id); expect(profile?.userId).toBe(unverifiedUserWithUnacceptedMembership.id); @@ -503,7 +498,7 @@ describe("inviteMember.handler Integration Tests", () => { where: { email: newUserEmail }, }); expect(createdUser).toBeTruthy(); - + if (createdUser) { trackUser(createdUser); diff --git a/packages/trpc/server/routers/viewer/teams/inviteMember/utils.ts b/packages/trpc/server/routers/viewer/teams/inviteMember/utils.ts index e759e0ac64..d00bf42f9c 100644 --- a/packages/trpc/server/routers/viewer/teams/inviteMember/utils.ts +++ b/packages/trpc/server/routers/viewer/teams/inviteMember/utils.ts @@ -2,7 +2,7 @@ import { randomBytes } from "crypto"; import type { TFunction } from "i18next"; import { getOrgFullOrigin } from "@calcom/ee/organizations/lib/orgDomains"; -import { sendTeamInviteEmail } from "@calcom/emails"; +import { sendTeamInviteEmail } from "@calcom/emails/organization-email-service"; import { checkAdminOrOwner } from "@calcom/features/auth/lib/checkAdminOrOwner"; import { updateNewTeamMemberEventTypes } from "@calcom/features/ee/teams/lib/queries"; import { PermissionCheckService } from "@calcom/features/pbac/services/permission-check.service"; diff --git a/packages/trpc/server/routers/viewer/teams/resendInvitation.handler.ts b/packages/trpc/server/routers/viewer/teams/resendInvitation.handler.ts index 7a18ae596e..216f99a7a2 100644 --- a/packages/trpc/server/routers/viewer/teams/resendInvitation.handler.ts +++ b/packages/trpc/server/routers/viewer/teams/resendInvitation.handler.ts @@ -1,4 +1,4 @@ -import { sendTeamInviteEmail } from "@calcom/emails"; +import { sendTeamInviteEmail } from "@calcom/emails/organization-email-service"; import { UserRepository } from "@calcom/features/users/repositories/UserRepository"; import { OnboardingPathService } from "@calcom/features/onboarding/lib/onboarding-path.service"; import { WEBAPP_URL } from "@calcom/lib/constants"; diff --git a/setupVitest.ts b/setupVitest.ts index 91d20c1c8c..5d7e1f65e7 100644 --- a/setupVitest.ts +++ b/setupVitest.ts @@ -2,6 +2,7 @@ import matchers from "@testing-library/jest-dom/matchers"; import ResizeObserver from "resize-observer-polyfill"; import { vi, expect } from "vitest"; import createFetchMock from "vitest-fetch-mock"; + import type { CalendarService } from "@calcom/types/Calendar"; global.ResizeObserver = ResizeObserver; @@ -61,9 +62,9 @@ class MockPaymentService { constructor(credentials?: any) { this.credentials = credentials; } - + private credentials: any; - + async create( payment: any, bookingId: number, @@ -78,7 +79,7 @@ class MockPaymentService { ) { const { default: prismaMock } = await import("./tests/libs/__mocks__/prisma"); const externalId = "mock_payment_external_id"; - + const paymentCreateData = { uid: MOCK_PAYMENT_UID, appId: null, @@ -96,7 +97,7 @@ class MockPaymentService { const createdPayment = await prismaMock.payment.create({ data: paymentCreateData, }); - + return createdPayment; } async collectCard() { @@ -112,7 +113,7 @@ class MockPaymentService { return { success: true }; } async afterPayment(event: any, booking: any, paymentData: any) { - const { sendAwaitingPaymentEmailAndSMS } = await import("@calcom/emails"); + const { sendAwaitingPaymentEmailAndSMS } = await import("@calcom/emails/email-manager"); await sendAwaitingPaymentEmailAndSMS({ ...event, paymentInfo: {