diff --git a/lib/calendarClient.ts b/lib/calendarClient.ts index 8bded41027..1e7aab3653 100644 --- a/lib/calendarClient.ts +++ b/lib/calendarClient.ts @@ -1,5 +1,4 @@ import EventOrganizerMail from "./emails/EventOrganizerMail"; -import EventAttendeeMail from "./emails/EventAttendeeMail"; import EventOrganizerRescheduledMail from "./emails/EventOrganizerRescheduledMail"; import EventAttendeeRescheduledMail from "./emails/EventAttendeeRescheduledMail"; import prisma from "./prisma"; @@ -120,6 +119,10 @@ export interface CalendarEvent { startTime: string; endTime: string; description?: string; + team?: { + name: string; + members: string[]; + }; location?: string; organizer: Person; attendees: Person[]; @@ -574,25 +577,11 @@ const createEvent = async ( entryPoints: maybeEntryPoints, }); - const attendeeMail = new EventAttendeeMail(calEvent, uid, { - hangoutLink: maybeHangoutLink, - conferenceData: maybeConferenceData, - entryPoints: maybeEntryPoints, - }); - try { await organizerMail.sendEmail(); } catch (e) { console.error("organizerMail.sendEmail failed", e); } - - if (!creationResult || !creationResult.disableConfirmationEmail) { - try { - await attendeeMail.sendEmail(); - } catch (e) { - console.error("attendeeMail.sendEmail failed", e); - } - } } return { diff --git a/lib/emails/EventAttendeeMail.ts b/lib/emails/EventAttendeeMail.ts index f53fb6f9b8..cf9ea78f14 100644 --- a/lib/emails/EventAttendeeMail.ts +++ b/lib/emails/EventAttendeeMail.ts @@ -65,7 +65,13 @@ export default class EventAttendeeMail extends EventMail { Who - ${this.calEvent.organizer.name}
${this.calEvent.organizer.email} + + ${this.calEvent.team?.name || this.calEvent.organizer.name}
+ + ${this.calEvent.organizer.email && !this.calEvent.team ? this.calEvent.organizer.email : ""} + ${this.calEvent.team ? this.calEvent.team.members.join(", ") : ""} + + Where @@ -122,6 +128,10 @@ export default class EventAttendeeMail extends EventMail { return ``; } + protected getAdditionalFooter(): string { + return this.parser.getChangeEventFooterHtml(); + } + /** * Returns the payload object for the nodemailer. * @@ -133,7 +143,7 @@ export default class EventAttendeeMail extends EventMail { from: `${this.calEvent.organizer.name} <${this.getMailerOptions().from}>`, replyTo: this.calEvent.organizer.email, subject: `Confirmed: ${this.calEvent.type} with ${ - this.calEvent.organizer.name + this.calEvent.team?.name || this.calEvent.organizer.name } on ${this.getInviteeStart().format("LT dddd, LL")}`, html: this.getHtmlRepresentation(), text: this.getPlainTextRepresentation(), diff --git a/lib/emails/EventAttendeeRescheduledMail.ts b/lib/emails/EventAttendeeRescheduledMail.ts index 1286c7ab7f..92592df3e2 100644 --- a/lib/emails/EventAttendeeRescheduledMail.ts +++ b/lib/emails/EventAttendeeRescheduledMail.ts @@ -13,8 +13,8 @@ export default class EventAttendeeRescheduledMail extends EventAttendeeMail { Hi ${this.calEvent.attendees[0].name},

Your ${this.calEvent.type} with ${ - this.calEvent.organizer.name - } has been rescheduled to ${this.getInviteeStart().format("h:mma")} + this.calEvent.team?.name || this.calEvent.organizer.name + } has been rescheduled to ${this.getInviteeStart().format("h:mma")} (${this.calEvent.attendees[0].timeZone}) on ${this.getInviteeStart().format("dddd, LL")}.
` + this.getAdditionalFooter() + diff --git a/lib/emails/EventMail.ts b/lib/emails/EventMail.ts index b3728872e6..3fa6eb5140 100644 --- a/lib/emails/EventMail.ts +++ b/lib/emails/EventMail.ts @@ -69,7 +69,7 @@ export default abstract class EventMail { /** * Sends the email to the event attendant and returns a Promise. */ - public sendEmail(): Promise { + public sendEmail() { new Promise((resolve, reject) => nodemailer .createTransport(this.getMailerOptions().transport) @@ -90,7 +90,7 @@ export default abstract class EventMail { * * @protected */ - protected getMailerOptions(): any { + protected getMailerOptions() { return { transport: serverConfig.transport, from: serverConfig.from, @@ -135,12 +135,4 @@ export default abstract class EventMail { protected getCancelLink(): string { return this.parser.getCancelLink(); } - - /** - * Defines a footer that will be appended to the email. - * @protected - */ - protected getAdditionalFooter(): string { - return this.parser.getChangeEventFooterHtml(); - } } diff --git a/lib/emails/EventOrganizerMail.ts b/lib/emails/EventOrganizerMail.ts index e5ee36b743..c24267895d 100644 --- a/lib/emails/EventOrganizerMail.ts +++ b/lib/emails/EventOrganizerMail.ts @@ -50,8 +50,10 @@ export default class EventOrganizerMail extends EventMail { return "A new event has been scheduled."; } - protected getBodyText(): string { - return "You and any other attendees have been emailed with this information."; + protected getAdditionalFooter(): string { + return `

Need to make a change? Manage my bookings

`; } protected getImage(): string { @@ -94,7 +96,6 @@ export default class EventOrganizerMail extends EventMail { > ${this.getImage()}

${this.getBodyHeader()}

-

${this.getBodyText()}


diff --git a/lib/events/EventManager.ts b/lib/events/EventManager.ts index f40fb49b87..f4af4217af 100644 --- a/lib/events/EventManager.ts +++ b/lib/events/EventManager.ts @@ -6,6 +6,7 @@ import prisma from "@lib/prisma"; import { LocationType } from "@lib/location"; import { v5 as uuidv5 } from "uuid"; import merge from "lodash.merge"; +import EventAttendeeMail from "@lib/emails/EventAttendeeMail"; export interface EventResult { type: string; @@ -65,10 +66,25 @@ export default class EventManager { // First, create all calendar events. If this is a dedicated integration event, don't send a mail right here. const results: Array = await this.createAllCalendarEvents(event, isDedicated, maybeUid); - // If and only if event type is a dedicated meeting, create a dedicated video meeting as well. if (isDedicated) { results.push(await this.createVideoEvent(event, maybeUid)); + } else { + if (!results.length || !results.some((eRes) => eRes.createdEvent.disableConfirmationEmail)) { + const metadata: { hangoutLink?: string; conferenceData?: unknown; entryPoints?: unknown } = {}; + if (results.length) { + // TODO: Handle created event metadata more elegantly + metadata.hangoutLink = results[0].createdEvent?.hangoutLink; + metadata.conferenceData = results[0].createdEvent?.conferenceData; + metadata.entryPoints = results[0].createdEvent?.entryPoints; + } + const attendeeMail = new EventAttendeeMail(event, maybeUid, metadata); + try { + await attendeeMail.sendEmail(); + } catch (e) { + console.error("attendeeMail.sendEmail failed", e); + } + } } const referencesToCreate: Array = results.map((result) => { diff --git a/pages/api/book/[user].ts b/pages/api/book/[user].ts index 6950c76134..e509f1e739 100644 --- a/pages/api/book/[user].ts +++ b/pages/api/book/[user].ts @@ -5,7 +5,6 @@ import { CalendarEvent, getBusyCalendarTimes } from "@lib/calendarClient"; import { v5 as uuidv5 } from "uuid"; import short from "short-uuid"; import { getBusyVideoTimes } from "@lib/videoClient"; -import EventAttendeeMail from "../../../lib/emails/EventAttendeeMail"; import { getEventName } from "@lib/event"; import dayjs from "dayjs"; import logger from "../../../lib/logger"; @@ -15,7 +14,6 @@ import utc from "dayjs/plugin/utc"; import timezone from "dayjs/plugin/timezone"; import isBetween from "dayjs/plugin/isBetween"; import dayjsBusinessDays from "dayjs-business-days"; -import { Exception } from "handlebars"; import EventOrganizerRequestMail from "@lib/emails/EventOrganizerRequestMail"; dayjs.extend(dayjsBusinessDays); @@ -81,25 +79,6 @@ function isOutOfBounds( } } -export async function handleLegacyConfirmationMail( - results: Array, - eventType: EventType, - evt: CalendarEvent, - hashUID: string -): Promise<{ error: Exception; message: string | null }> { - if (results.length === 0 && !eventType.requiresConfirmation) { - // Legacy as well, as soon as we have a separate email integration class. Just used - // to send an email even if there is no integration at all. - try { - const mail = new EventAttendeeMail(evt, hashUID); - await mail.sendEmail(); - } catch (e) { - return { error: e, message: "Booking failed" }; - } - } - return null; -} - export default async function handler(req: NextApiRequest, res: NextApiResponse): Promise { const { user } = req.query; log.debug(`Booking ${user} started`); @@ -311,13 +290,6 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse) results.length > 0 ? results[0].uid : translator.fromUUID(uuidv5(JSON.stringify(evt), uuidv5.URL)); // TODO Should just be set to the true case as soon as we have a "bare email" integration class. // UID generation should happen in the integration itself, not here. - const legacyMailError = await handleLegacyConfirmationMail(results, eventType, evt, hashUID); - if (legacyMailError) { - log.error("Sending legacy event mail failed", legacyMailError.error); - log.error(`Booking ${user} failed`); - res.status(500).json({ message: legacyMailError.message }); - return; - } try { await prisma.booking.create({