From 68e275ab070462219c2f7896291934d3825e0f9d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Omar=20L=C3=B3pez?= Date: Fri, 13 May 2022 06:34:54 -0600 Subject: [PATCH] Improving Email DRYness (#2486) * Email DRY * WIP * Improve email DRYness Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com> --- apps/web/lib/emails/templates/_base-email.ts | 41 ++++++++++++++++++ .../templates/attendee-scheduled-email.ts | 43 ++++--------------- .../emails/templates/forgot-password-email.ts | 38 +++------------- .../templates/organizer-scheduled-email.ts | 41 +++--------------- .../lib/emails/templates/team-invite-email.ts | 38 +++------------- 5 files changed, 66 insertions(+), 135 deletions(-) create mode 100644 apps/web/lib/emails/templates/_base-email.ts diff --git a/apps/web/lib/emails/templates/_base-email.ts b/apps/web/lib/emails/templates/_base-email.ts new file mode 100644 index 00000000..2e73025d --- /dev/null +++ b/apps/web/lib/emails/templates/_base-email.ts @@ -0,0 +1,41 @@ +import nodemailer from "nodemailer"; + +import { getErrorFromUnknown } from "@calcom/lib/errors"; +import { serverConfig } from "@calcom/lib/serverConfig"; + +export default class BaseEmail { + name: string = ""; + + protected getNodeMailerPayload(): Record { + return {}; + } + public sendEmail() { + new Promise((resolve, reject) => + nodemailer + .createTransport(this.getMailerOptions().transport) + .sendMail(this.getNodeMailerPayload(), (_err, info) => { + if (_err) { + const err = getErrorFromUnknown(_err); + this.printNodeMailerError(err); + reject(err); + } else { + resolve(info); + } + }) + ).catch((e) => console.error("sendEmail", e)); + return new Promise((resolve) => resolve("send mail async")); + } + + protected getMailerOptions() { + return { + transport: serverConfig.transport, + from: serverConfig.from, + }; + } + + protected printNodeMailerError(error: Error): void { + /** Don't clog the logs with unsent emails in E2E */ + if (process.env.NEXT_PUBLIC_IS_E2E) return; + console.error(`${this.name}_ERROR`, error); + } +} diff --git a/apps/web/lib/emails/templates/attendee-scheduled-email.ts b/apps/web/lib/emails/templates/attendee-scheduled-email.ts index 1a958990..051aa327 100644 --- a/apps/web/lib/emails/templates/attendee-scheduled-email.ts +++ b/apps/web/lib/emails/templates/attendee-scheduled-email.ts @@ -4,22 +4,20 @@ import timezone from "dayjs/plugin/timezone"; import toArray from "dayjs/plugin/toArray"; import utc from "dayjs/plugin/utc"; import { createEvent, DateArray } from "ics"; -import { DatasetJsonLdProps } from "next-seo"; -import nodemailer from "nodemailer"; import rrule from "rrule"; import { getAppName } from "@calcom/app-store/utils"; import { getCancelLink, getRichDescription } from "@calcom/lib/CalEventParser"; -import { getErrorFromUnknown } from "@calcom/lib/errors"; -import { serverConfig } from "@calcom/lib/serverConfig"; -import type { Person, CalendarEvent, RecurringEvent } from "@calcom/types/Calendar"; +import type { CalendarEvent, Person, RecurringEvent } from "@calcom/types/Calendar"; + +import BaseEmail from "@lib/emails/templates/_base-email"; import { - emailHead, - emailSchedulingBodyHeader, emailBodyLogo, + emailHead, emailScheduledBodyHeaderContent, emailSchedulingBodyDivider, + emailSchedulingBodyHeader, linkIcon, } from "./common"; @@ -28,34 +26,19 @@ dayjs.extend(timezone); dayjs.extend(localizedFormat); dayjs.extend(toArray); -export default class AttendeeScheduledEmail { +export default class AttendeeScheduledEmail extends BaseEmail { calEvent: CalendarEvent; attendee: Person; recurringEvent: RecurringEvent; constructor(calEvent: CalendarEvent, attendee: Person, recurringEvent: RecurringEvent) { + super(); + this.name = "SEND_BOOKING_CONFIRMATION"; this.calEvent = calEvent; this.attendee = attendee; this.recurringEvent = recurringEvent; } - public sendEmail() { - new Promise((resolve, reject) => - nodemailer - .createTransport(this.getMailerOptions().transport) - .sendMail(this.getNodeMailerPayload(), (_err, info) => { - if (_err) { - const err = getErrorFromUnknown(_err); - this.printNodeMailerError(err); - reject(err); - } else { - resolve(info); - } - }) - ).catch((e) => console.error("sendEmail", e)); - return new Promise((resolve) => resolve("send mail async")); - } - protected getiCalEventAsString(): string | undefined { // Taking care of recurrence rule beforehand let recurrenceRule: string | undefined = undefined; @@ -115,12 +98,6 @@ export default class AttendeeScheduledEmail { }; } - protected getMailerOptions() { - return { - transport: serverConfig.transport, - from: serverConfig.from, - }; - } protected getDescription(): string { if (!this.calEvent.description) return ""; return ` @@ -144,10 +121,6 @@ ${getRichDescription(this.calEvent)} `.trim(); } - protected printNodeMailerError(error: Error): void { - console.error("SEND_BOOKING_CONFIRMATION_ERROR", this.attendee.email, error); - } - protected getHtmlBody(): string { const headerContent = this.calEvent.attendees[0].language.translate("confirmed_event_type_subject", { eventType: this.calEvent.type, diff --git a/apps/web/lib/emails/templates/forgot-password-email.ts b/apps/web/lib/emails/templates/forgot-password-email.ts index 45cc19d9..cd5c0f2d 100644 --- a/apps/web/lib/emails/templates/forgot-password-email.ts +++ b/apps/web/lib/emails/templates/forgot-password-email.ts @@ -1,10 +1,8 @@ import { TFunction } from "next-i18next"; -import nodemailer from "nodemailer"; -import { getErrorFromUnknown } from "@calcom/lib/errors"; -import { serverConfig } from "@calcom/lib/serverConfig"; +import BaseEmail from "@lib/emails/templates/_base-email"; -import { emailHead, linkIcon, emailBodyLogo } from "./common"; +import { emailBodyLogo, emailHead, linkIcon } from "./common"; export type PasswordReset = { language: TFunction; @@ -17,37 +15,15 @@ export type PasswordReset = { export const PASSWORD_RESET_EXPIRY_HOURS = 6; -export default class ForgotPasswordEmail { +export default class ForgotPasswordEmail extends BaseEmail { passwordEvent: PasswordReset; constructor(passwordEvent: PasswordReset) { + super(); + this.name = "SEND_PASSWORD_RESET_EMAIL"; this.passwordEvent = passwordEvent; } - public sendEmail() { - new Promise((resolve, reject) => - nodemailer - .createTransport(this.getMailerOptions().transport) - .sendMail(this.getNodeMailerPayload(), (_err, info) => { - if (_err) { - const err = getErrorFromUnknown(_err); - this.printNodeMailerError(err); - reject(err); - } else { - resolve(info); - } - }) - ).catch((e) => console.error("sendEmail", e)); - return new Promise((resolve) => resolve("send mail async")); - } - - protected getMailerOptions() { - return { - transport: serverConfig.transport, - from: serverConfig.from, - }; - } - protected getNodeMailerPayload(): Record { return { to: `${this.passwordEvent.user.name} <${this.passwordEvent.user.email}>`, @@ -58,10 +34,6 @@ export default class ForgotPasswordEmail { }; } - protected printNodeMailerError(error: Error): void { - console.error("SEND_PASSWORD_RESET_EMAIL_ERROR", this.passwordEvent.user.email, error); - } - protected getTextBody(): string { return ` ${this.passwordEvent.language("reset_password_subject")} diff --git a/apps/web/lib/emails/templates/organizer-scheduled-email.ts b/apps/web/lib/emails/templates/organizer-scheduled-email.ts index fe9381a7..3f51f6f1 100644 --- a/apps/web/lib/emails/templates/organizer-scheduled-email.ts +++ b/apps/web/lib/emails/templates/organizer-scheduled-email.ts @@ -4,21 +4,20 @@ import timezone from "dayjs/plugin/timezone"; import toArray from "dayjs/plugin/toArray"; import utc from "dayjs/plugin/utc"; import { createEvent, DateArray, Person } from "ics"; -import nodemailer from "nodemailer"; import rrule from "rrule"; import { getAppName } from "@calcom/app-store/utils"; import { getCancelLink, getRichDescription } from "@calcom/lib/CalEventParser"; -import { getErrorFromUnknown } from "@calcom/lib/errors"; -import { serverConfig } from "@calcom/lib/serverConfig"; import type { CalendarEvent, RecurringEvent } from "@calcom/types/Calendar"; +import BaseEmail from "@lib/emails/templates/_base-email"; + import { - emailHead, - emailSchedulingBodyHeader, emailBodyLogo, + emailHead, emailScheduledBodyHeaderContent, emailSchedulingBodyDivider, + emailSchedulingBodyHeader, linkIcon, } from "./common"; @@ -27,32 +26,17 @@ dayjs.extend(timezone); dayjs.extend(localizedFormat); dayjs.extend(toArray); -export default class OrganizerScheduledEmail { +export default class OrganizerScheduledEmail extends BaseEmail { calEvent: CalendarEvent; recurringEvent: RecurringEvent; constructor(calEvent: CalendarEvent, recurringEvent: RecurringEvent) { + super(); + this.name = "SEND_BOOKING_CONFIRMATION"; this.calEvent = calEvent; this.recurringEvent = recurringEvent; } - public sendEmail() { - new Promise((resolve, reject) => - nodemailer - .createTransport(this.getMailerOptions().transport) - .sendMail(this.getNodeMailerPayload(), (_err, info) => { - if (_err) { - const err = getErrorFromUnknown(_err); - this.printNodeMailerError(err); - reject(err); - } else { - resolve(info); - } - }) - ).catch((e) => console.error("sendEmail", e)); - return new Promise((resolve) => resolve("send mail async")); - } - protected getiCalEventAsString(): string | undefined { // Taking care of recurrence rule beforehand let recurrenceRule: string | undefined = undefined; @@ -121,13 +105,6 @@ export default class OrganizerScheduledEmail { }; } - protected getMailerOptions() { - return { - transport: serverConfig.transport, - from: serverConfig.from, - }; - } - protected getTextBody(): string { return ` ${this.calEvent.organizer.language.translate( @@ -139,10 +116,6 @@ ${getRichDescription(this.calEvent)} `.trim(); } - protected printNodeMailerError(error: Error): void { - console.error("SEND_BOOKING_CONFIRMATION_ERROR", this.calEvent.organizer.email, error); - } - protected getHtmlBody(): string { const headerContent = this.calEvent.organizer.language.translate("confirmed_event_type_subject", { eventType: this.calEvent.type, diff --git a/apps/web/lib/emails/templates/team-invite-email.ts b/apps/web/lib/emails/templates/team-invite-email.ts index 7d9d91ae..c15c7053 100644 --- a/apps/web/lib/emails/templates/team-invite-email.ts +++ b/apps/web/lib/emails/templates/team-invite-email.ts @@ -1,10 +1,8 @@ import { TFunction } from "next-i18next"; -import nodemailer from "nodemailer"; -import { getErrorFromUnknown } from "@calcom/lib/errors"; -import { serverConfig } from "@calcom/lib/serverConfig"; +import BaseEmail from "@lib/emails/templates/_base-email"; -import { emailHead, linkIcon, emailBodyLogo } from "./common"; +import { emailBodyLogo, emailHead, linkIcon } from "./common"; export type TeamInvite = { language: TFunction; @@ -14,37 +12,15 @@ export type TeamInvite = { joinLink: string; }; -export default class TeamInviteEmail { +export default class TeamInviteEmail extends BaseEmail { teamInviteEvent: TeamInvite; constructor(teamInviteEvent: TeamInvite) { + super(); + this.name = "SEND_TEAM_INVITE_EMAIL"; this.teamInviteEvent = teamInviteEvent; } - public sendEmail() { - new Promise((resolve, reject) => - nodemailer - .createTransport(this.getMailerOptions().transport) - .sendMail(this.getNodeMailerPayload(), (_err, info) => { - if (_err) { - const err = getErrorFromUnknown(_err); - this.printNodeMailerError(err); - reject(err); - } else { - resolve(info); - } - }) - ).catch((e) => console.error("sendEmail", e)); - return new Promise((resolve) => resolve("send mail async")); - } - - protected getMailerOptions() { - return { - transport: serverConfig.transport, - from: serverConfig.from, - }; - } - protected getNodeMailerPayload(): Record { return { to: this.teamInviteEvent.to, @@ -58,10 +34,6 @@ export default class TeamInviteEmail { }; } - protected printNodeMailerError(error: Error): void { - console.error("SEND_TEAM_INVITE_EMAIL_ERROR", this.teamInviteEvent.to, error); - } - protected getTextBody(): string { return ""; }