feat: Add optional notification email when invite is accepted (#3718)
* feat: Add optional notification email when invite is accepted * Refactor to use beforeSend
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
import mailer from "@server/emails/mailer";
|
||||
import Logger from "@server/logging/Logger";
|
||||
import Metrics from "@server/logging/metrics";
|
||||
import { taskQueue } from "@server/queues";
|
||||
import { TaskPriority } from "@server/queues/tasks/BaseTask";
|
||||
@@ -54,11 +55,19 @@ export default abstract class BaseEmail<T extends EmailProps, S = any> {
|
||||
* @returns A promise that resolves once the email has been successfully sent.
|
||||
*/
|
||||
public async send() {
|
||||
const bsResponse = this.beforeSend
|
||||
? await this.beforeSend(this.props)
|
||||
: ({} as S);
|
||||
const data = { ...this.props, ...bsResponse };
|
||||
const templateName = this.constructor.name;
|
||||
const bsResponse = await this.beforeSend?.(this.props);
|
||||
|
||||
if (bsResponse === false) {
|
||||
Logger.info(
|
||||
"email",
|
||||
`Email ${templateName} not sent due to beforeSend hook`,
|
||||
this.props
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
const data = { ...this.props, ...(bsResponse ?? ({} as S)) };
|
||||
|
||||
try {
|
||||
await mailer.sendMail({
|
||||
@@ -116,10 +125,11 @@ export default abstract class BaseEmail<T extends EmailProps, S = any> {
|
||||
|
||||
/**
|
||||
* beforeSend hook allows async loading additional data that was not passed
|
||||
* through the serialized worker props.
|
||||
* through the serialized worker props. If false is returned then the email
|
||||
* send is aborted.
|
||||
*
|
||||
* @param props Props in email constructor
|
||||
* @returns A promise resolving to additional data
|
||||
*/
|
||||
protected beforeSend?(props: T): Promise<S>;
|
||||
protected beforeSend?(props: T): Promise<S | false>;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import invariant from "invariant";
|
||||
import * as React from "react";
|
||||
import env from "@server/env";
|
||||
import { Collection } from "@server/models";
|
||||
@@ -37,7 +36,10 @@ export default class CollectionNotificationEmail extends BaseEmail<
|
||||
const collection = await Collection.scope("withUser").findByPk(
|
||||
collectionId
|
||||
);
|
||||
invariant(collection, "Collection not found");
|
||||
if (!collection) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return { collection };
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import invariant from "invariant";
|
||||
import * as React from "react";
|
||||
import { Document } from "@server/models";
|
||||
import BaseEmail from "./BaseEmail";
|
||||
@@ -36,7 +35,10 @@ export default class DocumentNotificationEmail extends BaseEmail<
|
||||
> {
|
||||
protected async beforeSend({ documentId }: InputProps) {
|
||||
const document = await Document.unscoped().findByPk(documentId);
|
||||
invariant(document, "Document not found");
|
||||
if (!document) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return { document };
|
||||
}
|
||||
|
||||
|
||||
82
server/emails/templates/InviteAcceptedEmail.tsx
Normal file
82
server/emails/templates/InviteAcceptedEmail.tsx
Normal file
@@ -0,0 +1,82 @@
|
||||
import * as React from "react";
|
||||
import { NotificationSetting } from "@server/models";
|
||||
import BaseEmail from "./BaseEmail";
|
||||
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";
|
||||
|
||||
type Props = {
|
||||
to: string;
|
||||
inviterId: string;
|
||||
invitedName: string;
|
||||
teamUrl: string;
|
||||
};
|
||||
|
||||
type BeforeSendProps = {
|
||||
unsubscribeUrl: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* Email sent to a user when someone they invited successfully signs up.
|
||||
*/
|
||||
export default class InviteAcceptedEmail extends BaseEmail<Props> {
|
||||
protected async beforeSend({ inviterId }: Props) {
|
||||
const notificationSetting = await NotificationSetting.findOne({
|
||||
where: {
|
||||
userId: inviterId,
|
||||
event: "emails.invite_accepted",
|
||||
},
|
||||
});
|
||||
if (!notificationSetting) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return { unsubscribeUrl: notificationSetting.unsubscribeUrl };
|
||||
}
|
||||
|
||||
protected subject({ invitedName }: Props) {
|
||||
return `${invitedName} has joined your Outline team`;
|
||||
}
|
||||
|
||||
protected preview({ invitedName }: Props) {
|
||||
return `Great news, ${invitedName}, accepted your invitation`;
|
||||
}
|
||||
|
||||
protected renderAsText({ invitedName, teamUrl }: Props): string {
|
||||
return `
|
||||
Great news, ${invitedName} just accepted your invitation and has created an account. You can now start collaborating on documents.
|
||||
|
||||
Open Outline: ${teamUrl}
|
||||
`;
|
||||
}
|
||||
|
||||
protected render({
|
||||
invitedName,
|
||||
teamUrl,
|
||||
unsubscribeUrl,
|
||||
}: Props & BeforeSendProps) {
|
||||
return (
|
||||
<EmailTemplate>
|
||||
<Header />
|
||||
|
||||
<Body>
|
||||
<Heading>{invitedName} has joined your team</Heading>
|
||||
<p>
|
||||
Great news, {invitedName} just accepted your invitation and has
|
||||
created an account. You can now start collaborating on documents.
|
||||
</p>
|
||||
<EmptySpace height={10} />
|
||||
<p>
|
||||
<Button href={teamUrl}>Open Outline</Button>
|
||||
</p>
|
||||
</Body>
|
||||
|
||||
<Footer unsubscribeUrl={unsubscribeUrl} />
|
||||
</EmailTemplate>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -32,7 +32,7 @@ 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.
|
||||
To get started, head to the home screen and try creating a collection to help document your processes, create playbooks, or plan your teams work.
|
||||
|
||||
You can also import existing Markdown documents by dragging and dropping them to your collections.
|
||||
|
||||
@@ -49,9 +49,9 @@ ${teamUrl}/home
|
||||
<Heading>Welcome to Outline!</Heading>
|
||||
<p>Outline is a place for your team to build and share knowledge.</p>
|
||||
<p>
|
||||
To get started, head to your dashboard and try creating a collection
|
||||
to help document your workflow, create playbooks or help with team
|
||||
onboarding.
|
||||
To get started, head to the home screen and try creating a
|
||||
collection to help document your processes, create playbooks, or
|
||||
plan your teams work.
|
||||
</p>
|
||||
<p>
|
||||
You can also import existing Markdown documents by dragging and
|
||||
@@ -59,7 +59,7 @@ ${teamUrl}/home
|
||||
</p>
|
||||
<EmptySpace height={10} />
|
||||
<p>
|
||||
<Button href={`${teamUrl}/home`}>View my dashboard</Button>
|
||||
<Button href={`${teamUrl}/home`}>Open Outline</Button>
|
||||
</p>
|
||||
</Body>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user