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:
Apoorv Mishra
2023-08-23 18:43:52 +05:30
committed by GitHub
parent e3ba87dcb0
commit 53c6c5599a
16 changed files with 200 additions and 63 deletions

View File

@@ -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}`);

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -38,7 +38,7 @@ Code: ${deleteConfirmationCode}
protected render({ deleteConfirmationCode }: Props) {
return (
<EmailTemplate>
<EmailTemplate previewText={this.preview()}>
<Header />
<Body>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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} />

View File

@@ -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>

View File

@@ -60,7 +60,7 @@ Open ${env.APP_NAME}: ${teamUrl}
unsubscribeUrl,
}: Props & BeforeSendProps) {
return (
<EmailTemplate>
<EmailTemplate previewText={this.preview({ invitedName } as Props)}>
<Header />
<Body>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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;