Skip to content
Snippets Groups Projects
Commit c97864c5 authored by Aaron Dötsch's avatar Aaron Dötsch
Browse files

Add HTML version to mail notifications

parent acf56c92
No related branches found
No related tags found
No related merge requests found
import nodemailer from "nodemailer";
import type { Address } from "nodemailer/lib/mailer";
const transporter = nodemailer.createTransport({
host: process.env.MAIL_HOST,
......@@ -19,17 +20,29 @@ const transporter = nodemailer.createTransport({
* @param {boolean} [mailOptions.html=false] `true` if `text` is HTML, `false` if `text` is plain text
* @returns
*/
export async function sendMail({to, subject, text, html=false}) {
const content = html ? {html: text} : {text};
type MailOptions = {
to?: Address | string | Array<Address | string>,
cc?: Address | string | Array<Address | string>,
bcc?: Address | string | Array<Address | string>,
subject: string,
text: string,
html?: string
}
export async function sendMail({to, cc, bcc, subject, text, html}: MailOptions) {
const subjectPrefix = process.env.MAIL_SUBJECT_PREFIX ?? "";
return transporter.sendMail({
subject: subjectPrefix + subject,
to,
cc,
bcc,
from: {
address: process.env.MAIL_FROM_ADDRESS,
name: process.env.MAIL_FROM_NAME
},
replyTo: process.env.MAIL_REPLY_TO,
...content
text,
html
});
}
import { sendMail } from "../mail";
import { formatMessage, getMessageTitle } from "./formatter";
import { formatMessage, formatMessageToHtml, getMessageTitle } from "./formatter";
import type { HandlerReturnType, NotificationData, NotificationType, PossibleNotificationType } from "../../notifications/types";
export async function handleEmail<T extends PossibleNotificationType>(recipient: string, type: NotificationType<T>, data: NotificationData<T>): Promise<HandlerReturnType> {
return sendMail({
to: recipient,
subject: getMessageTitle(type),
text: formatMessage(type, data)
text: formatMessage(type, data),
html: formatMessageToHtml(type, data)
}).then(res=>{
if(res.accepted.length > 0) return { status: "success" };
else if(res.pending.length > 0) throw "Email is pending";
......
......@@ -76,3 +76,62 @@ Dein neuer Kontostand beträgt ${(balanceAfter/100).toFixed(2)}€.`;
}
}
}
export function formatMessageToHtml<T extends PossibleNotificationType>(type: NotificationType<T>, data: NotificationData<T>): string {
switch (type.name) {
case "buy": {
const { total, items, balanceBefore, balanceAfter } = data as NotificationData<"buy">;
const premiums = items.filter(item => item.premium && item.premium > 0).map(item => item.premium).reduce((a, b) => a + b, 0);
return `<p>Du hast folgende ${items.length} Artikel gekauft:</p>
<ul>${items.map(item => `<li>${escapeHtml(item.name)} (${escapeHtml(item.code)}) - ${(item.price / 100).toFixed(2)}€` + (item.premium && item.premium > 0 ? ` (+${(item.premium / 100).toFixed(2)}€)` : "") + `</li>`).join("")}</ul>
<p>Gesamt: ${(total / 100).toFixed(2)}€` + (premiums > 0 ? ` (davon ${(premiums / 100).toFixed(2)}€ Negativaufschlag)` : "") + `</p>
<p>Dein neuer Kontostand beträgt ${(balanceAfter / 100).toFixed(2)}€.</p>`;
}
case "refund": {
const { refund, item, balanceBefore, balanceAfter, timeBought } = data as NotificationData<"refund">;
const premiumMessage = refund.premium && refund.premium > 0 ? ` (+${(refund.premium / 100).toFixed(2)}€)` : "";
return `<p>Dir wurden ${(refund.price / 100).toFixed(2)}${premiumMessage} für ${escapeHtml(item.name)} (${escapeHtml(item.code)}) erstattet, gekauft am ${timeBought.toLocaleString()}.</p>
<p>Dein neuer Kontostand beträgt ${(balanceAfter / 100).toFixed(2)}€.</p>`
}
case "deposit": {
const { amount, balanceBefore, balanceAfter } = data as NotificationData<"deposit">;
return `<p>Du hast ${(amount / 100).toFixed(2)}€ eingezahlt.</p>
<p>Dein neuer Kontostand beträgt ${(balanceAfter / 100).toFixed(2)}€.</p>`;
}
case "withdraw": {
const { amount, balanceBefore, balanceAfter } = data as NotificationData<"withdraw">;
return `<p>Du hast ${(amount / 100).toFixed(2)}€ ausgezahlt.</p>
<p>Dein neuer Kontostand beträgt ${(balanceAfter / 100).toFixed(2)}€.</p>`;
}
case "use-voucher": {
const { voucher, balanceBefore, balanceAfter } = data as NotificationData<"use-voucher">;
return `<p>Du hast einen Gutschein im Wert von ${(voucher.value / 100).toFixed(2)}€ eingelöst.</p>
<p>Dein neuer Kontostand beträgt ${(balanceAfter / 100).toFixed(2)}€.</p>`;
}
case "send-transfer": {
const { amount, balanceBefore, balanceAfter, receiver } = data as NotificationData<"send-transfer">;
return `<p>Du hast ${(amount / 100).toFixed(2)}€ an ${escapeHtml(receiver.name)} überwiesen.</p>
<p>Dein neuer Kontostand beträgt ${(balanceAfter / 100).toFixed(2)}€.</p>`;
}
case "receive-transfer": {
const { amount, balanceBefore, balanceAfter, sender } = data as NotificationData<"receive-transfer">;
return `<p>Du hast ${(amount / 100).toFixed(2)}€ von ${escapeHtml(sender.name)} erhalten.</p>
<p>Dein neuer Kontostand beträgt ${(balanceAfter / 100).toFixed(2)}€.</p>`;
}
default: {
console.error(`Unknown notification type`, type);
return `Unknown notification type ${escapeHtml(JSON.stringify(type))}`;
}
}
}
function escapeHtml(unsafe: string): string{
const lookup = {
"&": "&amp;",
"<": "&lt;",
">": "&gt;",
'"': "&quot;",
"'": "&#39;"
};
return unsafe.replace(/[&<>"']/g, c => lookup[c]);
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment