From 513f54f4fe179d9b88542d4b4ff20f6931f3e608 Mon Sep 17 00:00:00 2001
From: Anik Dhabal Babu <81948346+anikdhabal@users.noreply.github.com>
Date: Thu, 24 Apr 2025 01:28:29 +0530
Subject: [PATCH] feat: ability to hide organizer email (#20782)
* feat: hide orgainzer email
* fix type error
* update
* update
* Update schema.prisma
---
apps/web/lib/booking.ts | 1 +
...ookings-single-view.getServerSideProps.tsx | 1 +
.../bookings/views/bookings-single-view.tsx | 8 +--
apps/web/public/static/locales/en/common.json | 2 +
packages/app-store/vital/lib/reschedule.ts | 1 +
.../wipemycalother/lib/reschedule.ts | 2 +
packages/emails/lib/generateIcsString.test.ts | 54 +++++++++++++++++--
packages/emails/lib/generateIcsString.ts | 6 ++-
packages/emails/src/components/WhoInfo.tsx | 2 +-
packages/features/CalendarEventBuilder.ts | 2 +
.../bookings/lib/getBookingToDelete.ts | 1 +
.../features/bookings/lib/getUserBooking.ts | 1 +
.../bookings/lib/handleCancelBooking.ts | 1 +
.../features/bookings/lib/handleNewBooking.ts | 1 +
.../handleNewBooking/getEventTypesFromDB.ts | 1 +
.../credentials/handleDeleteCredential.ts | 2 +
.../roundRobinManualReassignment.ts | 1 +
.../ee/round-robin/roundRobinReassignment.ts | 1 +
.../lib/reminders/smsReminderManager.ts | 1 +
.../tabs/advanced/EventAdvancedTab.tsx | 22 ++++++++
packages/lib/CalEventParser.ts | 6 ++-
packages/lib/buildCalEventFromBooking.ts | 2 +
packages/lib/builders/CalendarEvent/class.ts | 1 +
packages/lib/defaultEvents.ts | 1 +
packages/lib/payment/getBooking.ts | 2 +
packages/lib/server/eventTypeSelect.ts | 1 +
packages/lib/server/repository/booking.ts | 1 +
packages/lib/server/repository/eventType.ts | 2 +
packages/lib/test/builder.ts | 1 +
.../event-types/hooks/useEventTypeForm.ts | 1 +
.../migration.sql | 2 +
packages/prisma/schema.prisma | 1 +
packages/prisma/zod-utils.ts | 1 +
.../loggedInViewer/connectAndJoin.handler.ts | 2 +
.../viewer/bookings/addGuests.handler.ts | 1 +
.../viewer/bookings/confirm.handler.ts | 2 +
.../routers/viewer/bookings/get.handler.ts | 1 +
.../bookings/requestReschedule.handler.ts | 1 +
.../viewer/payments/chargeCard.handler.ts | 1 +
.../workflows/activateEventType.handler.ts | 2 +
.../server/routers/viewer/workflows/util.ts | 2 +
packages/types/Calendar.d.ts | 1 +
42 files changed, 136 insertions(+), 10 deletions(-)
create mode 100644 packages/prisma/migrations/20250420171801_hide_organizer_email/migration.sql
diff --git a/apps/web/lib/booking.ts b/apps/web/lib/booking.ts
index d138fa1a64..608660aff0 100644
--- a/apps/web/lib/booking.ts
+++ b/apps/web/lib/booking.ts
@@ -39,6 +39,7 @@ export const getEventTypesFromDB = async (id: number) => {
currency: true,
bookingFields: true,
allowReschedulingPastBookings: true,
+ hideOrganizerEmail: true,
disableCancelling: true,
disableRescheduling: true,
disableGuests: true,
diff --git a/apps/web/modules/bookings/views/bookings-single-view.getServerSideProps.tsx b/apps/web/modules/bookings/views/bookings-single-view.getServerSideProps.tsx
index 0997b4655b..c338799f80 100644
--- a/apps/web/modules/bookings/views/bookings-single-view.getServerSideProps.tsx
+++ b/apps/web/modules/bookings/views/bookings-single-view.getServerSideProps.tsx
@@ -132,6 +132,7 @@ export async function getServerSideProps(context: GetServerSidePropsContext) {
metadata: eventTypeMetaDataSchemaWithTypedApps.parse(eventTypeRaw.metadata),
recurringEvent: parseRecurringEvent(eventTypeRaw.recurringEvent),
customInputs: customInputSchema.array().parse(eventTypeRaw.customInputs),
+ hideOrganizerEmail: eventTypeRaw.hideOrganizerEmail,
bookingFields: eventTypeRaw.bookingFields.map((field) => {
return {
...field,
diff --git a/apps/web/modules/bookings/views/bookings-single-view.tsx b/apps/web/modules/bookings/views/bookings-single-view.tsx
index ffd6b55fae..59a9c60406 100644
--- a/apps/web/modules/bookings/views/bookings-single-view.tsx
+++ b/apps/web/modules/bookings/views/bookings-single-view.tsx
@@ -602,9 +602,11 @@ export default function Success(props: PageProps) {
{t("Host")}
-
- {bookingInfo?.userPrimaryEmail ?? bookingInfo.user.email}
-
+ {!bookingInfo.eventType?.hideOrganizerEmail && (
+
+ {bookingInfo?.userPrimaryEmail ?? bookingInfo.user.email}
+
+ )}
)}
{bookingInfo?.attendees.map((attendee) => (
diff --git a/apps/web/public/static/locales/en/common.json b/apps/web/public/static/locales/en/common.json
index b2c178942f..1949e5c654 100644
--- a/apps/web/public/static/locales/en/common.json
+++ b/apps/web/public/static/locales/en/common.json
@@ -3140,6 +3140,8 @@
"meeting_satisfaction_question": "How satisfied were you with your recent meeting?",
"meeting_not_joined_question": "didn't join the meeting?",
"reschedule_cta_short": "Reschedule here.",
+ "hide_organizer_email": "Hide organizer's email",
+ "hide_organizer_email_description": "Hide organizer's email address from the booking screen, email notifications, and calendar events",
"you_and_conjunction": "You &",
"select_verified_email": "Select a verified email",
"custom_reply_to_email_title": "Custom 'Reply-To' email",
diff --git a/packages/app-store/vital/lib/reschedule.ts b/packages/app-store/vital/lib/reschedule.ts
index 285ff44afa..4de1595efe 100644
--- a/packages/app-store/vital/lib/reschedule.ts
+++ b/packages/app-store/vital/lib/reschedule.ts
@@ -118,6 +118,7 @@ const Reschedule = async (bookingUid: string, cancellationReason: string) => {
tAttendees
),
organizer: userOwnerAsPeopleType,
+ hideOrganizerEmail: bookingToReschedule.eventType?.hideOrganizerEmail,
team: !!bookingToReschedule.eventType?.team
? {
name: bookingToReschedule.eventType.team.name,
diff --git a/packages/app-store/wipemycalother/lib/reschedule.ts b/packages/app-store/wipemycalother/lib/reschedule.ts
index 8974608ee7..301061c0bf 100644
--- a/packages/app-store/wipemycalother/lib/reschedule.ts
+++ b/packages/app-store/wipemycalother/lib/reschedule.ts
@@ -35,6 +35,7 @@ const Reschedule = async (bookingUid: string, cancellationReason: string) => {
eventType: {
select: {
metadata: true,
+ hideOrganizerEmail: true,
team: {
select: {
id: true,
@@ -120,6 +121,7 @@ const Reschedule = async (bookingUid: string, cancellationReason: string) => {
tAttendees
),
organizer: userOwnerAsPeopleType,
+ hideOrganizerEmail: bookingToReschedule.eventType?.hideOrganizerEmail,
team: !!bookingToReschedule.eventType?.team
? {
name: bookingToReschedule.eventType.team.name,
diff --git a/packages/emails/lib/generateIcsString.test.ts b/packages/emails/lib/generateIcsString.test.ts
index d271de9e5c..5e1538f075 100644
--- a/packages/emails/lib/generateIcsString.test.ts
+++ b/packages/emails/lib/generateIcsString.test.ts
@@ -31,9 +31,14 @@ const testIcsStringContains = ({
// Sometimes the deeply equal stringMatching error appears. Don't want to add flakey tests
// expect(icsString).toEqual(expect.stringContaining(`SUMMARY:${event.title}`));
expect(icsString).toEqual(expect.stringContaining(`DTSTART:${DTSTART}`));
- expect(icsString).toEqual(
- expect.stringContaining(`ORGANIZER;CN=${event.organizer.name}:mailto:${event.organizer.email}`)
- );
+ if (event.hideOrganizerEmail) {
+ expect(icsString).toEqual(expect.stringContaining(`ORGANIZER;CN=${event.organizer.name}`));
+ expect(icsString).not.toEqual(expect.stringContaining(`mailto:${event.organizer.email}`));
+ } else {
+ expect(icsString).toEqual(
+ expect.stringContaining(`ORGANIZER;CN=${event.organizer.name}:mailto:${event.organizer.email}`)
+ );
+ }
expect(icsString).toEqual(expect.stringContaining(`DTEND:${DTEND}`));
expect(icsString).toEqual(expect.stringContaining(`STATUS:${status}`));
// Getting an error expected icsString to deeply equal stringMatching
@@ -158,4 +163,47 @@ describe("generateIcsString", () => {
expect(icsString).toEqual(expect.stringContaining(`LOCATION:${event.location}`));
});
});
+
+ describe("hidden organizer email", () => {
+ test("when hideOrganizerEmail is true, organizer email should be omitted", () => {
+ const event = buildCalendarEvent({
+ iCalSequence: 0,
+ attendees: [buildPerson()],
+ hideOrganizerEmail: true,
+ });
+ const status = "CONFIRMED";
+
+ const icsString = generateIcsString({
+ event: event,
+ status,
+ });
+
+ const assertedIcsString = assertHasIcsString(icsString);
+
+ expect(assertedIcsString).toEqual(expect.stringContaining(`ORGANIZER;CN=${event.organizer.name}`));
+ expect(assertedIcsString).not.toEqual(
+ expect.stringContaining(`ORGANIZER;CN=${event.organizer.name}:mailto:`)
+ );
+ });
+
+ test("when hideOrganizerEmail is false, organizer email should be included", () => {
+ const event = buildCalendarEvent({
+ iCalSequence: 0,
+ attendees: [buildPerson()],
+ hideOrganizerEmail: false,
+ });
+ const status = "CONFIRMED";
+
+ const icsString = generateIcsString({
+ event: event,
+ status,
+ });
+
+ const assertedIcsString = assertHasIcsString(icsString);
+
+ expect(assertedIcsString).toEqual(
+ expect.stringContaining(`ORGANIZER;CN=${event.organizer.name}:mailto:${event.organizer.email}`)
+ );
+ });
+ });
});
diff --git a/packages/emails/lib/generateIcsString.ts b/packages/emails/lib/generateIcsString.ts
index 1eca2818ff..1541734bec 100644
--- a/packages/emails/lib/generateIcsString.ts
+++ b/packages/emails/lib/generateIcsString.ts
@@ -30,6 +30,7 @@ export type ICSCalendarEvent = Pick<
| "team"
| "type"
| "hideCalendarEventDetails"
+ | "hideOrganizerEmail"
>;
const toICalDateArray = (date: string): DateArray => {
@@ -73,7 +74,10 @@ const generateIcsString = ({
productId: "calcom/ics",
title: event.title,
description: getRichDescription(event, t),
- organizer: { name: event.organizer.name, email: event.organizer.email },
+ organizer: {
+ name: event.organizer.name,
+ ...(event.hideOrganizerEmail ? {} : { email: event.organizer.email }),
+ },
...{ recurrenceRule },
attendees: [
...event.attendees.map((attendee: Person) => ({
diff --git a/packages/emails/src/components/WhoInfo.tsx b/packages/emails/src/components/WhoInfo.tsx
index 27341a1933..5ebde366d9 100644
--- a/packages/emails/src/components/WhoInfo.tsx
+++ b/packages/emails/src/components/WhoInfo.tsx
@@ -33,7 +33,7 @@ export function WhoInfo(props: { calEvent: CalendarEvent; t: TFunction }) {
{props.calEvent.team?.members.map((member) => (
diff --git a/packages/features/CalendarEventBuilder.ts b/packages/features/CalendarEventBuilder.ts
index 447664775f..a4d9bae742 100644
--- a/packages/features/CalendarEventBuilder.ts
+++ b/packages/features/CalendarEventBuilder.ts
@@ -47,6 +47,7 @@ export class CalendarEventBuilder {
id: number;
hideCalendarNotes?: boolean;
hideCalendarEventDetails?: boolean;
+ hideOrganizerEmail?: boolean;
schedulingType?: SchedulingType | null;
seatsPerTimeSlot?: number | null;
seatsShowAttendees?: boolean | null;
@@ -60,6 +61,7 @@ export class CalendarEventBuilder {
eventTypeId: eventType.id,
hideCalendarNotes: eventType.hideCalendarNotes,
hideCalendarEventDetails: eventType.hideCalendarEventDetails,
+ hideOrganizerEmail: eventType.hideOrganizerEmail,
schedulingType: eventType.schedulingType,
seatsPerTimeSlot: eventType.seatsPerTimeSlot,
// if seats are not enabled we should default true
diff --git a/packages/features/bookings/lib/getBookingToDelete.ts b/packages/features/bookings/lib/getBookingToDelete.ts
index ea5d1c1422..590d445075 100644
--- a/packages/features/bookings/lib/getBookingToDelete.ts
+++ b/packages/features/bookings/lib/getBookingToDelete.ts
@@ -73,6 +73,7 @@ export async function getBookingToDelete(id: number | undefined, uid: string | u
bookingFields: true,
seatsShowAttendees: true,
metadata: true,
+ hideOrganizerEmail: true,
schedulingType: true,
customReplyToEmail: true,
hosts: {
diff --git a/packages/features/bookings/lib/getUserBooking.ts b/packages/features/bookings/lib/getUserBooking.ts
index 703486b85e..edb1962a4f 100644
--- a/packages/features/bookings/lib/getUserBooking.ts
+++ b/packages/features/bookings/lib/getUserBooking.ts
@@ -50,6 +50,7 @@ const getUserBooking = async (uid: string) => {
slug: true,
timeZone: true,
schedulingType: true,
+ hideOrganizerEmail: true,
},
},
seatsReferences: {
diff --git a/packages/features/bookings/lib/handleCancelBooking.ts b/packages/features/bookings/lib/handleCancelBooking.ts
index 69fb31c7c1..fd953d6c4d 100644
--- a/packages/features/bookings/lib/handleCancelBooking.ts
+++ b/packages/features/bookings/lib/handleCancelBooking.ts
@@ -265,6 +265,7 @@ async function handler(input: CancelBookingInput) {
platformClientId,
platformRescheduleUrl,
platformCancelUrl,
+ hideOrganizerEmail: bookingToDelete.eventType?.hideOrganizerEmail,
platformBookingUrl,
customReplyToEmail: bookingToDelete.eventType?.customReplyToEmail,
};
diff --git a/packages/features/bookings/lib/handleNewBooking.ts b/packages/features/bookings/lib/handleNewBooking.ts
index 819b0ba679..47c45c312e 100644
--- a/packages/features/bookings/lib/handleNewBooking.ts
+++ b/packages/features/bookings/lib/handleNewBooking.ts
@@ -1019,6 +1019,7 @@ async function handler(
id: eventType.id,
hideCalendarNotes: eventType.hideCalendarNotes,
hideCalendarEventDetails: eventType.hideCalendarEventDetails,
+ hideOrganizerEmail: eventType.hideOrganizerEmail,
schedulingType: eventType.schedulingType,
seatsPerTimeSlot: eventType.seatsPerTimeSlot,
// if seats are not enabled we should default true
diff --git a/packages/features/bookings/lib/handleNewBooking/getEventTypesFromDB.ts b/packages/features/bookings/lib/handleNewBooking/getEventTypesFromDB.ts
index 642a4e1600..277fc2451f 100644
--- a/packages/features/bookings/lib/handleNewBooking/getEventTypesFromDB.ts
+++ b/packages/features/bookings/lib/handleNewBooking/getEventTypesFromDB.ts
@@ -70,6 +70,7 @@ export const getEventTypesFromDB = async (eventTypeId: number) => {
destinationCalendar: true,
hideCalendarNotes: true,
hideCalendarEventDetails: true,
+ hideOrganizerEmail: true,
seatsPerTimeSlot: true,
recurringEvent: true,
seatsShowAttendees: true,
diff --git a/packages/features/credentials/handleDeleteCredential.ts b/packages/features/credentials/handleDeleteCredential.ts
index 40a24ccf4c..b8ced4e222 100644
--- a/packages/features/credentials/handleDeleteCredential.ts
+++ b/packages/features/credentials/handleDeleteCredential.ts
@@ -251,6 +251,7 @@ const handleDeleteCredential = async ({
seatsPerTimeSlot: true,
seatsShowAttendees: true,
eventName: true,
+ hideOrganizerEmail: true,
team: {
select: {
id: true,
@@ -346,6 +347,7 @@ const handleDeleteCredential = async ({
cancellationReason: "Payment method removed by organizer",
seatsPerTimeSlot: booking.eventType?.seatsPerTimeSlot,
seatsShowAttendees: booking.eventType?.seatsShowAttendees,
+ hideOrganizerEmail: booking.eventType?.hideOrganizerEmail,
team: !!booking.eventType?.team
? {
name: booking.eventType.team.name,
diff --git a/packages/features/ee/round-robin/roundRobinManualReassignment.ts b/packages/features/ee/round-robin/roundRobinManualReassignment.ts
index 381837e21d..6e9761db0a 100644
--- a/packages/features/ee/round-robin/roundRobinManualReassignment.ts
+++ b/packages/features/ee/round-robin/roundRobinManualReassignment.ts
@@ -266,6 +266,7 @@ export const roundRobinManualReassignment = async ({
name: eventType.team?.name || "",
id: eventType.team?.id || 0,
},
+ hideOrganizerEmail: eventType.hideOrganizerEmail,
customInputs: isPrismaObjOrUndefined(booking.customInputs),
...getCalEventResponses({
bookingFields: eventType.bookingFields ?? null,
diff --git a/packages/features/ee/round-robin/roundRobinReassignment.ts b/packages/features/ee/round-robin/roundRobinReassignment.ts
index c581baa22d..ae748558dd 100644
--- a/packages/features/ee/round-robin/roundRobinReassignment.ts
+++ b/packages/features/ee/round-robin/roundRobinReassignment.ts
@@ -318,6 +318,7 @@ export const roundRobinReassignment = async ({
bookingFields: eventType?.bookingFields ?? null,
booking,
}),
+ hideOrganizerEmail: eventType.hideOrganizerEmail,
customReplyToEmail: eventType?.customReplyToEmail,
location: bookingLocation,
...(platformClientParams ? platformClientParams : {}),
diff --git a/packages/features/ee/workflows/lib/reminders/smsReminderManager.ts b/packages/features/ee/workflows/lib/reminders/smsReminderManager.ts
index 5598894fcf..2bf1b18153 100644
--- a/packages/features/ee/workflows/lib/reminders/smsReminderManager.ts
+++ b/packages/features/ee/workflows/lib/reminders/smsReminderManager.ts
@@ -64,6 +64,7 @@ export type BookingInfo = {
metadata?: Prisma.JsonValue;
cancellationReason?: string | null;
rescheduleReason?: string | null;
+ hideOrganizerEmail?: boolean;
};
export type ScheduleTextReminderAction = Extract<
diff --git a/packages/features/eventtypes/components/tabs/advanced/EventAdvancedTab.tsx b/packages/features/eventtypes/components/tabs/advanced/EventAdvancedTab.tsx
index 0663bd525c..be706ca4a0 100644
--- a/packages/features/eventtypes/components/tabs/advanced/EventAdvancedTab.tsx
+++ b/packages/features/eventtypes/components/tabs/advanced/EventAdvancedTab.tsx
@@ -95,6 +95,7 @@ export type EventAdvancedTabCustomClassNames = {
seatsInput: InputClassNames;
};
timezoneLock?: SettingsToggleClassNames;
+ hideOrganizerEmail?: SettingsToggleClassNames;
eventTypeColors?: SettingsToggleClassNames & {
warningText?: string;
};
@@ -491,6 +492,7 @@ export const EventAdvancedTab = ({
const lockTimeZoneToggleOnBookingPageLocked = shouldLockDisableProps("lockTimeZoneToggleOnBookingPage");
const multiplePrivateLinksLocked = shouldLockDisableProps("multiplePrivateLinks");
const reschedulingPastBookingsLocked = shouldLockDisableProps("allowReschedulingPastBookings");
+ const hideOrganizerEmailLocked = shouldLockDisableProps("hideOrganizerEmail");
const customReplyToEmailLocked = shouldLockDisableProps("customReplyToEmail");
const disableCancellingLocked = shouldLockDisableProps("disableCancelling");
@@ -971,6 +973,26 @@ export const EventAdvancedTab = ({
>
)}
/>
+ (
+ onChange(e)}
+ data-testid="hide-organizer-email"
+ />
+ )}
+ />
(
diff --git a/packages/lib/CalEventParser.ts b/packages/lib/CalEventParser.ts
index 1578be3410..d4eac36f92 100644
--- a/packages/lib/CalEventParser.ts
+++ b/packages/lib/CalEventParser.ts
@@ -32,7 +32,7 @@ export const getWhen = (
export const getWho = (
calEvent: Pick<
CalendarEvent,
- "attendees" | "seatsPerTimeSlot" | "seatsShowAttendees" | "organizer" | "team"
+ "attendees" | "seatsPerTimeSlot" | "seatsShowAttendees" | "organizer" | "team" | "hideOrganizerEmail"
>,
t: TFunction
) => {
@@ -49,7 +49,9 @@ export const getWho = (
)
.join("\n");
- const organizer = `${calEvent.organizer.name} - ${t("organizer")}\n${calEvent.organizer.email}`;
+ const organizer = calEvent.hideOrganizerEmail
+ ? `${calEvent.organizer.name} - ${t("organizer")}`
+ : `${calEvent.organizer.name} - ${t("organizer")}\n${calEvent.organizer.email}`;
const teamMembers = calEvent.team?.members
? calEvent.team.members
diff --git a/packages/lib/buildCalEventFromBooking.ts b/packages/lib/buildCalEventFromBooking.ts
index 87f4154868..631353ce15 100644
--- a/packages/lib/buildCalEventFromBooking.ts
+++ b/packages/lib/buildCalEventFromBooking.ts
@@ -36,6 +36,7 @@ type EventType = {
recurringEvent: Prisma.JsonValue | null;
seatsPerTimeSlot: number | null;
seatsShowAttendees: boolean | null;
+ hideOrganizerEmail: boolean;
customReplyToEmail: string | null;
};
@@ -94,6 +95,7 @@ export const buildCalEventFromBooking = async ({
language: { translate: tOrganizer, locale: organizer.locale ?? "en" },
},
attendees: attendeesList,
+ hideOrganizerEmail: booking.eventType?.hideOrganizerEmail,
uid: booking.uid,
recurringEvent: parseRecurringEvent(booking.eventType?.recurringEvent),
location,
diff --git a/packages/lib/builders/CalendarEvent/class.ts b/packages/lib/builders/CalendarEvent/class.ts
index a4bd03c949..1ef147a3ad 100644
--- a/packages/lib/builders/CalendarEvent/class.ts
+++ b/packages/lib/builders/CalendarEvent/class.ts
@@ -31,6 +31,7 @@ class CalendarEventClass implements CalendarEvent {
rejectionReason?: string | null;
hideCalendarNotes?: boolean;
hideCalendarEventDetails?: boolean;
+ hideOrganizerEmail?: boolean;
additionalNotes?: string | null | undefined;
recurrence?: string;
iCalUID?: string | null;
diff --git a/packages/lib/defaultEvents.ts b/packages/lib/defaultEvents.ts
index 6e41bee897..2201f054fe 100644
--- a/packages/lib/defaultEvents.ts
+++ b/packages/lib/defaultEvents.ts
@@ -92,6 +92,7 @@ const commons = {
disableRescheduling: false,
onlyShowFirstAvailableSlot: false,
allowReschedulingPastBookings: false,
+ hideOrganizerEmail: false,
id: 0,
hideCalendarNotes: false,
hideCalendarEventDetails: false,
diff --git a/packages/lib/payment/getBooking.ts b/packages/lib/payment/getBooking.ts
index 92fad9676f..c50444cb9e 100644
--- a/packages/lib/payment/getBooking.ts
+++ b/packages/lib/payment/getBooking.ts
@@ -41,6 +41,7 @@ export async function getBooking(bookingId: number) {
length: true,
price: true,
requiresConfirmation: true,
+ hideOrganizerEmail: true,
metadata: true,
customReplyToEmail: true,
title: true,
@@ -159,6 +160,7 @@ export async function getBooking(bookingId: number) {
language: { translate: t, locale: user.locale ?? "en" },
id: user.id,
},
+ hideOrganizerEmail: booking.eventType?.hideOrganizerEmail,
team: !!booking.eventType?.team
? {
name: booking.eventType.team.name,
diff --git a/packages/lib/server/eventTypeSelect.ts b/packages/lib/server/eventTypeSelect.ts
index 23dc40c4ef..53ca0cfad2 100644
--- a/packages/lib/server/eventTypeSelect.ts
+++ b/packages/lib/server/eventTypeSelect.ts
@@ -39,6 +39,7 @@ export const eventTypeSelect = Prisma.validator()({
seatsPerTimeSlot: true,
onlyShowFirstAvailableSlot: true,
allowReschedulingPastBookings: true,
+ hideOrganizerEmail: true,
seatsShowAttendees: true,
seatsShowAvailabilityCount: true,
scheduleId: true,
diff --git a/packages/lib/server/repository/booking.ts b/packages/lib/server/repository/booking.ts
index 1ebd0e95c1..4265a42110 100644
--- a/packages/lib/server/repository/booking.ts
+++ b/packages/lib/server/repository/booking.ts
@@ -212,6 +212,7 @@ export class BookingRepository {
requiresConfirmationWillBlockSlot: true,
requiresConfirmation: true,
allowReschedulingPastBookings: true,
+ hideOrganizerEmail: true,
},
},
...(seatedEvent && {
diff --git a/packages/lib/server/repository/eventType.ts b/packages/lib/server/repository/eventType.ts
index e47914fb9f..120574d138 100644
--- a/packages/lib/server/repository/eventType.ts
+++ b/packages/lib/server/repository/eventType.ts
@@ -517,6 +517,7 @@ export class EventTypeRepository {
durationLimits: true,
assignAllTeamMembers: true,
allowReschedulingPastBookings: true,
+ hideOrganizerEmail: true,
assignRRMembersUsingSegment: true,
rrSegmentQueryValue: true,
isRRWeightsEnabled: true,
@@ -803,6 +804,7 @@ export class EventTypeRepository {
periodEndDate: true,
onlyShowFirstAvailableSlot: true,
allowReschedulingPastBookings: true,
+ hideOrganizerEmail: true,
periodCountCalendarDays: true,
rescheduleWithSameRoundRobinHost: true,
periodDays: true,
diff --git a/packages/lib/test/builder.ts b/packages/lib/test/builder.ts
index 6de5be6615..7640d0eba6 100644
--- a/packages/lib/test/builder.ts
+++ b/packages/lib/test/builder.ts
@@ -150,6 +150,7 @@ export const buildEventType = (eventType?: Partial): EventType => {
autoTranslateDescriptionEnabled: false,
useEventLevelSelectedCalendars: false,
allowReschedulingPastBookings: false,
+ hideOrganizerEmail: false,
customReplyToEmail: null,
...eventType,
};
diff --git a/packages/platform/atoms/event-types/hooks/useEventTypeForm.ts b/packages/platform/atoms/event-types/hooks/useEventTypeForm.ts
index 402ac35e7d..a8fe9f9669 100644
--- a/packages/platform/atoms/event-types/hooks/useEventTypeForm.ts
+++ b/packages/platform/atoms/event-types/hooks/useEventTypeForm.ts
@@ -87,6 +87,7 @@ export const useEventTypeForm = ({
slotInterval: eventType.slotInterval,
minimumBookingNotice: eventType.minimumBookingNotice,
allowReschedulingPastBookings: eventType.allowReschedulingPastBookings,
+ hideOrganizerEmail: eventType.hideOrganizerEmail,
metadata: eventType.metadata,
hosts: eventType.hosts.sort((a, b) => sortHosts(a, b, eventType.isRRWeightsEnabled)),
successRedirectUrl: eventType.successRedirectUrl || "",
diff --git a/packages/prisma/migrations/20250420171801_hide_organizer_email/migration.sql b/packages/prisma/migrations/20250420171801_hide_organizer_email/migration.sql
new file mode 100644
index 0000000000..20ad5b0285
--- /dev/null
+++ b/packages/prisma/migrations/20250420171801_hide_organizer_email/migration.sql
@@ -0,0 +1,2 @@
+-- AlterTable
+ALTER TABLE "EventType" ADD COLUMN "hideOrganizerEmail" BOOLEAN NOT NULL DEFAULT false;
diff --git a/packages/prisma/schema.prisma b/packages/prisma/schema.prisma
index 858f419681..22ec5085d8 100644
--- a/packages/prisma/schema.prisma
+++ b/packages/prisma/schema.prisma
@@ -165,6 +165,7 @@ model EventType {
maxLeadThreshold Int?
selectedCalendars SelectedCalendar[]
allowReschedulingPastBookings Boolean @default(false)
+ hideOrganizerEmail Boolean @default(false)
/// @zod.custom(imports.emailSchema)
customReplyToEmail String?
diff --git a/packages/prisma/zod-utils.ts b/packages/prisma/zod-utils.ts
index faeca3b1ee..6fbaafa8dc 100644
--- a/packages/prisma/zod-utils.ts
+++ b/packages/prisma/zod-utils.ts
@@ -665,6 +665,7 @@ export const allManagedEventTypeProps: { [k in keyof Omit {
bookingFields: true,
disableGuests: true,
metadata: true,
+ hideOrganizerEmail: true,
customInputs: true,
parentId: true,
customReplyToEmail: true,
@@ -203,6 +204,7 @@ export const Handler = async ({ ctx, input }: Options) => {
timeFormat: getTimeFormatStringFromUserTimeFormat(user.timeFormat),
language: { translate: tOrganizer, locale: user.locale ?? "en" },
},
+ hideOrganizerEmail: updatedBooking.eventType?.hideOrganizerEmail,
attendees: attendeesList,
location: updatedBooking.location ?? "",
uid: updatedBooking.uid,
diff --git a/packages/trpc/server/routers/viewer/bookings/addGuests.handler.ts b/packages/trpc/server/routers/viewer/bookings/addGuests.handler.ts
index 6a902425b2..fc8419e51a 100644
--- a/packages/trpc/server/routers/viewer/bookings/addGuests.handler.ts
+++ b/packages/trpc/server/routers/viewer/bookings/addGuests.handler.ts
@@ -133,6 +133,7 @@ export const addGuestsHandler = async ({ ctx, input }: AddGuestsOptions) => {
timeZone: organizer.timeZone,
language: { translate: tOrganizer, locale: organizer.locale ?? "en" },
},
+ hideOrganizerEmail: booking.eventType?.hideOrganizerEmail,
attendees: attendeesList,
uid: booking.uid,
recurringEvent: parseRecurringEvent(booking.eventType?.recurringEvent),
diff --git a/packages/trpc/server/routers/viewer/bookings/confirm.handler.ts b/packages/trpc/server/routers/viewer/bookings/confirm.handler.ts
index b3ee8293bd..e05aa51615 100644
--- a/packages/trpc/server/routers/viewer/bookings/confirm.handler.ts
+++ b/packages/trpc/server/routers/viewer/bookings/confirm.handler.ts
@@ -80,6 +80,7 @@ export const confirmHandler = async ({ ctx, input }: ConfirmOptions) => {
description: true,
price: true,
bookingFields: true,
+ hideOrganizerEmail: true,
disableGuests: true,
customReplyToEmail: true,
metadata: true,
@@ -226,6 +227,7 @@ export const confirmHandler = async ({ ctx, input }: ConfirmOptions) => {
? [booking.user?.destinationCalendar]
: [],
requiresConfirmation: booking?.eventType?.requiresConfirmation ?? false,
+ hideOrganizerEmail: booking.eventType?.hideOrganizerEmail,
eventTypeId: booking.eventType?.id,
customReplyToEmail: booking.eventType?.customReplyToEmail,
team: !!booking.eventType?.team
diff --git a/packages/trpc/server/routers/viewer/bookings/get.handler.ts b/packages/trpc/server/routers/viewer/bookings/get.handler.ts
index 603808cc56..2bec284781 100644
--- a/packages/trpc/server/routers/viewer/bookings/get.handler.ts
+++ b/packages/trpc/server/routers/viewer/bookings/get.handler.ts
@@ -103,6 +103,7 @@ export async function getBookings({
eventTypeColor: true,
customReplyToEmail: true,
allowReschedulingPastBookings: true,
+ hideOrganizerEmail: true,
disableCancelling: true,
disableRescheduling: true,
schedulingType: true,
diff --git a/packages/trpc/server/routers/viewer/bookings/requestReschedule.handler.ts b/packages/trpc/server/routers/viewer/bookings/requestReschedule.handler.ts
index f7b7ed05df..eb1fb4c43c 100644
--- a/packages/trpc/server/routers/viewer/bookings/requestReschedule.handler.ts
+++ b/packages/trpc/server/routers/viewer/bookings/requestReschedule.handler.ts
@@ -195,6 +195,7 @@ export const requestRescheduleHandler = async ({ ctx, input }: RequestReschedule
type: event && event.slug ? event.slug : bookingToReschedule.title,
startTime: bookingToReschedule.startTime.toISOString(),
endTime: bookingToReschedule.endTime.toISOString(),
+ hideOrganizerEmail: eventType?.hideOrganizerEmail,
attendees: usersToPeopleType(
// username field doesn't exists on attendee but could be in the future
bookingToReschedule.attendees as unknown as PersonAttendeeCommonFields[],
diff --git a/packages/trpc/server/routers/viewer/payments/chargeCard.handler.ts b/packages/trpc/server/routers/viewer/payments/chargeCard.handler.ts
index cc9caf6c36..2ae45b5b07 100644
--- a/packages/trpc/server/routers/viewer/payments/chargeCard.handler.ts
+++ b/packages/trpc/server/routers/viewer/payments/chargeCard.handler.ts
@@ -74,6 +74,7 @@ export const chargeCardHandler = async ({ ctx, input }: ChargeCardHandlerOptions
language: { translate: tOrganizer, locale: booking.user?.locale ?? "en" },
},
attendees: attendeesList,
+ hideOrganizerEmail: booking.eventType?.hideOrganizerEmail,
paymentInfo: {
amount: booking.payment[0].amount,
currency: booking.payment[0].currency,
diff --git a/packages/trpc/server/routers/viewer/workflows/activateEventType.handler.ts b/packages/trpc/server/routers/viewer/workflows/activateEventType.handler.ts
index fc433c7483..29fc9b2bf0 100644
--- a/packages/trpc/server/routers/viewer/workflows/activateEventType.handler.ts
+++ b/packages/trpc/server/routers/viewer/workflows/activateEventType.handler.ts
@@ -220,6 +220,7 @@ export const activateEventTypeHandler = async ({ ctx, input }: ActivateEventType
},
},
},
+ hideOrganizerEmail: true,
},
},
user: true,
@@ -254,6 +255,7 @@ export const activateEventTypeHandler = async ({ ctx, input }: ActivateEventType
endTime: booking.endTime.toISOString(),
title: booking.title,
language: { locale: booking?.user?.locale || defaultLocale },
+ hideOrganizerEmail: booking.eventType?.hideOrganizerEmail,
eventType: {
slug: booking.eventType?.slug || "",
schedulingType: booking.eventType?.schedulingType,
diff --git a/packages/trpc/server/routers/viewer/workflows/util.ts b/packages/trpc/server/routers/viewer/workflows/util.ts
index b0a3438f80..1b6e32aa03 100644
--- a/packages/trpc/server/routers/viewer/workflows/util.ts
+++ b/packages/trpc/server/routers/viewer/workflows/util.ts
@@ -57,6 +57,7 @@ export const bookingSelect = {
slug: true,
id: true,
schedulingType: true,
+ hideOrganizerEmail: true,
customReplyToEmail: true,
hosts: {
select: {
@@ -643,6 +644,7 @@ export async function scheduleBookingReminders(
endTime: booking.endTime?.toISOString(),
title: booking.title,
language: { locale: booking?.user?.locale || defaultLocale },
+ hideOrganizerEmail: booking.eventType?.hideOrganizerEmail,
eventType: {
slug: booking.eventType?.slug || "",
schedulingType: booking.eventType?.schedulingType,
diff --git a/packages/types/Calendar.d.ts b/packages/types/Calendar.d.ts
index 9853dd0c20..a9a5244070 100644
--- a/packages/types/Calendar.d.ts
+++ b/packages/types/Calendar.d.ts
@@ -205,6 +205,7 @@ export interface CalendarEvent {
schedulingType?: SchedulingType | null;
iCalUID?: string | null;
iCalSequence?: number | null;
+ hideOrganizerEmail?: boolean;
// It has responses to all the fields(system + user)
responses?: CalEventResponses | null;