This repository has been archived on 2022-08-14. You can view files and clone it, but cannot push or open issues or pull requests.
outline/server/mailer.js

251 lines
6.6 KiB
JavaScript
Raw Normal View History

// @flow
import * as Sentry from "@sentry/node";
import debug from "debug";
import nodemailer from "nodemailer";
import Oy from "oy-vey";
import * as React from "react";
import {
type Props as CollectionNotificationEmailT,
CollectionNotificationEmail,
collectionNotificationEmailText,
} from "./emails/CollectionNotificationEmail";
import {
type Props as DocumentNotificationEmailT,
DocumentNotificationEmail,
documentNotificationEmailText,
} from "./emails/DocumentNotificationEmail";
import { ExportEmail, exportEmailText } from "./emails/ExportEmail";
import {
type Props as InviteEmailT,
InviteEmail,
inviteEmailText,
} from "./emails/InviteEmail";
import { SigninEmail, signinEmailText } from "./emails/SigninEmail";
import { WelcomeEmail, welcomeEmailText } from "./emails/WelcomeEmail";
import { baseStyles } from "./emails/components/EmailLayout";
import { createQueue } from "./utils/queue";
const log = debug("emails");
const useTestEmailService =
process.env.NODE_ENV === "development" && !process.env.SMTP_USERNAME;
type Emails = "welcome" | "export";
type SendMailType = {
to: string,
properties?: any,
title: string,
previewText?: string,
text: string,
2018-05-05 23:16:08 +00:00
html: React.Node,
headCSS?: string,
attachments?: Object[],
};
type EmailJob = {
data: {
type: Emails,
opts: SendMailType,
},
};
/**
* Mailer
*
* Mailer class to contruct and send emails.
*
2017-11-13 00:35:23 +00:00
* To preview emails, add a new preview to `emails/index.js` if they
* require additional data (properties). Otherwise preview will work automatically.
*
* HTML: http://localhost:3000/email/:email_type/html
* TEXT: http://localhost:3000/email/:email_type/text
*/
export class Mailer {
transporter: ?any;
sendMail = async (data: SendMailType): ?Promise<*> => {
2017-11-13 00:35:23 +00:00
const { transporter } = this;
if (transporter) {
const html = Oy.renderTemplate(data.html, {
title: data.title,
headCSS: [baseStyles, data.headCSS].join(" "),
previewText: data.previewText,
});
try {
log(`Sending email "${data.title}" to ${data.to}`);
const info = await transporter.sendMail({
from: process.env.SMTP_FROM_EMAIL,
replyTo: process.env.SMTP_REPLY_EMAIL || process.env.SMTP_FROM_EMAIL,
to: data.to,
subject: data.title,
html: html,
text: data.text,
attachments: data.attachments,
});
if (useTestEmailService) {
log("Email Preview URL: %s", nodemailer.getTestMessageUrl(info));
}
} catch (err) {
if (process.env.SENTRY_DSN) {
Sentry.captureException(err);
}
throw err; // Re-throw for queue to re-try
}
}
};
welcome = async (opts: { to: string, teamUrl: string }) => {
this.sendMail({
2017-12-19 04:47:48 +00:00
to: opts.to,
title: "Welcome to Outline",
previewText:
"Outline is a place for your team to build and share knowledge.",
html: <WelcomeEmail {...opts} />,
text: welcomeEmailText(opts),
});
};
export = async (opts: { to: string, attachments: Object[] }) => {
this.sendMail({
to: opts.to,
attachments: opts.attachments,
title: "Your requested export",
previewText: "Here's your request data export from Outline",
html: <ExportEmail />,
text: exportEmailText,
});
};
invite = async (opts: { to: string } & InviteEmailT) => {
this.sendMail({
to: opts.to,
title: `${opts.actorName} invited you to join ${opts.teamName}s knowledge base`,
previewText:
"Outline is a place for your team to build and share knowledge.",
html: <InviteEmail {...opts} />,
text: inviteEmailText(opts),
});
};
signin = async (opts: { to: string, token: string, teamUrl: string }) => {
this.sendMail({
to: opts.to,
title: "Magic signin link",
previewText: "Heres your link to signin to Outline.",
html: <SigninEmail {...opts} />,
text: signinEmailText(opts),
});
};
documentNotification = async (
opts: { to: string } & DocumentNotificationEmailT
) => {
this.sendMail({
to: opts.to,
title: `${opts.document.title}${opts.eventName}`,
previewText: `${opts.actor.name} ${opts.eventName} a new document`,
html: <DocumentNotificationEmail {...opts} />,
text: documentNotificationEmailText(opts),
});
};
collectionNotification = async (
opts: { to: string } & CollectionNotificationEmailT
) => {
this.sendMail({
to: opts.to,
title: `${opts.collection.name}${opts.eventName}`,
previewText: `${opts.actor.name} ${opts.eventName} a collection`,
html: <CollectionNotificationEmail {...opts} />,
text: collectionNotificationEmailText(opts),
});
};
constructor() {
this.loadTransport();
}
async loadTransport() {
if (process.env.SMTP_HOST) {
let smtpConfig = {
host: process.env.SMTP_HOST,
port: process.env.SMTP_PORT,
secure:
"SMTP_SECURE" in process.env
? process.env.SMTP_SECURE === "true"
: process.env.NODE_ENV === "production",
auth: undefined,
tls:
"SMTP_TLS_CIPHERS" in process.env
? { ciphers: process.env.SMTP_TLS_CIPHERS }
: undefined,
};
if (process.env.SMTP_USERNAME) {
smtpConfig.auth = {
user: process.env.SMTP_USERNAME,
pass: process.env.SMTP_PASSWORD,
};
}
this.transporter = nodemailer.createTransport(smtpConfig);
return;
}
if (useTestEmailService) {
log("SMTP_USERNAME not provided, generating test account…");
2021-06-22 04:40:28 +00:00
try {
let testAccount = await nodemailer.createTestAccount();
const smtpConfig = {
host: "smtp.ethereal.email",
port: 587,
secure: false,
auth: {
user: testAccount.user,
pass: testAccount.pass,
},
};
this.transporter = nodemailer.createTransport(smtpConfig);
} catch (err) {
log(`Could not generate test account: ${err.message}`);
}
}
}
}
const mailer = new Mailer();
export default mailer;
export const mailerQueue = createQueue("email");
2017-12-19 04:47:48 +00:00
mailerQueue.process(async (job: EmailJob) => {
2017-12-19 04:47:48 +00:00
// $FlowIssue flow doesn't like dynamic values
await mailer[job.data.type](job.data.opts);
});
export const sendEmail = (type: Emails, to: string, options?: Object = {}) => {
2017-12-19 04:47:48 +00:00
mailerQueue.add(
{
type,
opts: {
to,
...options,
},
},
{
attempts: 5,
removeOnComplete: true,
2017-12-19 04:47:48 +00:00
backoff: {
type: "exponential",
2017-12-19 04:47:48 +00:00
delay: 60 * 1000,
},
}
);
};