From 712e34572b695d0c80842fb55371aaa29dd71b67 Mon Sep 17 00:00:00 2001
From: Brad Koehn <brad@koe.hn>
Date: Sat, 11 Jan 2025 13:02:46 -0600
Subject: [PATCH 1/5] added smtp monitor

---
 .../2025-01-01-0000-add-smtp.js               | 12 ++++
 package-lock.json                             |  4 +-
 server/model/monitor.js                       |  1 +
 server/monitor-types/smtp.js                  | 59 +++++++++++++++++++
 server/server.js                              |  1 +
 server/uptime-kuma-server.js                  |  2 +
 src/pages/EditMonitor.vue                     | 18 +++++-
 7 files changed, 92 insertions(+), 5 deletions(-)
 create mode 100644 db/knex_migrations/2025-01-01-0000-add-smtp.js
 create mode 100644 server/monitor-types/smtp.js

diff --git a/db/knex_migrations/2025-01-01-0000-add-smtp.js b/db/knex_migrations/2025-01-01-0000-add-smtp.js
new file mode 100644
index 000000000..00d4835f5
--- /dev/null
+++ b/db/knex_migrations/2025-01-01-0000-add-smtp.js
@@ -0,0 +1,12 @@
+exports.up = function (knex) {
+    return knex.schema
+        .alterTable("monitor", function (table) {
+            table.string("smtp_security").defaultTo(null);
+        });
+};
+
+exports.down = function (knex) {
+    return knex.schema.alterTable("monitor", function (table) {
+        table.dropColumn("smtp_security");
+    });
+};
diff --git a/package-lock.json b/package-lock.json
index 8d3f58b4d..ccf1a628c 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
 {
     "name": "uptime-kuma",
-    "version": "2.0.0-beta.0",
+    "version": "2.0.0-beta.1",
     "lockfileVersion": 3,
     "requires": true,
     "packages": {
         "": {
             "name": "uptime-kuma",
-            "version": "2.0.0-beta.0",
+            "version": "2.0.0-beta.1",
             "license": "MIT",
             "dependencies": {
                 "@grpc/grpc-js": "~1.8.22",
diff --git a/server/model/monitor.js b/server/model/monitor.js
index 3ad8cfafc..e8ec54c14 100644
--- a/server/model/monitor.js
+++ b/server/model/monitor.js
@@ -153,6 +153,7 @@ class Monitor extends BeanModel {
             snmpOid: this.snmpOid,
             jsonPathOperator: this.jsonPathOperator,
             snmpVersion: this.snmpVersion,
+            smtpSecurity: this.smtpSecurity,
             rabbitmqNodes: JSON.parse(this.rabbitmqNodes),
             conditions: JSON.parse(this.conditions),
         };
diff --git a/server/monitor-types/smtp.js b/server/monitor-types/smtp.js
new file mode 100644
index 000000000..3026dc2df
--- /dev/null
+++ b/server/monitor-types/smtp.js
@@ -0,0 +1,59 @@
+const { MonitorType } = require("./monitor-type");
+const { UP } = require("../../src/util");
+const nodemailer = require("nodemailer");
+
+class SMTPMonitorType extends MonitorType {
+    name = "smtp";
+
+    /**
+     * @param {*} smtpSecurity the user's SMTP security setting
+     * @returns {boolean} True if this should test SMTPS
+     */
+    isSMTPS(smtpSecurity) {
+        return smtpSecurity === "secure";
+    }
+
+    /**
+     * @param {*} smtpSecurity the user's SMTP security setting
+     * @returns {boolean} True if this should not attempt STARTTLS, even if it is available
+     */
+    isIgnoreTLS(smtpSecurity) {
+        return smtpSecurity === "nostarttls";
+    }
+
+    /**
+     * @param {*} smtpSecurity the user's SMTP security setting
+     * @returns {boolean} True if this should always test STARTTLS
+     */
+    isRequireTLS(smtpSecurity) {
+        return smtpSecurity === "starttls";
+    }
+
+    /**
+     * @inheritdoc
+     */
+    async check(monitor, heartbeat, _server) {
+        let options = {
+            port: monitor.port || 25,
+            host: monitor.hostname,
+            secure: this.isSMTPS(monitor.smtpSecurity), // use SMTPS (not STARTTLS)
+            ignoreTLS: this.isIgnoreTLS(monitor.smtpSecurity), // don't use STARTTLS even if it's available
+            requireTLS: this.isRequireTLS(monitor.smtpSecurity), // use STARTTLS or fail
+        };
+        let transporter = nodemailer.createTransport(options);
+        try {
+            await transporter.verify();
+
+            heartbeat.status = UP;
+            heartbeat.msg = "SMTP connection verifies successfully";
+        } catch (e) {
+            throw new Error(`SMTP connection doesn't verify: ${e}`);
+        } finally {
+            transporter.close();
+        }
+    }
+}
+
+module.exports = {
+    SMTPMonitorType,
+};
diff --git a/server/server.js b/server/server.js
index ec5ad49f6..476ef644b 100644
--- a/server/server.js
+++ b/server/server.js
@@ -866,6 +866,7 @@ let needSetup = false;
                     monitor.kafkaProducerAllowAutoTopicCreation;
                 bean.gamedigGivenPortOnly = monitor.gamedigGivenPortOnly;
                 bean.remote_browser = monitor.remote_browser;
+                bean.smtpSecurity = monitor.smtpSecurity;
                 bean.snmpVersion = monitor.snmpVersion;
                 bean.snmpOid = monitor.snmpOid;
                 bean.jsonPathOperator = monitor.jsonPathOperator;
diff --git a/server/uptime-kuma-server.js b/server/uptime-kuma-server.js
index 062f098d7..135feca2c 100644
--- a/server/uptime-kuma-server.js
+++ b/server/uptime-kuma-server.js
@@ -113,6 +113,7 @@ class UptimeKumaServer {
         UptimeKumaServer.monitorTypeList["tailscale-ping"] = new TailscalePing();
         UptimeKumaServer.monitorTypeList["dns"] = new DnsMonitorType();
         UptimeKumaServer.monitorTypeList["mqtt"] = new MqttMonitorType();
+        UptimeKumaServer.monitorTypeList["smtp"] = new SMTPMonitorType();
         UptimeKumaServer.monitorTypeList["snmp"] = new SNMPMonitorType();
         UptimeKumaServer.monitorTypeList["mongodb"] = new MongodbMonitorType();
         UptimeKumaServer.monitorTypeList["rabbitmq"] = new RabbitMqMonitorType();
@@ -551,6 +552,7 @@ const { RealBrowserMonitorType } = require("./monitor-types/real-browser-monitor
 const { TailscalePing } = require("./monitor-types/tailscale-ping");
 const { DnsMonitorType } = require("./monitor-types/dns");
 const { MqttMonitorType } = require("./monitor-types/mqtt");
+const { SMTPMonitorType } = require("./monitor-types/smtp");
 const { SNMPMonitorType } = require("./monitor-types/snmp");
 const { MongodbMonitorType } = require("./monitor-types/mongodb");
 const { RabbitMqMonitorType } = require("./monitor-types/rabbitmq");
diff --git a/src/pages/EditMonitor.vue b/src/pages/EditMonitor.vue
index a83f91cab..0f597a349 100644
--- a/src/pages/EditMonitor.vue
+++ b/src/pages/EditMonitor.vue
@@ -24,6 +24,9 @@
                                         <option value="ping">
                                             Ping
                                         </option>
+                                        <option value="smtp">
+                                            SMTP
+                                        </option>
                                         <option value="snmp">
                                             SNMP
                                         </option>
@@ -281,8 +284,8 @@
                             </template>
 
                             <!-- Hostname -->
-                            <!-- TCP Port / Ping / DNS / Steam / MQTT / Radius / Tailscale Ping / SNMP only -->
-                            <div v-if="monitor.type === 'port' || monitor.type === 'ping' || monitor.type === 'dns' || monitor.type === 'steam' || monitor.type === 'gamedig' || monitor.type === 'mqtt' || monitor.type === 'radius' || monitor.type === 'tailscale-ping' || monitor.type === 'snmp'" class="my-3">
+                            <!-- TCP Port / Ping / DNS / Steam / MQTT / Radius / Tailscale Ping / SNMP / SMTP only -->
+                            <div v-if="monitor.type === 'port' || monitor.type === 'ping' || monitor.type === 'dns' || monitor.type === 'steam' || monitor.type === 'gamedig' || monitor.type === 'mqtt' || monitor.type === 'radius' || monitor.type === 'tailscale-ping' || monitor.type === 'smtp' || monitor.type === 'snmp'" class="my-3">
                                 <label for="hostname" class="form-label">{{ $t("Hostname") }}</label>
                                 <input
                                     id="hostname"
@@ -297,7 +300,7 @@
 
                             <!-- Port -->
                             <!-- For TCP Port / Steam / MQTT / Radius Type / SNMP -->
-                            <div v-if="monitor.type === 'port' || monitor.type === 'steam' || monitor.type === 'gamedig' || monitor.type === 'mqtt' || monitor.type === 'radius' || monitor.type === 'snmp'" class="my-3">
+                            <div v-if="monitor.type === 'port' || monitor.type === 'steam' || monitor.type === 'gamedig' || monitor.type === 'mqtt' || monitor.type === 'radius' || monitor.type === 'smtp' || monitor.type === 'snmp'" class="my-3">
                                 <label for="port" class="form-label">{{ $t("Port") }}</label>
                                 <input id="port" v-model="monitor.port" type="number" class="form-control" required min="0" max="65535" step="1">
                             </div>
@@ -329,6 +332,15 @@
                                 </select>
                             </div>
 
+                            <div v-if="monitor.type === 'smtp'" class="my-3">
+                                <label for="smtp_security" class="form-label">{{ $t("SMTP Security") }}</label>
+                                <select id="smtp_security" v-model="monitor.smtpSecurity" class="form-select">
+                                    <option value="secure">SMTPS</option>
+                                    <option value="nostarttls">Ignore STARTTLS</option>
+                                    <option value="starttls">Use STARTTLS</option>
+                                </select>
+                            </div>
+
                             <!-- Json Query -->
                             <!-- For Json Query / SNMP -->
                             <div v-if="monitor.type === 'json-query' || monitor.type === 'snmp'" class="my-3">

From 340bfd7377467086c5cb8e76227b23d1e09b0518 Mon Sep 17 00:00:00 2001
From: Brad Koehn <brad@koe.hn>
Date: Fri, 24 Jan 2025 11:58:34 -0600
Subject: [PATCH 2/5] Added changes from review

---
 server/monitor-types/smtp.js | 30 +++---------------------------
 src/lang/en.json             |  3 ++-
 src/pages/EditMonitor.vue    |  4 ++--
 3 files changed, 7 insertions(+), 30 deletions(-)

diff --git a/server/monitor-types/smtp.js b/server/monitor-types/smtp.js
index 3026dc2df..e5f67157d 100644
--- a/server/monitor-types/smtp.js
+++ b/server/monitor-types/smtp.js
@@ -5,30 +5,6 @@ const nodemailer = require("nodemailer");
 class SMTPMonitorType extends MonitorType {
     name = "smtp";
 
-    /**
-     * @param {*} smtpSecurity the user's SMTP security setting
-     * @returns {boolean} True if this should test SMTPS
-     */
-    isSMTPS(smtpSecurity) {
-        return smtpSecurity === "secure";
-    }
-
-    /**
-     * @param {*} smtpSecurity the user's SMTP security setting
-     * @returns {boolean} True if this should not attempt STARTTLS, even if it is available
-     */
-    isIgnoreTLS(smtpSecurity) {
-        return smtpSecurity === "nostarttls";
-    }
-
-    /**
-     * @param {*} smtpSecurity the user's SMTP security setting
-     * @returns {boolean} True if this should always test STARTTLS
-     */
-    isRequireTLS(smtpSecurity) {
-        return smtpSecurity === "starttls";
-    }
-
     /**
      * @inheritdoc
      */
@@ -36,9 +12,9 @@ class SMTPMonitorType extends MonitorType {
         let options = {
             port: monitor.port || 25,
             host: monitor.hostname,
-            secure: this.isSMTPS(monitor.smtpSecurity), // use SMTPS (not STARTTLS)
-            ignoreTLS: this.isIgnoreTLS(monitor.smtpSecurity), // don't use STARTTLS even if it's available
-            requireTLS: this.isRequireTLS(monitor.smtpSecurity), // use STARTTLS or fail
+            secure: smtpSecurity === "secure", // use SMTPS (not STARTTLS)
+            ignoreTLS: smtpSecurity === "nostarttls", // don't use STARTTLS even if it's available
+            requireTLS: smtpSecurity === "starttls", // use STARTTLS or fail
         };
         let transporter = nodemailer.createTransport(options);
         try {
diff --git a/src/lang/en.json b/src/lang/en.json
index e215f1031..b324f9f78 100644
--- a/src/lang/en.json
+++ b/src/lang/en.json
@@ -1051,5 +1051,6 @@
     "RabbitMQ Password": "RabbitMQ Password",
     "rabbitmqHelpText": "To use the monitor, you will need to enable the Management Plugin in your RabbitMQ setup. For more information, please consult the {rabitmq_documentation}.",
     "SendGrid API Key": "SendGrid API Key",
-    "Separate multiple email addresses with commas": "Separate multiple email addresses with commas"
+    "Separate multiple email addresses with commas": "Separate multiple email addresses with commas",
+    "smtpHelpText": "Specify the TLS settings of the SMTP server you wish to monitor: SMTPS, STARTTLS, or ignore TLS"
 }
diff --git a/src/pages/EditMonitor.vue b/src/pages/EditMonitor.vue
index 0f597a349..bc3ef4224 100644
--- a/src/pages/EditMonitor.vue
+++ b/src/pages/EditMonitor.vue
@@ -332,14 +332,14 @@
                                 </select>
                             </div>
 
-                            <div v-if="monitor.type === 'smtp'" class="my-3">
+                            <i18n-t  v-if="monitor.type === 'smtp'" keypath="smtpHelpText" tag="div" class="my-3">
                                 <label for="smtp_security" class="form-label">{{ $t("SMTP Security") }}</label>
                                 <select id="smtp_security" v-model="monitor.smtpSecurity" class="form-select">
                                     <option value="secure">SMTPS</option>
                                     <option value="nostarttls">Ignore STARTTLS</option>
                                     <option value="starttls">Use STARTTLS</option>
                                 </select>
-                            </div>
+                            </i18n-t>
 
                             <!-- Json Query -->
                             <!-- For Json Query / SNMP -->

From 9c5b19c1d730916a956a0beab5cfc9603b13d738 Mon Sep 17 00:00:00 2001
From: Brad Koehn <brad@koe.hn>
Date: Fri, 24 Jan 2025 12:02:05 -0600
Subject: [PATCH 3/5] fix dumb typo

---
 server/monitor-types/smtp.js | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/server/monitor-types/smtp.js b/server/monitor-types/smtp.js
index e5f67157d..f4a49e238 100644
--- a/server/monitor-types/smtp.js
+++ b/server/monitor-types/smtp.js
@@ -12,9 +12,9 @@ class SMTPMonitorType extends MonitorType {
         let options = {
             port: monitor.port || 25,
             host: monitor.hostname,
-            secure: smtpSecurity === "secure", // use SMTPS (not STARTTLS)
-            ignoreTLS: smtpSecurity === "nostarttls", // don't use STARTTLS even if it's available
-            requireTLS: smtpSecurity === "starttls", // use STARTTLS or fail
+            secure: monitor.smtpSecurity === "secure", // use SMTPS (not STARTTLS)
+            ignoreTLS: monitor.smtpSecurity === "nostarttls", // don't use STARTTLS even if it's available
+            requireTLS: monitor.smtpSecurity === "starttls", // use STARTTLS or fail
         };
         let transporter = nodemailer.createTransport(options);
         try {

From 3dacf72471904d01d4b342faed2b75f8a9e1cba0 Mon Sep 17 00:00:00 2001
From: Brad Koehn <brad@koe.hn>
Date: Fri, 24 Jan 2025 12:06:05 -0600
Subject: [PATCH 4/5] Lint

---
 src/pages/EditMonitor.vue | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/pages/EditMonitor.vue b/src/pages/EditMonitor.vue
index bc3ef4224..c329b8923 100644
--- a/src/pages/EditMonitor.vue
+++ b/src/pages/EditMonitor.vue
@@ -332,7 +332,7 @@
                                 </select>
                             </div>
 
-                            <i18n-t  v-if="monitor.type === 'smtp'" keypath="smtpHelpText" tag="div" class="my-3">
+                            <i18n-t v-if="monitor.type === 'smtp'" keypath="smtpHelpText" tag="div" class="my-3">
                                 <label for="smtp_security" class="form-label">{{ $t("SMTP Security") }}</label>
                                 <select id="smtp_security" v-model="monitor.smtpSecurity" class="form-select">
                                     <option value="secure">SMTPS</option>

From 2353ba58b2a6c9a257f226ff4c909083d78626f0 Mon Sep 17 00:00:00 2001
From: Brad Koehn <brad@koe.hn>
Date: Mon, 27 Jan 2025 12:20:50 -0600
Subject: [PATCH 5/5] Updated help text

---
 src/lang/en.json          | 2 +-
 src/pages/EditMonitor.vue | 7 +++++--
 2 files changed, 6 insertions(+), 3 deletions(-)

diff --git a/src/lang/en.json b/src/lang/en.json
index b324f9f78..e7d31d1e4 100644
--- a/src/lang/en.json
+++ b/src/lang/en.json
@@ -1052,5 +1052,5 @@
     "rabbitmqHelpText": "To use the monitor, you will need to enable the Management Plugin in your RabbitMQ setup. For more information, please consult the {rabitmq_documentation}.",
     "SendGrid API Key": "SendGrid API Key",
     "Separate multiple email addresses with commas": "Separate multiple email addresses with commas",
-    "smtpHelpText": "Specify the TLS settings of the SMTP server you wish to monitor: SMTPS, STARTTLS, or ignore TLS"
+    "smtpHelpText": "“SMTPS” tests that SMTP/TLS is working; “Ignore TLS” connects over plaintext; “STARTTLS” connects, issues a STARTTLS command and verifies the server certificate. None of these send an email."
 }
diff --git a/src/pages/EditMonitor.vue b/src/pages/EditMonitor.vue
index c329b8923..e25dd8e4a 100644
--- a/src/pages/EditMonitor.vue
+++ b/src/pages/EditMonitor.vue
@@ -332,14 +332,17 @@
                                 </select>
                             </div>
 
-                            <i18n-t v-if="monitor.type === 'smtp'" keypath="smtpHelpText" tag="div" class="my-3">
+                            <div v-if="monitor.type === 'smtp'" class="my-3">
                                 <label for="smtp_security" class="form-label">{{ $t("SMTP Security") }}</label>
                                 <select id="smtp_security" v-model="monitor.smtpSecurity" class="form-select">
                                     <option value="secure">SMTPS</option>
                                     <option value="nostarttls">Ignore STARTTLS</option>
                                     <option value="starttls">Use STARTTLS</option>
                                 </select>
-                            </i18n-t>
+                                <div class="form-text">
+                                    {{ $t("smtpHelpText") }}
+                                </div>
+                            </div>
 
                             <!-- Json Query -->
                             <!-- For Json Query / SNMP -->