Go-To Actions with transactions emails (#5728)
* feat: go-to actions for emails * fix: comment * fix: tsc without previewText * fix: goToAction * fix: link to original template * fix: final comments
This commit is contained in:
@@ -56,6 +56,64 @@ export class Mailer {
|
||||
}
|
||||
}
|
||||
|
||||
template = ({
|
||||
title,
|
||||
bodyContent,
|
||||
headCSS = "",
|
||||
bgColor = "#FFFFFF",
|
||||
lang,
|
||||
dir = "ltr" /* https://www.w3.org/TR/html4/struct/dirlang.html#blocklevel-bidi */,
|
||||
}: Oy.CustomTemplateRenderOptions) => {
|
||||
if (!title) {
|
||||
throw new Error("`title` is a required option for `renderTemplate`");
|
||||
} else if (!bodyContent) {
|
||||
throw new Error(
|
||||
"`bodyContent` is a required option for `renderTemplate`"
|
||||
);
|
||||
}
|
||||
|
||||
// the template below is a slightly modified form of https://github.com/revivek/oy/blob/master/src/utils/HTML4.js
|
||||
return `
|
||||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
|
||||
<html
|
||||
${lang ? 'lang="' + lang + '"' : ""}
|
||||
dir="${dir}"
|
||||
xmlns="http://www.w3.org/1999/xhtml"
|
||||
xmlns:v="urn:schemas-microsoft-com:vml"
|
||||
xmlns:o="urn:schemas-microsoft-com:office:office">
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||
<meta name="viewport" content="width=device-width"/>
|
||||
|
||||
<title>${title}</title>
|
||||
|
||||
<style type="text/css">
|
||||
${headCSS}
|
||||
|
||||
#__bodyTable__ {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
width: 100% !important;
|
||||
}
|
||||
</style>
|
||||
|
||||
<!--[if gte mso 9]>
|
||||
<xml>
|
||||
<o:OfficeDocumentSettings>
|
||||
<o:AllowPNG/>
|
||||
<o:PixelsPerInch>96</o:PixelsPerInch>
|
||||
</o:OfficeDocumentSettings>
|
||||
</xml>
|
||||
<![endif]-->
|
||||
</head>
|
||||
<body bgcolor="${bgColor}" width="100%" style="-webkit-font-smoothing: antialiased; width:100% !important; background:${bgColor};-webkit-text-size-adjust:none; margin:0; padding:0; min-width:100%; direction: ${dir};">
|
||||
${bodyContent}
|
||||
</body>
|
||||
</html>
|
||||
`;
|
||||
};
|
||||
|
||||
sendMail = async (data: SendMailOptions): Promise<void> => {
|
||||
const { transporter } = this;
|
||||
|
||||
@@ -67,11 +125,14 @@ export class Mailer {
|
||||
return;
|
||||
}
|
||||
|
||||
const html = Oy.renderTemplate(data.component, {
|
||||
title: data.subject,
|
||||
headCSS: [baseStyles, data.headCSS].join(" "),
|
||||
previewText: data.previewText ?? "",
|
||||
});
|
||||
const html = Oy.renderTemplate(
|
||||
data.component,
|
||||
{
|
||||
title: data.subject,
|
||||
headCSS: [baseStyles, data.headCSS].join(" "),
|
||||
} as Oy.RenderOptions,
|
||||
this.template
|
||||
);
|
||||
|
||||
try {
|
||||
Logger.info("email", `Sending email "${data.subject}" to ${data.to}`);
|
||||
|
||||
@@ -68,8 +68,13 @@ Open Collection: ${teamUrl}${collection.url}
|
||||
}
|
||||
|
||||
protected render({ collection, teamUrl, unsubscribeUrl }: Props) {
|
||||
const collectionLink = `${teamUrl}${collection.url}`;
|
||||
|
||||
return (
|
||||
<EmailTemplate>
|
||||
<EmailTemplate
|
||||
previewText={this.preview({ collection } as Props)}
|
||||
goToAction={{ url: collectionLink, name: "View Collection" }}
|
||||
>
|
||||
<Header />
|
||||
|
||||
<Body>
|
||||
@@ -80,9 +85,7 @@ Open Collection: ${teamUrl}${collection.url}
|
||||
</p>
|
||||
<EmptySpace height={10} />
|
||||
<p>
|
||||
<Button href={`${teamUrl}${collection.url}`}>
|
||||
Open Collection
|
||||
</Button>
|
||||
<Button href={collectionLink}>Open Collection</Button>
|
||||
</p>
|
||||
</Body>
|
||||
|
||||
|
||||
@@ -147,17 +147,20 @@ Open Thread: ${teamUrl}${document.url}?commentId=${commentId}
|
||||
unsubscribeUrl,
|
||||
body,
|
||||
}: Props) {
|
||||
const link = `${teamUrl}${document.url}?commentId=${commentId}&ref=notification-email`;
|
||||
const threadLink = `${teamUrl}${document.url}?commentId=${commentId}&ref=notification-email`;
|
||||
|
||||
return (
|
||||
<EmailTemplate>
|
||||
<EmailTemplate
|
||||
previewText={this.preview({ isReply, actorName } as Props)}
|
||||
goToAction={{ url: threadLink, name: "View Thread" }}
|
||||
>
|
||||
<Header />
|
||||
|
||||
<Body>
|
||||
<Heading>{document.title}</Heading>
|
||||
<p>
|
||||
{actorName} {isReply ? "replied to a thread in" : "commented on"}{" "}
|
||||
<a href={link}>{document.title}</a>{" "}
|
||||
<a href={threadLink}>{document.title}</a>{" "}
|
||||
{collection.name ? `in the ${collection.name} collection` : ""}.
|
||||
</p>
|
||||
{body && (
|
||||
@@ -170,7 +173,7 @@ Open Thread: ${teamUrl}${document.url}?commentId=${commentId}
|
||||
</>
|
||||
)}
|
||||
<p>
|
||||
<Button href={link}>Open Thread</Button>
|
||||
<Button href={threadLink}>Open Thread</Button>
|
||||
</p>
|
||||
</Body>
|
||||
|
||||
|
||||
@@ -130,17 +130,20 @@ Open Thread: ${teamUrl}${document.url}?commentId=${commentId}
|
||||
unsubscribeUrl,
|
||||
body,
|
||||
}: Props) {
|
||||
const link = `${teamUrl}${document.url}?commentId=${commentId}&ref=notification-email`;
|
||||
const threadLink = `${teamUrl}${document.url}?commentId=${commentId}&ref=notification-email`;
|
||||
|
||||
return (
|
||||
<EmailTemplate>
|
||||
<EmailTemplate
|
||||
previewText={this.preview({ actorName } as Props)}
|
||||
goToAction={{ url: threadLink, name: "View Thread" }}
|
||||
>
|
||||
<Header />
|
||||
|
||||
<Body>
|
||||
<Heading>{document.title}</Heading>
|
||||
<p>
|
||||
{actorName} mentioned you in a comment on{" "}
|
||||
<a href={link}>{document.title}</a>{" "}
|
||||
<a href={threadLink}>{document.title}</a>{" "}
|
||||
{collection.name ? `in the ${collection.name} collection` : ""}.
|
||||
</p>
|
||||
{body && (
|
||||
@@ -153,7 +156,7 @@ Open Thread: ${teamUrl}${document.url}?commentId=${commentId}
|
||||
</>
|
||||
)}
|
||||
<p>
|
||||
<Button href={link}>Open Thread</Button>
|
||||
<Button href={threadLink}>Open Thread</Button>
|
||||
</p>
|
||||
</Body>
|
||||
|
||||
|
||||
@@ -38,7 +38,7 @@ Code: ${deleteConfirmationCode}
|
||||
|
||||
protected render({ deleteConfirmationCode }: Props) {
|
||||
return (
|
||||
<EmailTemplate>
|
||||
<EmailTemplate previewText={this.preview()}>
|
||||
<Header />
|
||||
|
||||
<Body>
|
||||
|
||||
@@ -58,20 +58,23 @@ Open Document: ${teamUrl}${document.url}
|
||||
}
|
||||
|
||||
protected render({ document, actorName, teamUrl }: Props) {
|
||||
const link = `${teamUrl}${document.url}?ref=notification-email`;
|
||||
const documentLink = `${teamUrl}${document.url}?ref=notification-email`;
|
||||
|
||||
return (
|
||||
<EmailTemplate>
|
||||
<EmailTemplate
|
||||
previewText={this.preview({ actorName } as Props)}
|
||||
goToAction={{ url: documentLink, name: "View Document" }}
|
||||
>
|
||||
<Header />
|
||||
|
||||
<Body>
|
||||
<Heading>You were mentioned</Heading>
|
||||
<p>
|
||||
{actorName} mentioned you in the document{" "}
|
||||
<a href={link}>{document.title}</a>.
|
||||
<a href={documentLink}>{document.title}</a>.
|
||||
</p>
|
||||
<p>
|
||||
<Button href={link}>Open Document</Button>
|
||||
<Button href={documentLink}>Open Document</Button>
|
||||
</p>
|
||||
</Body>
|
||||
</EmailTemplate>
|
||||
|
||||
@@ -144,11 +144,14 @@ Open Document: ${teamUrl}${document.url}
|
||||
unsubscribeUrl,
|
||||
body,
|
||||
}: Props) {
|
||||
const link = `${teamUrl}${document.url}?ref=notification-email`;
|
||||
const documentLink = `${teamUrl}${document.url}?ref=notification-email`;
|
||||
const eventName = this.eventName(eventType);
|
||||
|
||||
return (
|
||||
<EmailTemplate>
|
||||
<EmailTemplate
|
||||
previewText={this.preview({ actorName, eventType } as Props)}
|
||||
goToAction={{ url: documentLink, name: "View Document" }}
|
||||
>
|
||||
<Header />
|
||||
|
||||
<Body>
|
||||
@@ -157,8 +160,8 @@ Open Document: ${teamUrl}${document.url}
|
||||
</Heading>
|
||||
<p>
|
||||
{actorName} {eventName} the document{" "}
|
||||
<a href={link}>{document.title}</a>, in the {collection.name}{" "}
|
||||
collection.
|
||||
<a href={documentLink}>{document.title}</a>, in the{" "}
|
||||
{collection.name} collection.
|
||||
</p>
|
||||
{body && (
|
||||
<>
|
||||
@@ -170,7 +173,7 @@ Open Document: ${teamUrl}${document.url}
|
||||
</>
|
||||
)}
|
||||
<p>
|
||||
<Button href={link}>Open Document</Button>
|
||||
<Button href={documentLink}>Open Document</Button>
|
||||
</p>
|
||||
</Body>
|
||||
|
||||
|
||||
@@ -55,25 +55,23 @@ section to try again – if the problem persists please contact support.
|
||||
}
|
||||
|
||||
protected render({ teamUrl, unsubscribeUrl }: Props & BeforeSendProps) {
|
||||
const exportLink = `${teamUrl}/settings/export`;
|
||||
|
||||
return (
|
||||
<EmailTemplate>
|
||||
<EmailTemplate previewText={this.preview()}>
|
||||
<Header />
|
||||
<Body>
|
||||
<Heading>Your Data Export</Heading>
|
||||
<p>
|
||||
Sorry, your requested data export has failed, please visit the{" "}
|
||||
<a
|
||||
href={`${teamUrl}/settings/export`}
|
||||
rel="noreferrer"
|
||||
target="_blank"
|
||||
>
|
||||
<a href={exportLink} rel="noreferrer" target="_blank">
|
||||
admin section
|
||||
</a>
|
||||
. to try again – if the problem persists please contact support.
|
||||
</p>
|
||||
<EmptySpace height={10} />
|
||||
<p>
|
||||
<Button href={`${teamUrl}/settings/export`}>Go to export</Button>
|
||||
<Button href={exportLink}>Go to export</Button>
|
||||
</p>
|
||||
</Body>
|
||||
<Footer unsubscribeUrl={unsubscribeUrl} />
|
||||
|
||||
@@ -57,8 +57,13 @@ Your requested data export is complete, the exported files are also available in
|
||||
}
|
||||
|
||||
protected render({ id, teamUrl, unsubscribeUrl }: Props & BeforeSendProps) {
|
||||
const downloadLink = `${teamUrl}/api/fileOperations.redirect?id=${id}`;
|
||||
|
||||
return (
|
||||
<EmailTemplate>
|
||||
<EmailTemplate
|
||||
previewText={this.preview()}
|
||||
goToAction={{ url: downloadLink, name: "Download export" }}
|
||||
>
|
||||
<Header />
|
||||
|
||||
<Body>
|
||||
@@ -77,9 +82,7 @@ Your requested data export is complete, the exported files are also available in
|
||||
</p>
|
||||
<EmptySpace height={10} />
|
||||
<p>
|
||||
<Button href={`${teamUrl}/api/fileOperations.redirect?id=${id}`}>
|
||||
Download
|
||||
</Button>
|
||||
<Button href={downloadLink}>Download</Button>
|
||||
</p>
|
||||
</Body>
|
||||
|
||||
|
||||
@@ -60,7 +60,7 @@ Open ${env.APP_NAME}: ${teamUrl}
|
||||
unsubscribeUrl,
|
||||
}: Props & BeforeSendProps) {
|
||||
return (
|
||||
<EmailTemplate>
|
||||
<EmailTemplate previewText={this.preview({ invitedName } as Props)}>
|
||||
<Header />
|
||||
|
||||
<Body>
|
||||
|
||||
@@ -47,8 +47,10 @@ Join now: ${teamUrl}
|
||||
}
|
||||
|
||||
protected render({ teamName, actorName, actorEmail, teamUrl }: Props) {
|
||||
const inviteLink = `${teamUrl}?ref=invite-email`;
|
||||
|
||||
return (
|
||||
<EmailTemplate>
|
||||
<EmailTemplate previewText={this.preview()}>
|
||||
<Header />
|
||||
|
||||
<Body>
|
||||
@@ -62,7 +64,7 @@ Join now: ${teamUrl}
|
||||
</p>
|
||||
<EmptySpace height={10} />
|
||||
<p>
|
||||
<Button href={`${teamUrl}?ref=invite-email`}>Join now</Button>
|
||||
<Button href={inviteLink}>Join now</Button>
|
||||
</p>
|
||||
</Body>
|
||||
|
||||
|
||||
@@ -52,8 +52,9 @@ If you haven't signed up yet, you can do so here: ${teamUrl}
|
||||
}
|
||||
|
||||
protected render({ teamName, actorName, actorEmail, teamUrl }: Props) {
|
||||
const inviteLink = `${teamUrl}?ref=invite-reminder-email`;
|
||||
return (
|
||||
<EmailTemplate>
|
||||
<EmailTemplate previewText={this.preview()}>
|
||||
<Header />
|
||||
|
||||
<Body>
|
||||
@@ -69,9 +70,7 @@ If you haven't signed up yet, you can do so here: ${teamUrl}
|
||||
<p>If you haven't signed up yet, you can do so here:</p>
|
||||
<EmptySpace height={10} />
|
||||
<p>
|
||||
<Button href={`${teamUrl}?ref=invite-reminder-email`}>
|
||||
Join now
|
||||
</Button>
|
||||
<Button href={inviteLink}>Join now</Button>
|
||||
</p>
|
||||
</Body>
|
||||
|
||||
|
||||
@@ -46,7 +46,10 @@ signin page at: ${teamUrl}
|
||||
}
|
||||
|
||||
return (
|
||||
<EmailTemplate>
|
||||
<EmailTemplate
|
||||
previewText={this.preview()}
|
||||
goToAction={{ url: this.signinLink(token, client), name: "Sign In" }}
|
||||
>
|
||||
<Header />
|
||||
|
||||
<Body>
|
||||
|
||||
@@ -39,8 +39,9 @@ Webhook settings: ${teamUrl}/settings/webhooks
|
||||
}
|
||||
|
||||
protected render({ webhookName, teamUrl }: Props) {
|
||||
const webhookSettingsLink = `${teamUrl}/settings/webhooks`;
|
||||
return (
|
||||
<EmailTemplate>
|
||||
<EmailTemplate previewText={this.preview({ webhookName } as Props)}>
|
||||
<Header />
|
||||
|
||||
<Body>
|
||||
@@ -52,9 +53,7 @@ Webhook settings: ${teamUrl}/settings/webhooks
|
||||
</p>
|
||||
<EmptySpace height={10} />
|
||||
<p>
|
||||
<Button href={teamUrl + "/settings/webhooks"}>
|
||||
Webhook settings
|
||||
</Button>
|
||||
<Button href={webhookSettingsLink}>Webhook settings</Button>
|
||||
</p>
|
||||
</Body>
|
||||
|
||||
|
||||
@@ -44,8 +44,10 @@ ${teamUrl}/home
|
||||
}
|
||||
|
||||
protected render({ teamUrl }: Props) {
|
||||
const welcomLink = `${teamUrl}/home?ref=welcome-email`;
|
||||
|
||||
return (
|
||||
<EmailTemplate>
|
||||
<EmailTemplate previewText={this.preview()}>
|
||||
<Header />
|
||||
|
||||
<Body>
|
||||
@@ -65,9 +67,7 @@ ${teamUrl}/home
|
||||
</p>
|
||||
<EmptySpace height={10} />
|
||||
<p>
|
||||
<Button href={`${teamUrl}/home?ref=welcome-email`}>
|
||||
Open {env.APP_NAME}
|
||||
</Button>
|
||||
<Button href={welcomLink}>Open {env.APP_NAME}</Button>
|
||||
</p>
|
||||
</Body>
|
||||
|
||||
|
||||
@@ -2,15 +2,72 @@ import { Table, TBody, TR, TD } from "oy-vey";
|
||||
import * as React from "react";
|
||||
import theme from "@shared/styles/theme";
|
||||
|
||||
const EmailLayout: React.FC = ({ children }) => (
|
||||
<Table width="550">
|
||||
<TBody>
|
||||
<TR>
|
||||
<TD align="left">{children}</TD>
|
||||
</TR>
|
||||
</TBody>
|
||||
</Table>
|
||||
);
|
||||
const EmailLayout: React.FC<{
|
||||
bgcolor?: string;
|
||||
previewText: string;
|
||||
goToAction?: { url: string; name: string };
|
||||
}> = ({ previewText, bgcolor = "#FFFFFF", goToAction, children }) => {
|
||||
let markup;
|
||||
if (goToAction) {
|
||||
markup = JSON.stringify({
|
||||
"@context": "http://schema.org",
|
||||
"@type": "EmailMessage",
|
||||
potentialAction: {
|
||||
"@type": "ViewAction",
|
||||
url: goToAction.url,
|
||||
name: goToAction.name,
|
||||
},
|
||||
});
|
||||
}
|
||||
return (
|
||||
<>
|
||||
{markup ? (
|
||||
<script
|
||||
type="application/ld+json"
|
||||
dangerouslySetInnerHTML={{ __html: markup }}
|
||||
/>
|
||||
) : null}
|
||||
<Table
|
||||
bgcolor={bgcolor}
|
||||
id="__bodyTable__"
|
||||
width="100%"
|
||||
style={{
|
||||
WebkitFontSmoothing: "antialiased",
|
||||
width: "100% !important",
|
||||
background: `${bgcolor}`,
|
||||
WebkitTextSizeAdjust: "none",
|
||||
margin: 0,
|
||||
padding: 0,
|
||||
minWidth: "100%",
|
||||
}}
|
||||
>
|
||||
<TR>
|
||||
<TD align="center">
|
||||
<span
|
||||
style={{
|
||||
display: "none !important",
|
||||
color: `${bgcolor}`,
|
||||
margin: 0,
|
||||
padding: 0,
|
||||
fontSize: "1px",
|
||||
lineHeight: "1px",
|
||||
}}
|
||||
>
|
||||
{previewText}
|
||||
</span>
|
||||
<Table width="550">
|
||||
<TBody>
|
||||
<TR>
|
||||
<TD align="left">{children}</TD>
|
||||
</TR>
|
||||
</TBody>
|
||||
</Table>
|
||||
</TD>
|
||||
</TR>
|
||||
</Table>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default EmailLayout;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user