feat: clean up email generation

This commit is contained in:
limes 2025-11-10 02:14:13 +01:00
parent e3fcd8f47f
commit 2e9ac5c49c
6 changed files with 279 additions and 305 deletions

8
package-lock.json generated
View File

@ -2381,6 +2381,7 @@
"resolved": "https://registry.npmjs.org/@redis/client/-/client-1.6.1.tgz",
"integrity": "sha512-/KCsg3xSlR+nCK8/8ZYSknYxvXHwubJrU82F3Lm1Fp6789VQ0/3RJKfsmRXjqfaTA++23CvC3hqmqe/2GEt6Kw==",
"license": "MIT",
"peer": true,
"dependencies": {
"cluster-key-slot": "1.1.2",
"generic-pool": "3.9.0",
@ -3419,6 +3420,7 @@
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.112.tgz",
"integrity": "sha512-i+Vukt9POdS/MBI7YrrkkI5fMfwFtOjphSmt4WXYLfwqsfr6z/HdCx7LqT9M7JktGob8WNgj8nFB4TbGNE4Cog==",
"license": "MIT",
"peer": true,
"dependencies": {
"undici-types": "~5.26.4"
}
@ -3564,6 +3566,7 @@
"integrity": "sha512-4O3idHxhyzjClSMJ0a29AcoK0+YwnEqzI6oz3vlRf3xw0zbzt15MzXwItOlnr5nIth6zlY2RENLsOPvhyrKAQA==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@typescript-eslint/scope-manager": "8.34.1",
"@typescript-eslint/types": "8.34.1",
@ -4049,6 +4052,7 @@
"integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
"dev": true,
"license": "MIT",
"peer": true,
"bin": {
"acorn": "bin/acorn"
},
@ -5842,6 +5846,7 @@
"integrity": "sha512-GsGizj2Y1rCWDu6XoEekL3RLilp0voSePurjZIkxL3wlm5o5EC9VpgaP7lrCvjnkuLvzFBQWB3vWB3K5KQTveQ==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@eslint-community/eslint-utils": "^4.2.0",
"@eslint-community/regexpp": "^4.12.1",
@ -5988,6 +5993,7 @@
"integrity": "sha512-ixmkI62Rbc2/w8Vfxyh1jQRTdRTF52VxwRVHl/ykPAmqG+Nb7/kNn+byLP0LxPgI7zWA16Jt82SybJInmMia3A==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@rtsao/scc": "^1.1.0",
"array-includes": "^3.1.8",
@ -8416,6 +8422,7 @@
"resolved": "https://registry.npmjs.org/mongoose/-/mongoose-7.8.7.tgz",
"integrity": "sha512-5Bo4CrUxrPITrhMKsqUTOkXXo2CoRC5tXxVQhnddCzqDMwRXfyStrxj1oY865g8gaekSBhxAeNkYyUSJvGm9Hw==",
"license": "MIT",
"peer": true,
"dependencies": {
"bson": "^5.5.0",
"kareem": "2.5.1",
@ -10654,6 +10661,7 @@
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz",
"integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==",
"license": "Apache-2.0",
"peer": true,
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"

View File

@ -1,203 +0,0 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd" lang="en">
<html lang="en">
<head>
<meta name="color-scheme" content="light dark">
<meta http-equiv="Content-Type" content="text/html charset=UTF-8" />
<style>
@import url('https://fonts.googleapis.com/css2?family=Poppins:wght@400;700&display=swap');
:root {
color-scheme: light dark;
supported-color-schemes:light dark;
}
@media (prefers-color-scheme: light) {
body.email-body,
table.centerer,
table.wrapper {
background-color: #FFFFFF !important;
color: #FFFFFF !important;
}
table.card {
background-color: #673DB6 !important;
}
span.shoutout {
color: #D9C6FA !important;
}
td.confirm-link {
background-color: #9D6FF3 !important;
}
td.confirm-code {
background-color: #D9C6FA !important;
color: #45297A !important;
}
td.notice {
color: #9D6FF3 !important;
}
td.notice a {
color: #673DB6 !important;
}
img.logo {
content: url("https://assets.pretendo.cc/images/pretendo-wordmark-singlecolor-purple.png") !important;
}
}
@media (prefers-color-scheme: dark) {
body.email-body,
table.centerer,
table.wrapper {
background-color: #1B1F3B !important;
color: #FFFFFF !important;
}
table.card {
background-color: #23274A !important;
}
span.shoutout {
color: #CAB1FB !important;
}
td.confirm-link {
background-color: #673DB6 !important;
}
td.confirm-code {
background-color: #373C65 !important;
color: #ffffff !important;
}
td.notice {
color: #8990C1 !important;
}
td.notice a {
color: #CAC1F5 !important;
}
}
</style>
</head>
<body class="email-body" bgcolor="#1B1F3B" style="margin-left: 0; margin-right: 0; margin-top: 0; margin-bottom: 0; padding-left: 0; padding-right: 0; padding-top: 0; padding-bottom: 0; font-family: Poppins, Arial, Helvetica, sans-serif;">
<div style="display:none;">Hello {{username}}! Your Pretendo Network ID activation is almost complete. Please click the link in this email to confirm your e-mail address and complete the activation process.</div>
<table class="centerer" bgcolor="#1B1F3B" border="0" cellpadding="0" cellspacing="0" height="100%" width="100%">
<tr>
<td align="center">
<table class="wrapper" bgcolor="#1B1F3B" style="font-family: Poppins, Arial, Helvetica, sans-serif;" border="0" cellpadding="0" cellspacing="0" height="100%" width="420px">
<tr>
<td>
<table border="0" cellpadding="0" cellspacing="0" height="100%" width="100%">
<tr>
<td width="32px">&nbsp;</td>
<td>
<table border="0" cellpadding="0" cellspacing="0" height="100%" width="100%">
<tr>
<td height="36px" style="line-height: 36px;" width="100%">&nbsp;</td>
</tr>
<tr>
<td>
<table border="0" cellpadding="0" cellspacing="0" height="100%" width="100%">
<tr>
<td>
<a href="https://pretendo.network">
<img class="logo" width="auto" height="48px" src="https://assets.pretendo.cc/images/pretendo-wordmark-multicolor-purple+white.png" alt="Pretendo">
</a>
</td>
</tr>
<tr>
<td width="100%" height="36px" style="line-height: 36px;">&nbsp;</td>
</tr>
<tr>
<td>
<table class="card" bgcolor="#23274a" style="color: #ffffff; border-radius: 10px;" border="0" cellpadding="0" cellspacing="0" height="100%" width="100%">
<tr>
<td width="24px" height="100%">&nbsp;</td>
<td>
<table border="0" cellpadding="0" cellspacing="0" height="100%" width="100%">
<tr width="100%" height="48px" style="line-height: 48px;">
<td>&nbsp;</td>
</tr>
<tr style="font-size: 24px; font-weight: 700;">
<td>
Hello <span class="shoutout" style="color: #cab1fb;">{{username}}</span>!
</td>
</tr>
<tr>
<td width="100%" height="24px" style="line-height: 24px;">&nbsp;</td>
</tr>
<tr>
<td style="color: #ffffff; ">
Your Pretendo Network ID activation is almost complete. Please click the link below to confirm your e-mail address and complete the activation process.
</td>
</tr>
<tr>
<td width="100%" height="16px" style="line-height: 16px;">&nbsp;</td>
</tr>
<tr>
<td class="confirm-link" bgcolor="#673db6" style="font-size: 14px; font-weight: 700; border-radius: 10px; padding: 12px" align="center">
<a href="{{confirmation-href}}" style="text-decoration: none; color: #ffffff; " width="100%">
Confirm email address
</a>
</td>
</tr>
<tr>
<td width="100%" height="48px" style="line-height: 48px;">&nbsp;</td>
</tr>
<tr>
<td>
You may also enter the following 6-digit code on your console:
</td>
</tr>
<tr>
<td width="100%" height="16px" style="line-height: 16px;">&nbsp;</td>
</tr>
<tr>
<td class="confirm-code" bgcolor="#373c65" style="color: #ffffff; font-size: 14px; font-weight: 700; border-radius: 10px; padding: 12px" align="center">
{{confirmation-code}}
</td>
</tr>
<tr>
<td width="100%" height="48px" style="line-height: 48px;">&nbsp;</td>
</tr>
<tr>
<td>
We hope you have fun using our services!
</td>
</tr>
<tr>
<td width="100%" height="36px" style="line-height: 36px;">&nbsp;</td>
</tr>
<tr>
<td align="right">
The Pretendo Network team
</td>
</tr>
<tr>
<td width="100%" height="24px" style="line-height: 24px;">&nbsp;</td>
</tr>
</table>
</td>
<td width="24px" height="100%">&nbsp;</td>
</tr>
</table>
</td>
</tr>
<tr>
<td width="100%" height="18px" style="line-height: 18px;">&nbsp;</td>
</tr>
<tr>
<td class="notice" style="color: #8990c1; font-size: 12px;">
Note: this email message was auto-generated, please do not respond. For further assistance, please join our <a href="https://invite.gg/pretendo" style="text-decoration: none; color: #ffffff; ">Discord server</a> or make a post on our <a href="https://forum.pretendo.network" style="text-decoration: none; color: #ffffff; ">Forum</a>.
</td>
</tr>
<tr>
<td width="100%" height="48px" style="line-height: 48px;">&nbsp;</td>
</tr>
</table>
</td>
</tr>
</table>
</td>
<td width="32px">&nbsp;</td>
</tr>
</table>
</td>
</tr>
</table>
</td>
</tr>
</table>
</body>
</html>

View File

@ -15,30 +15,44 @@
body.email-body,
table.centerer,
table.wrapper {
background-color: #FFFFFF !important;
color: #FFFFFF !important;
background-color: #673DB6 !important;
color: #673DB6 !important;
}
table.card {
background-color: #673DB6 !important;
background-color: #fff !important;
}
span.shoutout {
color: #D9C6FA !important;
color: #9D6FF3 !important;
}
td.confirm-link {
td {
color: #673DB6 !important;
}
td a {
color: #673DB6 !important;
font-weight: 700 !important;
text-decoration: underline !important;
}
td.primary, td.primary a {
background-color: #9D6FF3 !important;
color: #fff !important;
}
td.confirm-code {
td.secondary, td.secondary a {
background-color: #D9C6FA !important;
color: #45297A !important;
}
td.primary a, td.secondary a {
text-decoration: none !important;
}
td.notice {
color: #9D6FF3 !important;
color: #c5adf2 !important;
}
td.notice a {
color: #673DB6 !important;
color: #fff !important;
font-weight: 700 !important;
}
img.logo {
content: url("https://assets.pretendo.cc/images/pretendo-wordmark-singlecolor-purple.png") !important;
strong {
font-weight: 700 !important;
color: #9D6FF3 !important;
}
}
@media (prefers-color-scheme: dark) {
@ -46,7 +60,7 @@
table.centerer,
table.wrapper {
background-color: #1B1F3B !important;
color: #FFFFFF !important;
color: #A1A8D9 !important;
}
table.card {
background-color: #23274A !important;
@ -54,28 +68,52 @@
span.shoutout {
color: #CAB1FB !important;
}
td.confirm-link {
td {
color: #A1A8D9 !important;
}
td a {
color: #fff !important;
font-weight: 700 !important;
text-decoration: underline !important;
}
td.header {
color: #fff !important;
}
td.primary {
background-color: #673DB6 !important;
}
td.confirm-code {
td.secondary {
background-color: #373C65 !important;
color: #ffffff !important;
color: #fff !important;
}
td.primary a, td.secondary a {
text-decoration: none !important;
}
td.signature {
color: #A1A8D9 !important;
}
td.notice {
color: #8990C1 !important;
}
td.notice a {
color: #CAC1F5 !important;
color: #fff !important;
}
strong {
font-weight: 700 !important;
color: #fff !important;
}
img.logo {
content: url("https://assets.pretendo.cc/images/pretendo-wordmark-multicolor-purple+white.png") !important;
}
}
</style>
</head>
<body class="email-body" bgcolor="#1B1F3B" style="margin-left: 0; margin-right: 0; margin-top: 0; margin-bottom: 0; padding-left: 0; padding-right: 0; padding-top: 0; padding-bottom: 0; font-family: Poppins, Arial, Helvetica, sans-serif;">
<div style="display:none;">{{preview}}</div>
<div style="display:none;"><!--plainText--></div>
<table class="centerer" bgcolor="#1B1F3B" border="0" cellpadding="0" cellspacing="0" height="100%" width="100%">
<tr>
<td align="center">
<table class="wrapper" bgcolor="#1B1F3B" style="font-family: Poppins, Arial, Helvetica, sans-serif;" border="0" cellpadding="0" cellspacing="0" height="100%" width="420px">
<table class="wrapper" bgcolor="#1B1F3B" style="font-family: Poppins, Arial, Helvetica, sans-serif;" border="0" cellpadding="0" cellspacing="0" height="100%" width="600px">
<tr>
<td>
<table border="0" cellpadding="0" cellspacing="0" height="100%" width="100%">
@ -92,7 +130,7 @@
<tr>
<td>
<a href="https://pretendo.network">
<img class="logo" width="auto" height="48px" src="https://assets.pretendo.cc/images/pretendo-wordmark-multicolor-purple+white.png" alt="Pretendo">
<img class="logo" width="auto" height="48px" src="/home/limes/Downloads/pretendo-wordmark-white.png" alt="Pretendo">
</a>
</td>
</tr>
@ -101,42 +139,30 @@
</tr>
<tr>
<td>
<table class="card" bgcolor="#23274a" style="color: #ffffff; border-radius: 10px;" border="0" cellpadding="0" cellspacing="0" height="100%" width="100%">
<table class="card" bgcolor="#23274a" style="color: #A1A8D9; border-radius: 10px;" border="0" cellpadding="0" cellspacing="0" height="100%" width="100%">
<tr>
<td width="24px" height="100%">&nbsp;</td>
<td width="36px" height="100%">&nbsp;</td>
<td>
<table border="0" cellpadding="0" cellspacing="0" height="100%" width="100%">
<tr width="100%" height="48px" style="line-height: 48px;">
<tr width="100%" height="36px" style="line-height: 36px;">
<td>&nbsp;</td>
</tr>
<tr style="font-size: 24px; font-weight: 700;">
<td>
Dear <span class="shoutout" style="color: #cab1fb;">{{username}}</span>,
</td>
</tr>
<!--innerHTML-->
<tr><td width="100%" height="32px" style="line-height: 32px;">&nbsp;</td></tr>
<tr>
<td width="100%" height="24px" style="line-height: 24px;">&nbsp;</td>
</tr>
<tr>
<td style="color: #ffffff; ">
{{paragraph}}
</td>
</tr>
<!--{{buttonPlaceholder}}-->
<tr>
<td width="100%" height="36px" style="line-height: 36px;">&nbsp;</td>
</tr>
<tr>
<td align="right">
<td align="right" class="signature">
The Pretendo Network team
</td>
</tr>
<tr>
<td width="100%" height="24px" style="line-height: 24px;">&nbsp;</td>
<td width="100%" height="36px" style="line-height: 36px;">&nbsp;</td>
</tr>
</table>
</td>
<td width="24px" height="100%">&nbsp;</td>
<td width="36px" height="100%">&nbsp;</td>
</tr>
</table>
</td>
@ -145,8 +171,8 @@
<td width="100%" height="18px" style="line-height: 18px;">&nbsp;</td>
</tr>
<tr>
<td class="notice" style="color: #8990c1; font-size: 12px;">
Note: this email message was auto-generated, please do not respond. For further assistance, please join our <a href="https://invite.gg/pretendo" style="text-decoration: none; color: #ffffff; ">Discord server</a> or make a post on our <a href="https://forum.pretendo.network" style="text-decoration: none; color: #ffffff; ">Forum</a>.
<td class="notice" style="color: #8990c1; font-size: 13px; text-align: center;">
Note: this email message was auto-generated, please do not respond. For further assistance, please join our <a href="https://discord.pretendo.network" style="text-decoration: none; color: #ffffff; ">Discord server</a> or make a post on our <a href="https://forum.pretendo.network" style="text-decoration: none; color: #ffffff; ">Forum</a>.
</td>
</tr>
<tr>

View File

@ -6,7 +6,6 @@ import { config, disabledFeatures } from '@/config-manager';
import type { MailerOptions } from '@/types/common/mailer-options';
const genericEmailTemplate = fs.readFileSync(path.join(__dirname, './assets/emails/genericTemplate.html'), 'utf8');
const confirmationEmailTemplate = fs.readFileSync(path.join(__dirname, './assets/emails/confirmationTemplate.html'), 'utf8');
let transporter: nodemailer.Transporter;
@ -28,31 +27,174 @@ if (!disabledFeatures.email) {
});
}
interface emailComponent {
type: 'header' | 'paragraph';
text: string;
replacements?: emailTextReplacements;
}
interface paddingComponent {
type: 'padding';
size: number;
}
interface buttonComponent {
type: 'button';
text: string;
link?: string;
primary?: boolean;
}
interface emailTextReplacements {
[key: string]: string;
}
export class CreateEmail {
// an array which stores all components of the email
private readonly componentArray: (emailComponent | paddingComponent | buttonComponent)[] = [];
/**
* utility function which returns a table row of the specified height to use as padding.
*/
private addPadding(size: number): paddingComponent {
return {
type: 'padding',
size
};
}
/**
* adds a header. for greetings, do addHeader("Hi {{pnid}}!", { pnid: "theUsername" }).
*/
public addHeader(text: string, replacements?: emailTextReplacements): this {
const component: emailComponent = { type: 'header', text, replacements };
this.componentArray.push(component, this.addPadding(24));
return this;
}
/**
* adds a paragraph. for links, do addParagraph("this is a [named link](https://example.org)."). for greetings, do addParagraph("Hi {pnid}!", { pnid: "theUsername" }).
*/
public addParagraph(text: string, replacements?: emailTextReplacements): this {
const component: emailComponent = { type: 'paragraph', text, replacements };
this.componentArray.push(component, this.addPadding(16));
return this;
}
/**
* adds a button.
*
* @param {String} text the button text
* @param {String} [link] the link
* @param {boolean} [primary] set to false to use the secondary button styles (true by default)
*/
public addButton(text: string, link?: string, primary: boolean = true): this {
const component: buttonComponent = { type: 'button', text, link, primary };
this.componentArray.push(this.addPadding(4), component, this.addPadding(32));
return this;
}
// parses pnid name and links. set the plaintext bool (false by default) to use no html
private parseReplacements(c: emailComponent, plainText: boolean = false): void {
// for now only replaces the pnid for shoutouts. could easily be expanded to add more.
if (c?.replacements) {
Object.entries(c.replacements).forEach(([key, value]) => {
if (key === 'pnid') {
if (plainText) {
c.text = c.text.replace(/{{pnid}}/g, value);
} else {
c.text = c.text.replace(/{{pnid}}/g, `<span class="shoutout" style="color: #cab1fb;">${value}</span>`);
}
}
});
}
// replace [links](https://example.com) with html anchor tags or a plaintext representation
const linkRegex = /\[(?<linkText>.*?)\]\((?<linkAddress>.*?)\)/g;
if (linkRegex.test(c.text)) {
if (plainText) {
c.text = c.text.replace(linkRegex, `$<linkText> ($<linkAddress>)`);
} else {
c.text = c.text.replace(linkRegex, `<a href="$<linkAddress>" style="text-decoration: none; font-weight: 700; color: #ffffff; ">$<linkText></a>`);
}
}
}
// generates the html version of the email
public toHTML(): string {
let innerHTML = '';
this.componentArray.forEach((c) => {
switch (c.type) {
case 'padding':
innerHTML += `\n<tr><td width="100%" height="${c.size}px" style="line-height: ${c.size}px;">&nbsp;</td></tr>`;
break;
case 'header':
this.parseReplacements(c);
innerHTML += `\n<tr style="font-size: 24px; font-weight: 700; color: #fff"><td class="header">${c.text}</td></tr>`;
break;
case 'paragraph':
this.parseReplacements(c);
innerHTML += `\n<tr><td>${c.text}</td></tr>`;
break;
case 'button':
innerHTML += `\n<tr><td class="${c.primary ? 'primary' : 'secondary'}" bgcolor="#673db6" style="font-size: 14px; font-weight: 700; border-radius: 10px; padding: 12px" align="center"><a href="${c.link || ''}" style="color: #ffffff; " width="100%">${c.text}</a></td></tr>`;
break;
}
});
const generatedHTML = genericEmailTemplate.replace('<!--innerHTML-->', innerHTML);
return generatedHTML;
}
// generates the plaintext version that shows up on the email preview (and is shown by plaintext clients)
public toPlainText(): string {
let plainText = '';
this.componentArray.forEach((c) => {
switch (c.type) {
case 'padding':
break;
case 'header':
this.parseReplacements(c, true);
plainText += `\n${c.text}`;
break;
case 'paragraph':
this.parseReplacements(c, true);
plainText += `\n${c.text}`;
break;
case 'button':
if (c.link) {
plainText += `\n\n${c.text}: ${c.link}\n`;
} else {
plainText += ` ${c.text}\n`;
}
break;
}
});
// the signature is baked into the template, so it needs to be added manually to the plaintext version
plainText += '\n\n- The Pretendo Network team';
plainText = plainText.replace(/(<([^>]+)>)/gi, '');
return plainText;
}
}
export async function sendMail(options: MailerOptions): Promise<void> {
if (!disabledFeatures.email) {
const { to, subject, username, paragraph, preview, text, link, confirmation } = options;
let html = confirmation ? confirmationEmailTemplate : genericEmailTemplate;
html = html.replace(/{{username}}/g, username);
html = html.replace(/{{paragraph}}/g, paragraph || '');
html = html.replace(/{{preview}}/g, preview || '');
html = html.replace(/{{confirmation-href}}/g, confirmation?.href || '');
html = html.replace(/{{confirmation-code}}/g, confirmation?.code || '');
if (link) {
const { href, text } = link;
const button = `<tr><td width="100%" height="16px" style="line-height: 16px;">&nbsp;</td></tr><tr><td class="confirm-link" bgcolor="#673db6" style="font-size: 14px; font-weight: 700; border-radius: 10px; padding: 12px" align="center"><a href="${href}" style="text-decoration: none; color: #ffffff; " width="100%">${text}</a></td></tr>`;
html = html.replace(/<!--{{buttonPlaceholder}}-->/g, button);
}
const { to, subject, email } = options;
await transporter.sendMail({
from: config.email.from,
to,
subject,
text,
html
text: email.toPlainText(),
html: email.toHTML()
});
}
}

View File

@ -1,16 +1,7 @@
import type { CreateEmail } from '@/mailer';
export interface MailerOptions {
to: string;
subject: string;
username: string;
paragraph?: string;
preview?: string;
text: string;
link?: {
href: string;
text: string;
};
confirmation?: {
href: string;
code: string;
};
email: CreateEmail;
}

View File

@ -4,7 +4,7 @@ import { S3 } from '@aws-sdk/client-s3';
import fs from 'fs-extra';
import bufferCrc32 from 'buffer-crc32';
import { crc32 } from 'crc';
import { sendMail } from '@/mailer';
import { sendMail, CreateEmail } from '@/mailer';
import { SystemType } from '@/types/common/system-types';
import { TokenType } from '@/types/common/token-types';
import { config, disabledFeatures } from '@/config-manager';
@ -201,39 +201,47 @@ export function nascError(errorCode: string): URLSearchParams {
}
export async function sendConfirmationEmail(pnid: mongoose.HydratedDocument<IPNID, IPNIDMethods>): Promise<void> {
const email = new CreateEmail()
.addHeader('Hello {{pnid}}!', { pnid: pnid.username })
.addParagraph('Your <strong>Pretendo Network ID</strong> activation is almost complete. Please click the link below to confirm your e-mail address and complete the activation process.')
.addButton('Confirm email address', `https://api.pretendo.cc/v1/email/verify?token=${pnid.identification.email_token}`)
.addParagraph('You may also enter the following 6-digit code on your console:')
.addButton(pnid.identification.email_code, '', false)
.addParagraph('We hope you have fun using our services!');
const options = {
to: pnid.email.address,
subject: '[Pretendo Network] Please confirm your email address',
username: pnid.username,
confirmation: {
href: `https://api.pretendo.cc/v1/email/verify?token=${pnid.identification.email_token}`,
code: pnid.identification.email_code
},
text: `Hello ${pnid.username}! \r\n\r\nYour Pretendo Network ID activation is almost complete. Please click the link to confirm your e-mail address and complete the activation process: \r\nhttps://api.pretendo.cc/v1/email/verify?token=${pnid.identification.email_token} \r\n\r\nYou may also enter the following 6-digit code on your console: ${pnid.identification.email_code}`
email
};
await sendMail(options);
}
export async function sendEmailConfirmedEmail(pnid: mongoose.HydratedDocument<IPNID, IPNIDMethods>): Promise<void> {
const email = new CreateEmail()
.addHeader('Dear {{pnid}}!', { pnid: pnid.username })
.addParagraph('Your email address has been confirmed.')
.addParagraph('We hope you have fun on Pretendo Network!');
const options = {
to: pnid.email.address,
subject: '[Pretendo Network] Email address confirmed',
username: pnid.username,
paragraph: 'your email address has been confirmed. We hope you have fun on Pretendo Network!',
text: `Dear ${pnid.username}, \r\n\r\nYour email address has been confirmed. We hope you have fun on Pretendo Network!`
email
};
await sendMail(options);
}
export async function sendEmailConfirmedParentalControlsEmail(pnid: mongoose.HydratedDocument<IPNID, IPNIDMethods>): Promise<void> {
const email = new CreateEmail()
.addHeader('Dear {{pnid}},', { pnid: pnid.username })
.addParagraph('your email address has been confirmed for use with Parental Controls.');
const options = {
to: pnid.email.address,
subject: '[Pretendo Network] Email address confirmed for Parental Controls',
username: pnid.username,
paragraph: 'your email address has been confirmed for use with Parental Controls.',
text: `Dear ${pnid.username}, \r\n\r\nYour email address has been confirmed for use with Parental Controls.`
email
};
await sendMail(options);
@ -254,31 +262,33 @@ export async function sendForgotPasswordEmail(pnid: mongoose.HydratedDocument<IP
// TODO - Handle null token
const email = new CreateEmail()
.addHeader('Dear {{pnid}},', { pnid: pnid.username })
.addParagraph('a password reset has been requested from this account.')
.addParagraph('If you did not request the password reset, please ignore this email. If you did request this password reset, please click the link below to reset your password.')
.addButton('Reset password', `${config.website_base}/account/reset-password?token=${encodeURIComponent(passwordResetToken)}`);
const mailerOptions = {
to: pnid.email.address,
subject: '[Pretendo Network] Forgot Password',
username: pnid.username,
paragraph: 'a password reset has been requested from this account. If you did not request the password reset, please ignore this email. If you did request this password reset, please click the link below to reset your password.',
link: {
text: 'Reset password',
href: `${config.website_base}/account/reset-password?token=${encodeURIComponent(passwordResetToken)}`
},
text: `Dear ${pnid.username}, a password reset has been requested from this account. \r\n\r\nIf you did not request the password reset, please ignore this email. \r\nIf you did request this password reset, please click the link to reset your password: ${config.website_base}/account/reset-password?token=${encodeURIComponent(passwordResetToken)}`
email
};
await sendMail(mailerOptions);
}
export async function sendPNIDDeletedEmail(email: string, username: string): Promise<void> {
export async function sendPNIDDeletedEmail(emailAddress: string, username: string): Promise<void> {
const email = new CreateEmail()
.addHeader('Dear {{pnid}},', { pnid: username })
.addParagraph('your PNID has successfully been deleted.')
.addParagraph('If you had a tier subscription, a separate cancellation email will be sent.')
.addParagraph('If you do not receive this cancellation email, or your subscription is still being charged, please contact @jonbarrow on our Discord server.')
.addButton('Join the Discord', 'https://discord.pretendo.network/');
const options = {
to: email,
to: emailAddress,
subject: '[Pretendo Network] PNID Deleted',
username: username,
link: {
text: 'Discord Server',
href: 'https://discord.com/invite/pretendo'
},
text: `Your PNID ${username} has successfully been deleted. If you had a tier subscription, a separate cancellation email will be sent. If you do not receive this cancellation email, or your subscription is still being charged, please contact @jon on our Discord server`
email
};
await sendMail(options);