mirror of
https://github.com/outline/outline.git
synced 2026-06-13 19:35:02 +03:00
Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 4e19e7131d | |||
| 60236da65b |
@@ -1,5 +1,4 @@
|
||||
import addressparser from "addressparser";
|
||||
import invariant from "invariant";
|
||||
import { EmailAddress } from "addressparser";
|
||||
import nodemailer, { Transporter } from "nodemailer";
|
||||
import SMTPTransport from "nodemailer/lib/smtp-transport";
|
||||
import Oy from "oy-vey";
|
||||
@@ -12,7 +11,7 @@ const useTestEmailService = env.isDevelopment && !env.SMTP_USERNAME;
|
||||
|
||||
type SendMailOptions = {
|
||||
to: string;
|
||||
fromName?: string;
|
||||
from?: EmailAddress | string;
|
||||
replyTo?: string;
|
||||
messageId?: string;
|
||||
references?: string[];
|
||||
@@ -143,20 +142,8 @@ export class Mailer {
|
||||
try {
|
||||
Logger.info("email", `Sending email "${data.subject}" to ${data.to}`);
|
||||
|
||||
invariant(
|
||||
env.SMTP_FROM_EMAIL,
|
||||
"SMTP_FROM_EMAIL is required to send emails"
|
||||
);
|
||||
|
||||
const from = addressparser(env.SMTP_FROM_EMAIL)[0];
|
||||
|
||||
const info = await transporter.sendMail({
|
||||
from: data.fromName
|
||||
? {
|
||||
name: data.fromName,
|
||||
address: from.address,
|
||||
}
|
||||
: env.SMTP_FROM_EMAIL,
|
||||
from: data.from ?? env.SMTP_FROM_EMAIL,
|
||||
replyTo: data.replyTo ?? env.SMTP_REPLY_EMAIL ?? env.SMTP_FROM_EMAIL,
|
||||
to: data.to,
|
||||
messageId: data.messageId,
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
import addressparser from "addressparser";
|
||||
import Bull from "bull";
|
||||
import invariant from "invariant";
|
||||
import randomstring from "randomstring";
|
||||
import * as React from "react";
|
||||
import mailer from "@server/emails/mailer";
|
||||
import env from "@server/env";
|
||||
import Logger from "@server/logging/Logger";
|
||||
import Metrics from "@server/logging/Metrics";
|
||||
import Notification from "@server/models/Notification";
|
||||
@@ -9,6 +13,13 @@ import { TaskPriority } from "@server/queues/tasks/BaseTask";
|
||||
import { NotificationMetadata } from "@server/types";
|
||||
import { getEmailMessageId } from "@server/utils/emails";
|
||||
|
||||
export enum EmailMessageCategory {
|
||||
Authentication = "authentication",
|
||||
Invitation = "invitation",
|
||||
Notification = "notification",
|
||||
Marketing = "marketing",
|
||||
}
|
||||
|
||||
export interface EmailProps {
|
||||
to: string | null;
|
||||
}
|
||||
@@ -20,6 +31,9 @@ export default abstract class BaseEmail<
|
||||
private props: T;
|
||||
private metadata?: NotificationMetadata;
|
||||
|
||||
/** The message category for the email. */
|
||||
protected abstract get category(): EmailMessageCategory;
|
||||
|
||||
/**
|
||||
* Schedule this email type to be sent asyncronously by a worker.
|
||||
*
|
||||
@@ -113,7 +127,7 @@ export default abstract class BaseEmail<
|
||||
try {
|
||||
await mailer.sendMail({
|
||||
to: this.props.to,
|
||||
fromName: this.fromName?.(data),
|
||||
from: this.from(data),
|
||||
subject: this.subject(data),
|
||||
messageId,
|
||||
references,
|
||||
@@ -148,6 +162,29 @@ export default abstract class BaseEmail<
|
||||
}
|
||||
}
|
||||
|
||||
private from(props: S & T) {
|
||||
invariant(
|
||||
env.SMTP_FROM_EMAIL,
|
||||
"SMTP_FROM_EMAIL is required to send emails"
|
||||
);
|
||||
|
||||
const parsedFrom = addressparser(env.SMTP_FROM_EMAIL)[0];
|
||||
const name = this.fromName?.(props);
|
||||
|
||||
if (this.category === EmailMessageCategory.Authentication) {
|
||||
const domain = parsedFrom.address.split("@")[1];
|
||||
return {
|
||||
name: name ?? parsedFrom.name,
|
||||
address: `noreply-${randomstring.generate(24)}@${domain}`,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
name: name ?? parsedFrom.name,
|
||||
address: parsedFrom.address,
|
||||
};
|
||||
}
|
||||
|
||||
private pixel(notification: Notification) {
|
||||
return <img src={notification.pixelUrl} width="1" height="1" />;
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ import * as React from "react";
|
||||
import { NotificationEventType } from "@shared/types";
|
||||
import { Collection } from "@server/models";
|
||||
import NotificationSettingsHelper from "@server/models/helpers/NotificationSettingsHelper";
|
||||
import BaseEmail, { EmailProps } from "./BaseEmail";
|
||||
import BaseEmail, { EmailMessageCategory, EmailProps } from "./BaseEmail";
|
||||
import Body from "./components/Body";
|
||||
import Button from "./components/Button";
|
||||
import EmailTemplate from "./components/EmailLayout";
|
||||
@@ -32,6 +32,10 @@ export default class CollectionCreatedEmail extends BaseEmail<
|
||||
InputProps,
|
||||
BeforeSend
|
||||
> {
|
||||
protected get category() {
|
||||
return EmailMessageCategory.Notification;
|
||||
}
|
||||
|
||||
protected async beforeSend(props: InputProps) {
|
||||
const collection = await Collection.scope("withUser").findByPk(
|
||||
props.collectionId
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import * as React from "react";
|
||||
import { CollectionPermission } from "@shared/types";
|
||||
import { Collection, UserMembership } from "@server/models";
|
||||
import BaseEmail, { EmailProps } from "./BaseEmail";
|
||||
import BaseEmail, { EmailMessageCategory, EmailProps } from "./BaseEmail";
|
||||
import Body from "./components/Body";
|
||||
import Button from "./components/Button";
|
||||
import EmailTemplate from "./components/EmailLayout";
|
||||
@@ -29,6 +29,10 @@ export default class CollectionSharedEmail extends BaseEmail<
|
||||
InputProps,
|
||||
BeforeSend
|
||||
> {
|
||||
protected get category() {
|
||||
return EmailMessageCategory.Notification;
|
||||
}
|
||||
|
||||
protected async beforeSend({ userId, collectionId }: InputProps) {
|
||||
const collection = await Collection.findByPk(collectionId);
|
||||
if (!collection) {
|
||||
|
||||
@@ -6,7 +6,7 @@ import HTMLHelper from "@server/models/helpers/HTMLHelper";
|
||||
import NotificationSettingsHelper from "@server/models/helpers/NotificationSettingsHelper";
|
||||
import { ProsemirrorHelper } from "@server/models/helpers/ProsemirrorHelper";
|
||||
import { TextHelper } from "@server/models/helpers/TextHelper";
|
||||
import BaseEmail, { EmailProps } from "./BaseEmail";
|
||||
import BaseEmail, { EmailMessageCategory, EmailProps } from "./BaseEmail";
|
||||
import Body from "./components/Body";
|
||||
import Button from "./components/Button";
|
||||
import Diff from "./components/Diff";
|
||||
@@ -43,6 +43,10 @@ export default class CommentCreatedEmail extends BaseEmail<
|
||||
InputProps,
|
||||
BeforeSend
|
||||
> {
|
||||
protected get category() {
|
||||
return EmailMessageCategory.Notification;
|
||||
}
|
||||
|
||||
protected async beforeSend(props: InputProps) {
|
||||
const { documentId, commentId } = props;
|
||||
const document = await Document.unscoped().findByPk(documentId);
|
||||
|
||||
@@ -6,7 +6,7 @@ import HTMLHelper from "@server/models/helpers/HTMLHelper";
|
||||
import NotificationSettingsHelper from "@server/models/helpers/NotificationSettingsHelper";
|
||||
import { ProsemirrorHelper } from "@server/models/helpers/ProsemirrorHelper";
|
||||
import { TextHelper } from "@server/models/helpers/TextHelper";
|
||||
import BaseEmail, { EmailProps } from "./BaseEmail";
|
||||
import BaseEmail, { EmailMessageCategory, EmailProps } from "./BaseEmail";
|
||||
import Body from "./components/Body";
|
||||
import Button from "./components/Button";
|
||||
import Diff from "./components/Diff";
|
||||
@@ -41,6 +41,10 @@ export default class CommentMentionedEmail extends BaseEmail<
|
||||
InputProps,
|
||||
BeforeSend
|
||||
> {
|
||||
protected get category() {
|
||||
return EmailMessageCategory.Notification;
|
||||
}
|
||||
|
||||
protected async beforeSend(props: InputProps) {
|
||||
const { documentId, commentId } = props;
|
||||
const document = await Document.unscoped().findByPk(documentId);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import * as React from "react";
|
||||
import env from "@server/env";
|
||||
import BaseEmail, { EmailProps } from "./BaseEmail";
|
||||
import BaseEmail, { EmailMessageCategory, EmailProps } from "./BaseEmail";
|
||||
import Body from "./components/Body";
|
||||
import CopyableCode from "./components/CopyableCode";
|
||||
import EmailTemplate from "./components/EmailLayout";
|
||||
@@ -17,6 +17,10 @@ type Props = EmailProps & {
|
||||
* Email sent to a user when they request to delete their workspace.
|
||||
*/
|
||||
export default class ConfirmTeamDeleteEmail extends BaseEmail<Props> {
|
||||
protected get category() {
|
||||
return EmailMessageCategory.Notification;
|
||||
}
|
||||
|
||||
protected subject() {
|
||||
return `Your workspace deletion request`;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import * as React from "react";
|
||||
import env from "@server/env";
|
||||
import BaseEmail, { EmailProps } from "./BaseEmail";
|
||||
import BaseEmail, { EmailMessageCategory, EmailProps } from "./BaseEmail";
|
||||
import Body from "./components/Body";
|
||||
import CopyableCode from "./components/CopyableCode";
|
||||
import EmailTemplate from "./components/EmailLayout";
|
||||
@@ -17,6 +17,10 @@ type Props = EmailProps & {
|
||||
* Email sent to a user when they request to delete their account.
|
||||
*/
|
||||
export default class ConfirmUserDeleteEmail extends BaseEmail<Props> {
|
||||
protected get category() {
|
||||
return EmailMessageCategory.Notification;
|
||||
}
|
||||
|
||||
protected subject() {
|
||||
return `Your account deletion request`;
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ import { DocumentHelper } from "@server/models/helpers/DocumentHelper";
|
||||
import HTMLHelper from "@server/models/helpers/HTMLHelper";
|
||||
import { ProsemirrorHelper } from "@server/models/helpers/ProsemirrorHelper";
|
||||
import { TextHelper } from "@server/models/helpers/TextHelper";
|
||||
import BaseEmail, { EmailProps } from "./BaseEmail";
|
||||
import BaseEmail, { EmailMessageCategory, EmailProps } from "./BaseEmail";
|
||||
import Body from "./components/Body";
|
||||
import Button from "./components/Button";
|
||||
import Diff from "./components/Diff";
|
||||
@@ -37,6 +37,10 @@ export default class DocumentMentionedEmail extends BaseEmail<
|
||||
InputProps,
|
||||
BeforeSend
|
||||
> {
|
||||
protected get category() {
|
||||
return EmailMessageCategory.Notification;
|
||||
}
|
||||
|
||||
protected async beforeSend({ documentId, revisionId, userId }: InputProps) {
|
||||
const document = await Document.unscoped().findByPk(documentId);
|
||||
if (!document) {
|
||||
|
||||
@@ -6,7 +6,7 @@ import { DocumentHelper } from "@server/models/helpers/DocumentHelper";
|
||||
import HTMLHelper from "@server/models/helpers/HTMLHelper";
|
||||
import NotificationSettingsHelper from "@server/models/helpers/NotificationSettingsHelper";
|
||||
import SubscriptionHelper from "@server/models/helpers/SubscriptionHelper";
|
||||
import BaseEmail, { EmailProps } from "./BaseEmail";
|
||||
import BaseEmail, { EmailMessageCategory, EmailProps } from "./BaseEmail";
|
||||
import Body from "./components/Body";
|
||||
import Button from "./components/Button";
|
||||
import Diff from "./components/Diff";
|
||||
@@ -44,6 +44,10 @@ export default class DocumentPublishedOrUpdatedEmail extends BaseEmail<
|
||||
InputProps,
|
||||
BeforeSend
|
||||
> {
|
||||
protected get category() {
|
||||
return EmailMessageCategory.Notification;
|
||||
}
|
||||
|
||||
protected async beforeSend(props: InputProps) {
|
||||
const { documentId, revisionId } = props;
|
||||
const document = await Document.unscoped().findByPk(documentId, {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import * as React from "react";
|
||||
import { DocumentPermission } from "@shared/types";
|
||||
import { Document, UserMembership } from "@server/models";
|
||||
import BaseEmail, { EmailProps } from "./BaseEmail";
|
||||
import BaseEmail, { EmailMessageCategory, EmailProps } from "./BaseEmail";
|
||||
import Body from "./components/Body";
|
||||
import Button from "./components/Button";
|
||||
import EmailTemplate from "./components/EmailLayout";
|
||||
@@ -29,6 +29,10 @@ export default class DocumentSharedEmail extends BaseEmail<
|
||||
InputProps,
|
||||
BeforeSend
|
||||
> {
|
||||
protected get category() {
|
||||
return EmailMessageCategory.Notification;
|
||||
}
|
||||
|
||||
protected async beforeSend({ documentId, userId }: InputProps) {
|
||||
const document = await Document.unscoped().findByPk(documentId);
|
||||
if (!document) {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import * as React from "react";
|
||||
import { NotificationEventType } from "@shared/types";
|
||||
import NotificationSettingsHelper from "@server/models/helpers/NotificationSettingsHelper";
|
||||
import BaseEmail, { EmailProps } from "./BaseEmail";
|
||||
import BaseEmail, { EmailMessageCategory, EmailProps } from "./BaseEmail";
|
||||
import Body from "./components/Body";
|
||||
import Button from "./components/Button";
|
||||
import EmailTemplate from "./components/EmailLayout";
|
||||
@@ -29,6 +29,10 @@ export default class ExportFailureEmail extends BaseEmail<
|
||||
InputProps,
|
||||
BeforeSend
|
||||
> {
|
||||
protected get category() {
|
||||
return EmailMessageCategory.Notification;
|
||||
}
|
||||
|
||||
protected async beforeSend(props: InputProps) {
|
||||
return {
|
||||
unsubscribeUrl: this.unsubscribeUrl(props),
|
||||
|
||||
@@ -2,7 +2,7 @@ import * as React from "react";
|
||||
import { NotificationEventType } from "@shared/types";
|
||||
import env from "@server/env";
|
||||
import NotificationSettingsHelper from "@server/models/helpers/NotificationSettingsHelper";
|
||||
import BaseEmail, { EmailProps } from "./BaseEmail";
|
||||
import BaseEmail, { EmailMessageCategory, EmailProps } from "./BaseEmail";
|
||||
import Body from "./components/Body";
|
||||
import Button from "./components/Button";
|
||||
import EmailTemplate from "./components/EmailLayout";
|
||||
@@ -32,6 +32,10 @@ export default class ExportSuccessEmail extends BaseEmail<
|
||||
InputProps,
|
||||
BeforeSend
|
||||
> {
|
||||
protected get category() {
|
||||
return EmailMessageCategory.Notification;
|
||||
}
|
||||
|
||||
protected async beforeSend(props: InputProps) {
|
||||
return {
|
||||
unsubscribeUrl: this.unsubscribeUrl(props),
|
||||
|
||||
@@ -2,7 +2,7 @@ import * as React from "react";
|
||||
import { NotificationEventType } from "@shared/types";
|
||||
import env from "@server/env";
|
||||
import NotificationSettingsHelper from "@server/models/helpers/NotificationSettingsHelper";
|
||||
import BaseEmail, { EmailProps } from "./BaseEmail";
|
||||
import BaseEmail, { EmailMessageCategory, EmailProps } from "./BaseEmail";
|
||||
import Body from "./components/Body";
|
||||
import Button from "./components/Button";
|
||||
import EmailTemplate from "./components/EmailLayout";
|
||||
@@ -30,6 +30,10 @@ export default class InviteAcceptedEmail extends BaseEmail<
|
||||
InputProps,
|
||||
BeforeSend
|
||||
> {
|
||||
protected get category() {
|
||||
return EmailMessageCategory.Notification;
|
||||
}
|
||||
|
||||
protected async beforeSend(props: InputProps) {
|
||||
return {
|
||||
unsubscribeUrl: this.unsubscribeUrl(props),
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import * as React from "react";
|
||||
import env from "@server/env";
|
||||
import BaseEmail, { EmailProps } from "./BaseEmail";
|
||||
import BaseEmail, { EmailMessageCategory, EmailProps } from "./BaseEmail";
|
||||
import Body from "./components/Body";
|
||||
import Button from "./components/Button";
|
||||
import EmailTemplate from "./components/EmailLayout";
|
||||
@@ -21,6 +21,10 @@ type Props = EmailProps & {
|
||||
* Email sent to an external user when an admin sends them an invite.
|
||||
*/
|
||||
export default class InviteEmail extends BaseEmail<Props, Record<string, any>> {
|
||||
protected get category() {
|
||||
return EmailMessageCategory.Invitation;
|
||||
}
|
||||
|
||||
protected subject({ actorName, teamName }: Props) {
|
||||
return `${actorName} invited you to join ${teamName}’s workspace`;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import * as React from "react";
|
||||
import env from "@server/env";
|
||||
import BaseEmail, { EmailProps } from "./BaseEmail";
|
||||
import BaseEmail, { EmailMessageCategory, EmailProps } from "./BaseEmail";
|
||||
import Body from "./components/Body";
|
||||
import Button from "./components/Button";
|
||||
import EmailTemplate from "./components/EmailLayout";
|
||||
@@ -22,6 +22,10 @@ type Props = EmailProps & {
|
||||
* haven't signed in after a few days.
|
||||
*/
|
||||
export default class InviteReminderEmail extends BaseEmail<Props> {
|
||||
protected get category() {
|
||||
return EmailMessageCategory.Invitation;
|
||||
}
|
||||
|
||||
protected subject({ actorName, teamName }: Props) {
|
||||
return `Reminder: ${actorName} invited you to join ${teamName}’s workspace`;
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ import * as React from "react";
|
||||
import { Client } from "@shared/types";
|
||||
import env from "@server/env";
|
||||
import logger from "@server/logging/Logger";
|
||||
import BaseEmail, { EmailProps } from "./BaseEmail";
|
||||
import BaseEmail, { EmailMessageCategory, EmailProps } from "./BaseEmail";
|
||||
import Body from "./components/Body";
|
||||
import Button from "./components/Button";
|
||||
import EmailTemplate from "./components/EmailLayout";
|
||||
@@ -21,6 +21,10 @@ type Props = EmailProps & {
|
||||
* Email sent to a user when they request a magic sign-in link.
|
||||
*/
|
||||
export default class SigninEmail extends BaseEmail<Props, Record<string, any>> {
|
||||
protected get category() {
|
||||
return EmailMessageCategory.Authentication;
|
||||
}
|
||||
|
||||
protected subject() {
|
||||
return "Magic signin link";
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import * as React from "react";
|
||||
import BaseEmail, { EmailProps } from "./BaseEmail";
|
||||
import BaseEmail, { EmailMessageCategory, EmailProps } from "./BaseEmail";
|
||||
import Body from "./components/Body";
|
||||
import Button from "./components/Button";
|
||||
import EmailTemplate from "./components/EmailLayout";
|
||||
@@ -18,6 +18,10 @@ type Props = EmailProps & {
|
||||
* due to repeated failure.
|
||||
*/
|
||||
export default class WebhookDisabledEmail extends BaseEmail<Props> {
|
||||
protected get category() {
|
||||
return EmailMessageCategory.Notification;
|
||||
}
|
||||
|
||||
protected subject() {
|
||||
return `Warning: Webhook disabled`;
|
||||
}
|
||||
@@ -28,7 +32,7 @@ export default class WebhookDisabledEmail extends BaseEmail<Props> {
|
||||
|
||||
protected renderAsText({ webhookName, teamUrl }: Props): string {
|
||||
return `
|
||||
Your webhook (${webhookName}) has been automatically disabled as the last 25
|
||||
Your webhook (${webhookName}) has been automatically disabled as the last 25
|
||||
delivery attempts have failed. You can re-enable by editing the webhook.
|
||||
|
||||
Webhook settings: ${teamUrl}/settings/webhooks
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import * as React from "react";
|
||||
import { UserRole } from "@shared/types";
|
||||
import env from "@server/env";
|
||||
import BaseEmail, { EmailProps } from "./BaseEmail";
|
||||
import BaseEmail, { EmailMessageCategory, EmailProps } from "./BaseEmail";
|
||||
import Body from "./components/Body";
|
||||
import Button from "./components/Button";
|
||||
import EmailTemplate from "./components/EmailLayout";
|
||||
@@ -22,6 +22,10 @@ type BeforeSend = Record<string, never>;
|
||||
* in for the first time from an invite.
|
||||
*/
|
||||
export default class WelcomeEmail extends BaseEmail<Props, BeforeSend> {
|
||||
protected get category() {
|
||||
return EmailMessageCategory.Notification;
|
||||
}
|
||||
|
||||
protected subject() {
|
||||
return `Welcome to ${env.APP_NAME}`;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user