mirror of
https://github.com/louislam/uptime-kuma.git
synced 2024-12-24 05:04:08 -08:00
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:
parent
579d7232c9
commit
3fcb7bf181
|
@ -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,76 +40,86 @@ 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:
|
|
||||||
// - The msg ends with "Testing" or
|
|
||||||
// - Actual Up/Down Notification
|
|
||||||
if ((monitorJSON && heartbeatJSON) || msg.endsWith("Testing")) {
|
|
||||||
let customSubject = "";
|
|
||||||
|
|
||||||
// Our subject cannot end with whitespace it's often raise spam score
|
|
||||||
// Once I got "Cannot read property 'trim' of undefined", better be safe than sorry
|
|
||||||
if (notification.customSubject) {
|
|
||||||
customSubject = notification.customSubject.trim();
|
|
||||||
}
|
|
||||||
|
|
||||||
// If custom subject is not empty, change subject for notification
|
|
||||||
if (customSubject !== "") {
|
|
||||||
|
|
||||||
// Replace "MACROS" with corresponding variable
|
|
||||||
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";
|
|
||||||
let monitorHostnameOrURL = "testing.hostname";
|
|
||||||
let serviceStatus = "⚠️ Test";
|
|
||||||
|
|
||||||
if (monitorJSON !== null) {
|
|
||||||
monitorName = monitorJSON["name"];
|
|
||||||
|
|
||||||
if (monitorJSON["type"] === "http" || monitorJSON["type"] === "keyword" || monitorJSON["type"] === "json-query") {
|
|
||||||
monitorHostnameOrURL = monitorJSON["url"];
|
|
||||||
} else {
|
|
||||||
monitorHostnameOrURL = monitorJSON["hostname"];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (heartbeatJSON !== null) {
|
|
||||||
serviceStatus = (heartbeatJSON["status"] === DOWN) ? "🔴 Down" : "✅ Up";
|
|
||||||
}
|
|
||||||
|
|
||||||
// Break replace to one by line for better readability
|
|
||||||
customSubject = customSubject.replace(replaceStatus, serviceStatus);
|
|
||||||
customSubject = customSubject.replace(replaceName, monitorName);
|
|
||||||
customSubject = customSubject.replace(replaceHostnameOrURL, monitorHostnameOrURL);
|
|
||||||
|
|
||||||
subject = customSubject;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let transporter = nodemailer.createTransport(config);
|
|
||||||
|
|
||||||
let bodyTextContent = msg;
|
|
||||||
if (heartbeatJSON) {
|
if (heartbeatJSON) {
|
||||||
bodyTextContent = `${msg}\nTime (${heartbeatJSON["timezone"]}): ${heartbeatJSON["localDateTime"]}`;
|
body = `${msg}\nTime (${heartbeatJSON["timezone"]}): ${heartbeatJSON["localDateTime"]}`;
|
||||||
|
}
|
||||||
|
// subject and body are templated
|
||||||
|
if ((monitorJSON && heartbeatJSON) || msg.endsWith("Testing")) {
|
||||||
|
// cannot end with whitespace as this often raises spam scores
|
||||||
|
const customSubject = notification.customSubject?.trim() || "";
|
||||||
|
const customBody = notification.customBody?.trim() || "";
|
||||||
|
|
||||||
|
const context = this.generateContext(msg, monitorJSON, heartbeatJSON);
|
||||||
|
const engine = new Liquid();
|
||||||
|
if (customSubject !== "") {
|
||||||
|
const tpl = engine.parse(customSubject);
|
||||||
|
subject = await engine.render(tpl, context);
|
||||||
|
}
|
||||||
|
if (customBody !== "") {
|
||||||
|
const tpl = engine.parse(customBody);
|
||||||
|
body = await engine.render(tpl, context);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// send mail with defined transport object
|
// send mail with defined transport object
|
||||||
|
let transporter = nodemailer.createTransport(config);
|
||||||
await transporter.sendMail({
|
await transporter.sendMail({
|
||||||
from: notification.smtpFrom,
|
from: notification.smtpFrom,
|
||||||
cc: notification.smtpCC,
|
cc: notification.smtpCC,
|
||||||
bcc: notification.smtpBCC,
|
bcc: notification.smtpBCC,
|
||||||
to: notification.smtpTo,
|
to: notification.smtpTo,
|
||||||
subject: subject,
|
subject: subject,
|
||||||
text: bodyTextContent,
|
text: body,
|
||||||
});
|
});
|
||||||
|
|
||||||
return "Sent Successfully.";
|
return "Sent Successfully.";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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";
|
||||||
|
|
||||||
|
if (monitorJSON !== null) {
|
||||||
|
monitorName = monitorJSON["name"];
|
||||||
|
|
||||||
|
if (monitorJSON["type"] === "http" || monitorJSON["type"] === "keyword" || monitorJSON["type"] === "json-query") {
|
||||||
|
monitorHostnameOrURL = monitorJSON["url"];
|
||||||
|
} else {
|
||||||
|
monitorHostnameOrURL = monitorJSON["hostname"];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let serviceStatus = "⚠️ Test";
|
||||||
|
if (heartbeatJSON !== null) {
|
||||||
|
serviceStatus = (heartbeatJSON["status"] === DOWN) ? "🔴 Down" : "✅ Up";
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
// for v1 compatibility, to be removed in v3
|
||||||
|
"STATUS": serviceStatus,
|
||||||
|
"NAME": monitorName,
|
||||||
|
"HOSTNAME_OR_URL": monitorHostnameOrURL,
|
||||||
|
|
||||||
|
// variables which are officially supported
|
||||||
|
"status": serviceStatus,
|
||||||
|
"name": monitorName,
|
||||||
|
"hostnameOrURL": monitorHostnameOrURL,
|
||||||
|
monitorJSON,
|
||||||
|
heartbeatJSON,
|
||||||
|
msg,
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = SMTP;
|
module.exports = SMTP;
|
||||||
|
|
|
@ -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>
|
||||||
|
|
||||||
|
|
|
@ -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",
|
||||||
|
|
Loading…
Reference in a new issue