From 5c24f9e1d528ebb8651ed70809e729be97290ae6 Mon Sep 17 00:00:00 2001 From: Tom Moor Date: Thu, 7 Apr 2022 16:50:04 -0700 Subject: [PATCH] chore: Email + mailer refactor (#3342) * Huge email refactor * fix: One rename too many * comments --- server/__snapshots__/mailer.test.ts.snap | 67 ----- server/commands/accountProvisioner.test.ts | 8 +- server/commands/accountProvisioner.ts | 11 +- server/commands/userInviter.ts | 19 +- server/emails/CollectionNotificationEmail.tsx | 53 ---- server/emails/DocumentNotificationEmail.tsx | 66 ----- server/emails/ExportFailureEmail.tsx | 44 --- server/emails/ExportSuccessEmail.tsx | 52 ---- server/emails/InviteEmail.tsx | 56 ---- server/emails/SigninEmail.tsx | 50 ---- server/emails/WelcomeEmail.tsx | 52 ---- server/emails/index.ts | 44 --- server/emails/mailer.tsx | 131 +++++++++ server/emails/templates/BaseEmail.tsx | 107 +++++++ .../templates/CollectionNotificationEmail.tsx | 88 ++++++ .../templates/DocumentNotificationEmail.tsx | 100 +++++++ .../emails/templates/ExportFailureEmail.tsx | 63 ++++ .../emails/templates/ExportSuccessEmail.tsx | 69 +++++ server/emails/templates/InviteEmail.tsx | 68 +++++ server/emails/templates/SigninEmail.tsx | 72 +++++ server/emails/templates/WelcomeEmail.tsx | 70 +++++ .../{ => templates}/components/Body.tsx | 0 .../{ => templates}/components/Button.tsx | 0 .../components/EmailLayout.tsx | 0 .../{ => templates}/components/EmptySpace.tsx | 0 .../{ => templates}/components/Footer.tsx | 0 .../{ => templates}/components/Header.tsx | 0 .../{ => templates}/components/Heading.tsx | 0 server/emails/templates/index.ts | 16 ++ server/mailer.test.ts | 23 -- server/mailer.tsx | 269 ------------------ server/queues/processors/ExportsProcessor.ts | 23 +- .../processors/NotificationsProcessor.test.ts | 16 +- .../processors/NotificationsProcessor.ts | 35 +-- server/queues/tasks/BaseTask.ts | 15 + server/queues/tasks/EmailTask.ts | 18 +- server/routes/auth/providers/email.test.ts | 19 +- server/routes/auth/providers/email.ts | 23 +- server/services/web.ts | 2 - 39 files changed, 879 insertions(+), 870 deletions(-) delete mode 100644 server/__snapshots__/mailer.test.ts.snap delete mode 100644 server/emails/CollectionNotificationEmail.tsx delete mode 100644 server/emails/DocumentNotificationEmail.tsx delete mode 100644 server/emails/ExportFailureEmail.tsx delete mode 100644 server/emails/ExportSuccessEmail.tsx delete mode 100644 server/emails/InviteEmail.tsx delete mode 100644 server/emails/SigninEmail.tsx delete mode 100644 server/emails/WelcomeEmail.tsx delete mode 100644 server/emails/index.ts create mode 100644 server/emails/mailer.tsx create mode 100644 server/emails/templates/BaseEmail.tsx create mode 100644 server/emails/templates/CollectionNotificationEmail.tsx create mode 100644 server/emails/templates/DocumentNotificationEmail.tsx create mode 100644 server/emails/templates/ExportFailureEmail.tsx create mode 100644 server/emails/templates/ExportSuccessEmail.tsx create mode 100644 server/emails/templates/InviteEmail.tsx create mode 100644 server/emails/templates/SigninEmail.tsx create mode 100644 server/emails/templates/WelcomeEmail.tsx rename server/emails/{ => templates}/components/Body.tsx (100%) rename server/emails/{ => templates}/components/Button.tsx (100%) rename server/emails/{ => templates}/components/EmailLayout.tsx (100%) rename server/emails/{ => templates}/components/EmptySpace.tsx (100%) rename server/emails/{ => templates}/components/Footer.tsx (100%) rename server/emails/{ => templates}/components/Header.tsx (100%) rename server/emails/{ => templates}/components/Heading.tsx (100%) create mode 100644 server/emails/templates/index.ts delete mode 100644 server/mailer.test.ts delete mode 100644 server/mailer.tsx diff --git a/server/__snapshots__/mailer.test.ts.snap b/server/__snapshots__/mailer.test.ts.snap deleted file mode 100644 index 2017e1b2c..000000000 --- a/server/__snapshots__/mailer.test.ts.snap +++ /dev/null @@ -1,67 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`Mailer #welcome 1`] = ` -Object { - "from": "hello@example.com", - "html": " - - - - - - - - Welcome to Outline - - - - - - - - - - -
- Outline is a place for your team to build and share knowledge. -
 
\\"Outline\\"
 

Welcome to Outline!

Outline is a place for your team to build and share knowledge.

To get started, head to your dashboard and try creating a collection to help document your workflow, create playbooks or help with team onboarding.

You can also import existing Markdown documents by dragging and dropping them to your collections.

 

View my dashboard

 
OutlineTwitter
-
- - - ", - "replyTo": "hello@example.com", - "subject": "Welcome to Outline", - "text": " -Welcome to Outline! - -Outline is a place for your team to build and share knowledge. - -To get started, head to your dashboard and try creating a collection to help document your workflow, create playbooks or help with team onboarding. - -You can also import existing Markdown documents by dragging and dropping them to your collections. - -http://example.com/home -", - "to": "user@example.com", -} -`; diff --git a/server/commands/accountProvisioner.test.ts b/server/commands/accountProvisioner.test.ts index c1070c740..64522fae6 100644 --- a/server/commands/accountProvisioner.test.ts +++ b/server/commands/accountProvisioner.test.ts @@ -1,6 +1,6 @@ +import WelcomeEmail from "@server/emails/templates/WelcomeEmail"; import Collection from "@server/models/Collection"; import UserAuthentication from "@server/models/UserAuthentication"; -import EmailTask from "@server/queues/tasks/EmailTask"; import { buildUser, buildTeam } from "@server/test/factories"; import { flushdb } from "@server/test/support"; import accountProvisioner from "./accountProvisioner"; @@ -13,7 +13,7 @@ describe("accountProvisioner", () => { const ip = "127.0.0.1"; it("should create a new user and team", async () => { - const spy = jest.spyOn(EmailTask, "schedule"); + const spy = jest.spyOn(WelcomeEmail, "schedule"); const { user, team, isNewTeam, isNewUser } = await accountProvisioner({ ip, user: { @@ -55,7 +55,7 @@ describe("accountProvisioner", () => { }); it("should update exising user and authentication", async () => { - const spy = jest.spyOn(EmailTask, "schedule"); + const spy = jest.spyOn(WelcomeEmail, "schedule"); const existingTeam = await buildTeam(); const providers = await existingTeam.$get("authenticationProviders"); const authenticationProvider = providers[0]; @@ -149,7 +149,7 @@ describe("accountProvisioner", () => { }); it("should create a new user in an existing team", async () => { - const spy = jest.spyOn(EmailTask, "schedule"); + const spy = jest.spyOn(WelcomeEmail, "schedule"); const team = await buildTeam(); const authenticationProviders = await team.$get("authenticationProviders"); const authenticationProvider = authenticationProviders[0]; diff --git a/server/commands/accountProvisioner.ts b/server/commands/accountProvisioner.ts index 7d4b3b952..8056c25c6 100644 --- a/server/commands/accountProvisioner.ts +++ b/server/commands/accountProvisioner.ts @@ -1,5 +1,6 @@ import invariant from "invariant"; import { UniqueConstraintError } from "sequelize"; +import WelcomeEmail from "@server/emails/templates/WelcomeEmail"; import { AuthenticationError, EmailAuthenticationRequiredError, @@ -7,7 +8,6 @@ import { } from "@server/errors"; import { APM } from "@server/logging/tracing"; import { Collection, Team, User } from "@server/models"; -import EmailTask from "@server/queues/tasks/EmailTask"; import teamCreator from "./teamCreator"; import userCreator from "./userCreator"; @@ -89,12 +89,9 @@ async function accountProvisioner({ const { isNewUser, user } = result; if (isNewUser) { - await EmailTask.schedule({ - type: "welcome", - options: { - to: user.email, - teamUrl: team.url, - }, + await WelcomeEmail.schedule({ + to: user.email, + teamUrl: team.url, }); } diff --git a/server/commands/userInviter.ts b/server/commands/userInviter.ts index e1c6a83df..007906f14 100644 --- a/server/commands/userInviter.ts +++ b/server/commands/userInviter.ts @@ -1,9 +1,9 @@ import invariant from "invariant"; import { uniqBy } from "lodash"; import { Role } from "@shared/types"; +import InviteEmail from "@server/emails/templates/InviteEmail"; import Logger from "@server/logging/logger"; import { User, Event, Team } from "@server/models"; -import EmailTask from "@server/queues/tasks/EmailTask"; type Invite = { name: string; @@ -75,16 +75,13 @@ export default async function userInviter({ ip, }); - await EmailTask.schedule({ - type: "invite", - options: { - to: invite.email, - name: invite.name, - actorName: user.name, - actorEmail: user.email, - teamName: team.name, - teamUrl: team.url, - }, + await InviteEmail.schedule({ + to: invite.email, + name: invite.name, + actorName: user.name, + actorEmail: user.email, + teamName: team.name, + teamUrl: team.url, }); if (process.env.NODE_ENV === "development") { diff --git a/server/emails/CollectionNotificationEmail.tsx b/server/emails/CollectionNotificationEmail.tsx deleted file mode 100644 index a79a1f664..000000000 --- a/server/emails/CollectionNotificationEmail.tsx +++ /dev/null @@ -1,53 +0,0 @@ -import * as React from "react"; -import { Collection } from "@server/models"; -import Body from "./components/Body"; -import Button from "./components/Button"; -import EmailTemplate from "./components/EmailLayout"; -import EmptySpace from "./components/EmptySpace"; -import Footer from "./components/Footer"; -import Header from "./components/Header"; -import Heading from "./components/Heading"; - -export type Props = { - collection: Collection; - eventName: string; - unsubscribeUrl: string; -}; - -export const collectionNotificationEmailText = ({ - collection, - eventName = "created", -}: Props) => ` -${collection.name} - -${collection.user.name} ${eventName} the collection "${collection.name}" - -Open Collection: ${process.env.URL}${collection.url} -`; - -export const CollectionNotificationEmail = ({ - collection, - eventName = "created", - unsubscribeUrl, -}: Props) => { - return ( - -
- - - {collection.name} -

- {collection.user.name} {eventName} the collection "{collection.name}". -

- -

- -

- - -