Feature: SMTP-templating of customBody and customHeader via liquidjs (#3414)

* replaced the regex replacement engine with `Liquid`

* added custom bodys

* fixed a typo

* formatting fixes

* switched all template-variables to be camelCase
This commit is contained in:
Frank Elsinga 2023-10-16 16:16:49 +02:00 committed by GitHub
parent 579d7232c9
commit 3fcb7bf181
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 100 additions and 66 deletions

View file

@ -1,6 +1,7 @@
const nodemailer = require("nodemailer"); const nodemailer = require("nodemailer");
const NotificationProvider = require("./notification-provider"); const NotificationProvider = require("./notification-provider");
const { DOWN } = require("../../src/util"); const { DOWN } = require("../../src/util");
const { Liquid } = require("liquidjs");
class SMTP extends NotificationProvider { class SMTP extends NotificationProvider {
@ -39,33 +40,56 @@ class SMTP extends NotificationProvider {
pass: notification.smtpPassword, pass: notification.smtpPassword,
}; };
} }
// Lets start with default subject and empty string for custom one
// default values in case the user does not want to template
let subject = msg; let subject = msg;
let body = msg;
// Change the subject if: if (heartbeatJSON) {
// - The msg ends with "Testing" or body = `${msg}\nTime (${heartbeatJSON["timezone"]}): ${heartbeatJSON["localDateTime"]}`;
// - Actual Up/Down Notification }
// subject and body are templated
if ((monitorJSON && heartbeatJSON) || msg.endsWith("Testing")) { if ((monitorJSON && heartbeatJSON) || msg.endsWith("Testing")) {
let customSubject = ""; // cannot end with whitespace as this often raises spam scores
const customSubject = notification.customSubject?.trim() || "";
const customBody = notification.customBody?.trim() || "";
// Our subject cannot end with whitespace it's often raise spam score const context = this.generateContext(msg, monitorJSON, heartbeatJSON);
// Once I got "Cannot read property 'trim' of undefined", better be safe than sorry const engine = new Liquid();
if (notification.customSubject) { if (customSubject !== "") {
customSubject = notification.customSubject.trim(); const tpl = engine.parse(customSubject);
subject = await engine.render(tpl, context);
}
if (customBody !== "") {
const tpl = engine.parse(customBody);
body = await engine.render(tpl, context);
}
} }
// If custom subject is not empty, change subject for notification // send mail with defined transport object
if (customSubject !== "") { let transporter = nodemailer.createTransport(config);
await transporter.sendMail({
from: notification.smtpFrom,
cc: notification.smtpCC,
bcc: notification.smtpBCC,
to: notification.smtpTo,
subject: subject,
text: body,
});
// Replace "MACROS" with corresponding variable return "Sent Successfully.";
let replaceName = new RegExp("{{NAME}}", "g"); }
let replaceHostnameOrURL = new RegExp("{{HOSTNAME_OR_URL}}", "g");
let replaceStatus = new RegExp("{{STATUS}}", "g");
// Lets start with dummy values to simplify code /**
let monitorName = "Test"; * Generate context for LiquidJS
* @param {string} msg the message that will be included in the context
* @param {?object} monitorJSON Monitor details (For Up/Down/Cert-Expiry only)
* @param {?object} heartbeatJSON Heartbeat details (For Up/Down only)
* @returns {{STATUS: string, status: string, HOSTNAME_OR_URL: string, hostnameOrUrl: string, NAME: string, name: string, monitorJSON: ?object, heartbeatJSON: ?object, msg: string}} the context
*/
generateContext(msg, monitorJSON, heartbeatJSON) {
// Let's start with dummy values to simplify code
let monitorName = "Monitor Name not available";
let monitorHostnameOrURL = "testing.hostname"; let monitorHostnameOrURL = "testing.hostname";
let serviceStatus = "⚠️ Test";
if (monitorJSON !== null) { if (monitorJSON !== null) {
monitorName = monitorJSON["name"]; monitorName = monitorJSON["name"];
@ -77,37 +101,24 @@ class SMTP extends NotificationProvider {
} }
} }
let serviceStatus = "⚠️ Test";
if (heartbeatJSON !== null) { if (heartbeatJSON !== null) {
serviceStatus = (heartbeatJSON["status"] === DOWN) ? "🔴 Down" : "✅ Up"; serviceStatus = (heartbeatJSON["status"] === DOWN) ? "🔴 Down" : "✅ Up";
} }
return {
// for v1 compatibility, to be removed in v3
"STATUS": serviceStatus,
"NAME": monitorName,
"HOSTNAME_OR_URL": monitorHostnameOrURL,
// Break replace to one by line for better readability // variables which are officially supported
customSubject = customSubject.replace(replaceStatus, serviceStatus); "status": serviceStatus,
customSubject = customSubject.replace(replaceName, monitorName); "name": monitorName,
customSubject = customSubject.replace(replaceHostnameOrURL, monitorHostnameOrURL); "hostnameOrURL": monitorHostnameOrURL,
monitorJSON,
subject = customSubject; heartbeatJSON,
} msg,
} };
let transporter = nodemailer.createTransport(config);
let bodyTextContent = msg;
if (heartbeatJSON) {
bodyTextContent = `${msg}\nTime (${heartbeatJSON["timezone"]}): ${heartbeatJSON["localDateTime"]}`;
}
// send mail with defined transport object
await transporter.sendMail({
from: notification.smtpFrom,
cc: notification.smtpCC,
bcc: notification.smtpBCC,
to: notification.smtpTo,
subject: subject,
text: bodyTextContent,
});
return "Sent Successfully.";
} }
} }

View file

@ -59,6 +59,28 @@
<input id="to-bcc" v-model="$parent.notification.smtpBCC" type="text" class="form-control" autocomplete="false" :required="!hasRecipient"> <input id="to-bcc" v-model="$parent.notification.smtpBCC" type="text" class="form-control" autocomplete="false" :required="!hasRecipient">
</div> </div>
<p class="form-text">
<i18n-t tag="div" keypath="smtpLiquidIntroduction" class="form-text mb-3">
<a href="https://liquidjs.com/" target="_blank">{{ $t("documentation") }}</a>
</i18n-t>
<code v-pre>{{name}}</code>: {{ $t("emailTemplateServiceName") }}<br />
<code v-pre>{{msg}}</code>: {{ $t("emailTemplateMsg") }}<br />
<code v-pre>{{status}}</code>: {{ $t("emailTemplateStatus") }}<br />
<code v-pre>{{heartbeatJSON}}</code>: {{ $t("emailTemplateHeartbeatJSON") }}<b>{{ $t("emailTemplateLimitedToUpDownNotification") }}</b><br />
<code v-pre>{{monitorJSON}}</code>: {{ $t("emailTemplateMonitorJSON") }} <b>{{ $t("emailTemplateLimitedToUpDownNotification") }}</b><br />
<code v-pre>{{hostnameOrURL}}</code>: {{ $t("emailTemplateHostnameOrURL") }}<br />
</p>
<div class="mb-3">
<label for="subject-email" class="form-label">{{ $t("emailCustomSubject") }}</label>
<input id="subject-email" v-model="$parent.notification.customSubject" type="text" class="form-control" autocomplete="false" placeholder="">
<div class="form-text">{{ $t("leave blank for default subject") }}</div>
</div>
<div class="mb-3">
<label for="body-email" class="form-label">{{ $t("emailCustomBody") }}</label>
<textarea id="body-email" v-model="$parent.notification.customBody" type="text" class="form-control" autocomplete="false" placeholder=""></textarea>
<div class="form-text">{{ $t("leave blank for default body") }}</div>
</div>
<ToggleSection :heading="$t('smtpDkimSettings')"> <ToggleSection :heading="$t('smtpDkimSettings')">
<i18n-t tag="div" keypath="smtpDkimDesc" class="form-text mb-3"> <i18n-t tag="div" keypath="smtpDkimDesc" class="form-text mb-3">
<a href="https://nodemailer.com/dkim/" target="_blank">{{ $t("documentation") }}</a> <a href="https://nodemailer.com/dkim/" target="_blank">{{ $t("documentation") }}</a>
@ -89,17 +111,6 @@
<input id="dkim-skip-fields" v-model="$parent.notification.smtpDkimskipFields" type="text" class="form-control" autocomplete="false" placeholder="message-id:date"> <input id="dkim-skip-fields" v-model="$parent.notification.smtpDkimskipFields" type="text" class="form-control" autocomplete="false" placeholder="message-id:date">
</div> </div>
</ToggleSection> </ToggleSection>
<div class="mb-3">
<label for="subject-email" class="form-label">{{ $t("emailCustomSubject") }}</label>
<input id="subject-email" v-model="$parent.notification.customSubject" type="text" class="form-control" autocomplete="false" placeholder="">
<div v-pre class="form-text">
(leave blank for default one)<br />
{{NAME}}: Service Name<br />
{{HOSTNAME_OR_URL}}: Hostname or URL<br />
{{STATUS}}: Status<br />
</div>
</div>
</div> </div>
</template> </template>

View file

@ -489,7 +489,19 @@
"secureOptionTLS": "TLS (465)", "secureOptionTLS": "TLS (465)",
"Ignore TLS Error": "Ignore TLS Error", "Ignore TLS Error": "Ignore TLS Error",
"From Email": "From Email", "From Email": "From Email",
"emailCustomisableContent": "Customisable content",
"smtpLiquidIntroduction": "The following two fields are templatable via the Liquid templating Language. Please refer to the {0} for usage instructions. These are the available variables:",
"emailCustomSubject": "Custom Subject", "emailCustomSubject": "Custom Subject",
"leave blank for default subject": "leave blank for default subject",
"emailCustomBody": "Custom Body",
"leave blank for default body": "leave blank for default body",
"emailTemplateServiceName": "Service Name",
"emailTemplateHostnameOrURL": "Hostname or URL",
"emailTemplateStatus": "Status",
"emailTemplateMonitorJSON": "object describing the monitor",
"emailTemplateHeartbeatJSON": "object describing the heartbeat",
"emailTemplateMsg": "message of the notification",
"emailTemplateLimitedToUpDownNotification": "only available for UP/DOWN heartbeats, otherwise null",
"To Email": "To Email", "To Email": "To Email",
"smtpCC": "CC", "smtpCC": "CC",
"smtpBCC": "BCC", "smtpBCC": "BCC",