Repository: louislam/uptime-kuma
Pull request #5800: feat: Support SMSPlanet notification provider
Author: login: dpierwola
Labels: name: pr:depends on other
description: pending other things to be done first
Created: 2025-04-25T16:31:13Z | Merged: 2025-04-26T20:33:26Z

**⚠️ Please Note: We do not accept all types of pull requests, and we want to ensure we don’t waste your time. Before submitting, make sure you have read our pull request guidelines: [Pull Request Rules](https://github.com/louislam/uptime-kuma/blob/master/CONTRIBUTING.md#can-i-create-a-pull-request-for-uptime-kuma)**


## 📋 Overview

Provide a clear summary of the purpose and scope of this pull request:

- **What problem does this pull request address?**

  - It solves the lack of SMSPlanet integration, giving users—especially in Poland—an affordable and easy way to send SMS alerts.

- **What features or functionality does this pull request introduce or enhance?**

  - It introduces a new notification provider for SMSPlanet, including configuration options and sending SMS through their API.

## 🔄 Changes

### 🛠️ Type of change

<!-- Please select all options that apply -->

- [ ] 🐛 Bugfix (a non-breaking change that resolves an issue)
- [X] ✨ New feature (a non-breaking change that adds new functionality)
- [ ] ⚠️ Breaking change (a fix or feature that alters existing functionality in a way that could cause issues)
- [ ] 🎨 User Interface (UI) updates
- [ ] 📄 New Documentation (addition of new documentation)
- [ ] 📄 Documentation Update (modification of existing documentation)
- [ ] 📄 Documentation Update Required (the change requires updates to related documentation)
- [ ] 🔧 Other (please specify):


## 📄 Checklist *

<!-- Please select all options that apply -->

- [x] 🔍 My code adheres to the style guidelines of this project.
- [x] ✅ I ran ESLint and other code linters for modified files.
- [x] 🛠️ I have reviewed and tested my code.
- [x] 📝 I have commented my code, especially in hard-to-understand areas (e.g., using JSDoc for methods).
- [x] ⚠️ My changes generate no new warnings.
- [ ] 🤖 My code needed automated testing. I have added them (this is an optional task).
- [ ] 📄 Documentation updates are included (if applicable).
- [x] 🔒 I have considered potential security impacts and mitigated risks.
- [ ] 🧰 Dependency updates are listed and explained.
- [x] 📚 I have read and understood the [Pull Request guidelines](https://github.com/louislam/uptime-kuma/blob/master/CONTRIBUTING.md#recommended-pull-request-guideline).

## 📷 Screenshots or Visual Changes

![Zrzut ekranu 2025-04-25 163322](https://github.com/user-attachments/assets/b52e07df-cf8d-4143-935b-0f015ca9cc27)
![Zrzut ekranu 2025-04-25 163729](https://github.com/user-attachments/assets/b60fc1a4-0a8c-459f-84b5-3006ccab3795)
![051df2ee-6507-477c-977b-28b83e2f627d](https://github.com/user-attachments/assets/e78c6712-17f9-47de-aed4-80d85c3d2900)



Diff:
diff --git a/server/notification-providers/sms-planet.js b/server/notification-providers/sms-planet.js
new file mode 100644
index 00000000000..97e9e715e2d
--- /dev/null
+++ b/server/notification-providers/sms-planet.js
@@ -0,0 +1,40 @@
+const NotificationProvider = require("./notification-provider");
+const axios = require("axios");
+
+class SMSPlanet extends NotificationProvider {
+    name = "SMSPlanet";
+
+    /**
+     * @inheritdoc
+     */
+    async send(notification, msg, monitorJSON = null, heartbeatJSON = null) {
+        const okMsg = "Sent Successfully.";
+        const url = "https://api2.smsplanet.pl/sms";
+
+        try {
+            let config = {
+                headers: {
+                    "Authorization": "Bearer " + notification.smsplanetApiToken,
+                    "content-type": "multipart/form-data"
+                }
+            };
+
+            let data = {
+                "from": notification.smsplanetSenderName,
+                "to": notification.smsplanetPhoneNumbers,
+                "msg": msg.replace(/🔴/, "❌")
+            };
+
+            let response = await axios.post(url, data, config);
+            if (!response.data?.messageId) {
+                throw new Error(response.data?.errorMsg ?? "SMSPlanet server did not respond with the expected result");
+            }
+
+            return okMsg;
+        } catch (error) {
+            this.throwGeneralAxiosError(error);
+        }
+    }
+}
+
+module.exports = SMSPlanet;
diff --git a/server/notification.js b/server/notification.js
index 0c222d93263..537f7d137a2 100644
--- a/server/notification.js
+++ b/server/notification.js
@@ -72,6 +72,7 @@ const Onesender = require("./notification-providers/onesender");
 const Wpush = require("./notification-providers/wpush");
 const SendGrid = require("./notification-providers/send-grid");
 const YZJ = require("./notification-providers/yzj");
+const SMSPlanet = require("./notification-providers/sms-planet");
 
 class Notification {
 
@@ -160,7 +161,8 @@ class Notification {
             new Cellsynt(),
             new Wpush(),
             new SendGrid(),
-            new YZJ()
+            new YZJ(),
+            new SMSPlanet(),
         ];
         for (let item of list) {
             if (! item.name) {
diff --git a/src/components/NotificationDialog.vue b/src/components/NotificationDialog.vue
index 56cae66c8c6..20e61141841 100644
--- a/src/components/NotificationDialog.vue
+++ b/src/components/NotificationDialog.vue
@@ -185,7 +185,8 @@ export default {
                 "PushPlus": "PushPlus (推送加)",
                 "smsc": "SMSC",
                 "WPush": "WPush(wpush.cn)",
-                "YZJ": "YZJ (云之家自定义机器人)"
+                "YZJ": "YZJ (云之家自定义机器人)",
+                "SMSPlanet": "SMSPlanet.pl"
             };
 
             // Sort by notification name
diff --git a/src/components/notifications/SMSPlanet.vue b/src/components/notifications/SMSPlanet.vue
new file mode 100644
index 00000000000..bbf070461b5
--- /dev/null
+++ b/src/components/notifications/SMSPlanet.vue
@@ -0,0 +1,46 @@
+<template>
+    <div class="mb-3">
+        <label for="smsplanet-api-token" class="form-label">{{ $t('smsplanetApiToken') }}</label>
+        <HiddenInput id="smsplanet-api-token" v-model="$parent.notification.smsplanetApiToken" :required="true"></HiddenInput>
+        <i18n-t tag="div" keypath="smsplanetApiDocs" class="form-text">
+            <template #the_smsplanet_documentation>
+                <a
+                    href="https://smsplanet.pl/doc/slate/index.html#introduction"
+                    target="_blank"
+                >{{ $t("the smsplanet documentation") }}</a>
+            </template>
+        </i18n-t>
+    </div>
+    <div class="mb-3">
+        <label for="smsplanet-phone-numbers" class="form-label">{{ $t("Phone numbers") }}</label>
+        <textarea
+            id="smsplanet-phone-numbers"
+            v-model="$parent.notification.smsplanetPhoneNumbers"
+            class="form-control"
+            :placeholder="smsplanetPhoneNumbers"
+            required
+        ></textarea>
+    </div>
+    <div class="mb-3">
+        <label for="smsplanet-sender-name" class="form-label">{{ $t("Sender name") }}</label>
+        <input id="smsplanet-sender-name" v-model="$parent.notification.smsplanetSenderName" type="text" minlength="3" maxlength="11" class="form-control">
+        <div class="form-text">{{ $t("smsplanetNeedToApproveName") }}</div>
+    </div>
+</template>
+
+<script>
+import HiddenInput from "../HiddenInput.vue";
+
+export default {
+    components: {
+        HiddenInput,
+    },
+    computed: {
+        smsplanetPhoneNumbers() {
+            return this.$t("Example:", [
+                "+48123456789,+48111222333",
+            ]);
+        }
+    }
+};
+</script>
diff --git a/src/components/notifications/index.js b/src/components/notifications/index.js
index cbd6f2b685e..14989c45f01 100644
--- a/src/components/notifications/index.js
+++ b/src/components/notifications/index.js
@@ -70,6 +70,7 @@ import WPush from "./WPush.vue";
 import SIGNL4 from "./SIGNL4.vue";
 import SendGrid from "./SendGrid.vue";
 import YZJ from "./YZJ.vue";
+import SMSPlanet from "./SMSPlanet.vue";
 
 /**
  * Manage all notification form.
@@ -148,6 +149,7 @@ const NotificationFormList = {
     "WPush": WPush,
     "SendGrid": SendGrid,
     "YZJ": YZJ,
+    "SMSPlanet": SMSPlanet,
 };
 
 export default NotificationFormList;
diff --git a/src/lang/en.json b/src/lang/en.json
index c32bebaaea1..b53fa13500f 100644
--- a/src/lang/en.json
+++ b/src/lang/en.json
@@ -1068,5 +1068,11 @@
     "Plain Text": "Plain Text",
     "Message Template": "Message Template",
     "Template Format": "Template Format",
-    "Font Twemoji by Twitter licensed under": "Font Twemoji by Twitter licensed under"
+    "Font Twemoji by Twitter licensed under": "Font Twemoji by Twitter licensed under",
+    "smsplanetApiToken": "Token for the SMSPlanet API",
+    "smsplanetApiDocs": "Detailed information on obtaining API tokens can be found in {the_smsplanet_documentation}.",
+    "the smsplanet documentation": "the smsplanet documentation",
+    "Phone numbers": "Phone numbers",
+    "Sender name": "Sender name",
+    "smsplanetNeedToApproveName": "Needs to be approved in the client panel"
 }


Reviews:
user: login: CommanderStorm
body: Thanks for the new notification provider.

<img src="https://media1.giphy.com/media/v1.Y2lkPWJkM2VhNTdlaHIycXN6MWl3dW1nYzM4NGN6ZjM1eHJrNnp6dWNwZ3pyYWl5ZWc0YyZlcD12MV9naWZzX3NlYXJjaCZjdD1n/3oEdva9BUHPIs2SkGk/giphy.gif"/>
state: APPROVED
author_association: COLLABORATOR
submitted_at: 2025-04-25T19:23:40Z
user: login: CommanderStorm
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-25T19:45:08Z

Review comments:
diff_hunk: @@ -1068,5 +1068,9 @@
     "Plain Text": "Plain Text",
     "Message Template": "Message Template",
     "Template Format": "Template Format",
-    "Font Twemoji by Twitter licensed under": "Font Twemoji by Twitter licensed under"
+    "Font Twemoji by Twitter licensed under": "Font Twemoji by Twitter licensed under",
+    "smsplanetApiToken": "Token for the SMSPlanet API",
+    "smsplanetApiDocs": "Detailed information on obtaining the API token can be found in the documentation available at {0}.",
path: src/lang/en.json
user: login: CommanderStorm
body: ```suggestion
    "smsplanetApiDocs": "Detailed information on obtaining API tokens can be found in {the_smsplanet_documentation}.",
    "the smsplanet documentation": "the smsplanet documentation",
```
created_at: 2025-04-25T19:11:44Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,43 @@
+<template>
+    <div class="mb-3">
+        <label for="smsplanet-api-token" class="form-label">{{ $t('smsplanetApiToken') }}</label>
+        <HiddenInput id="smsplanet-api-token" v-model="$parent.notification.smsplanetApiToken" :required="true"></HiddenInput>
+        <i18n-t tag="div" keypath="smsplanetApiDocs" class="form-text">
+            <a
+                href="https://smsplanet.pl/doc/slate/index.html#introduction"
+                target="_blank"
+            >{{ $t("here") }}</a>
+        </i18n-t>
+    </div>
+    <div class="mb-3">
+        <label for="smsplanet-phone-numbers" class="form-label">{{ $t("smsplanetPhoneNumber") }}</label>
+        <textarea
+            id="smsplanet-phone-numbers"
+            v-model="$parent.notification.smsplanetPhoneNumbers"
+            class="form-control"
+            :placeholder="smsplanetPhoneNumbers"
+            required
+        ></textarea>
+    </div>
+    <div class="mb-3">
+        <label for="smsplanet-sender-name" class="form-label">{{ $t("smsplanetSenderName") }}</label>
+        <input id="smsplanet-sender-name" v-model="$parent.notification.smsplanetSenderName" type="text" minlength="3" maxlength="11" class="form-control">
path: src/components/notifications/SMSPlanet.vue
user: login: CommanderStorm
body: ```suggestion
        <input id="smsplanet-sender-name" v-model="$parent.notification.smsplanetSenderName" type="text" minlength="3" maxlength="11" class="form-control">
        <div class="form-text">{{ $t("smsplanetNeedToApproveName") }}<div>
```
created_at: 2025-04-25T19:15:17Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,43 @@
+<template>
+    <div class="mb-3">
+        <label for="smsplanet-api-token" class="form-label">{{ $t('smsplanetApiToken') }}</label>
+        <HiddenInput id="smsplanet-api-token" v-model="$parent.notification.smsplanetApiToken" :required="true"></HiddenInput>
+        <i18n-t tag="div" keypath="smsplanetApiDocs" class="form-text">
+            <a
+                href="https://smsplanet.pl/doc/slate/index.html#introduction"
+                target="_blank"
+            >{{ $t("here") }}</a>
path: src/components/notifications/SMSPlanet.vue
user: login: CommanderStorm
body: ```suggestion
            <template #the_smsplanet_documentation>
                <a
                    href="https://smsplanet.pl/doc/slate/index.html#introduction"
                    target="_blank"
                >{{ $t("the smsplanet documentation") }}</a>
            </template>
```
created_at: 2025-04-25T19:18:05Z
author_association: COLLABORATOR
diff_hunk: @@ -1068,5 +1068,9 @@
     "Plain Text": "Plain Text",
     "Message Template": "Message Template",
     "Template Format": "Template Format",
-    "Font Twemoji by Twitter licensed under": "Font Twemoji by Twitter licensed under"
+    "Font Twemoji by Twitter licensed under": "Font Twemoji by Twitter licensed under",
+    "smsplanetApiToken": "Token for the SMSPlanet API",
+    "smsplanetApiDocs": "Detailed information on obtaining the API token can be found in the documentation available at {0}.",
+    "smsplanetPhoneNumber": "Phone numbers",
+    "smsplanetSenderName": "Sender name (approved in the client panel)"
path: src/lang/en.json
user: login: CommanderStorm
body: ```suggestion
    "Sender name": "Sender name",
    "smsplanetNeedToApproveName": "Needs to be approved in the client panel"
```
created_at: 2025-04-25T19:18:29Z
author_association: COLLABORATOR
diff_hunk: @@ -1068,5 +1068,9 @@
     "Plain Text": "Plain Text",
     "Message Template": "Message Template",
     "Template Format": "Template Format",
-    "Font Twemoji by Twitter licensed under": "Font Twemoji by Twitter licensed under"
+    "Font Twemoji by Twitter licensed under": "Font Twemoji by Twitter licensed under",
+    "smsplanetApiToken": "Token for the SMSPlanet API",
+    "smsplanetApiDocs": "Detailed information on obtaining the API token can be found in the documentation available at {0}.",
+    "smsplanetPhoneNumber": "Phone numbers",
path: src/lang/en.json
user: login: CommanderStorm
body: ```suggestion
    "Phone numbers": "Phone numbers",
```
created_at: 2025-04-25T19:19:34Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,43 @@
+<template>
+    <div class="mb-3">
+        <label for="smsplanet-api-token" class="form-label">{{ $t('smsplanetApiToken') }}</label>
+        <HiddenInput id="smsplanet-api-token" v-model="$parent.notification.smsplanetApiToken" :required="true"></HiddenInput>
+        <i18n-t tag="div" keypath="smsplanetApiDocs" class="form-text">
+            <a
+                href="https://smsplanet.pl/doc/slate/index.html#introduction"
+                target="_blank"
+            >{{ $t("here") }}</a>
+        </i18n-t>
+    </div>
+    <div class="mb-3">
+        <label for="smsplanet-phone-numbers" class="form-label">{{ $t("smsplanetPhoneNumber") }}</label>
path: src/components/notifications/SMSPlanet.vue
user: login: CommanderStorm
body: ```suggestion
        <label for="smsplanet-phone-numbers" class="form-label">{{ $t("Phone numbers") }}</label>
```
created_at: 2025-04-25T19:20:39Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,43 @@
+<template>
+    <div class="mb-3">
+        <label for="smsplanet-api-token" class="form-label">{{ $t('smsplanetApiToken') }}</label>
+        <HiddenInput id="smsplanet-api-token" v-model="$parent.notification.smsplanetApiToken" :required="true"></HiddenInput>
+        <i18n-t tag="div" keypath="smsplanetApiDocs" class="form-text">
+            <a
+                href="https://smsplanet.pl/doc/slate/index.html#introduction"
+                target="_blank"
+            >{{ $t("here") }}</a>
+        </i18n-t>
+    </div>
+    <div class="mb-3">
+        <label for="smsplanet-phone-numbers" class="form-label">{{ $t("smsplanetPhoneNumber") }}</label>
+        <textarea
+            id="smsplanet-phone-numbers"
+            v-model="$parent.notification.smsplanetPhoneNumbers"
+            class="form-control"
+            :placeholder="smsplanetPhoneNumbers"
+            required
+        ></textarea>
+    </div>
+    <div class="mb-3">
+        <label for="smsplanet-sender-name" class="form-label">{{ $t("smsplanetSenderName") }}</label>
path: src/components/notifications/SMSPlanet.vue
user: login: CommanderStorm
body: ```suggestion
        <label for="smsplanet-sender-name" class="form-label">{{ $t("Sender name") }}</label>
```
created_at: 2025-04-25T19:21:02Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,46 @@
+<template>
+    <div class="mb-3">
+        <label for="smsplanet-api-token" class="form-label">{{ $t('smsplanetApiToken') }}</label>
+        <HiddenInput id="smsplanet-api-token" v-model="$parent.notification.smsplanetApiToken" :required="true"></HiddenInput>
+        <i18n-t tag="div" keypath="smsplanetApiDocs" class="form-text">
+            <template #the_smsplanet_documentation>
+                <a
+                    href="https://smsplanet.pl/doc/slate/index.html#introduction"
+                    target="_blank"
+                >{{ $t("the smsplanet documentation") }}</a>
+            </template>
+        </i18n-t>
+    </div>
+    <div class="mb-3">
+        <label for="smsplanet-phone-numbers" class="form-label">{{ $t("Phone numbers") }}</label>
+        <textarea
+            id="smsplanet-phone-numbers"
+            v-model="$parent.notification.smsplanetPhoneNumbers"
+            class="form-control"
+            :placeholder="smsplanetPhoneNumbers"
+            required
+        ></textarea>
+    </div>
+    <div class="mb-3">
+        <label for="smsplanet-sender-name" class="form-label">{{ $t("Sender name") }}</label>
+        <input id="smsplanet-sender-name" v-model="$parent.notification.smsplanetSenderName" type="text" minlength="3" maxlength="11" class="form-control">
+        <div class="form-text">{{ $t("smsplanetNeedToApproveName") }}<div>
path: src/components/notifications/SMSPlanet.vue
user: login: CommanderStorm
body: ```suggestion
        <div class="form-text">{{ $t("smsplanetNeedToApproveName") }}</div>
```
created_at: 2025-04-25T19:45:08Z
author_association: COLLABORATOR

PR comments:
user: login: dpierwola
created_at: 2025-04-26T20:12:08Z
body: @CommanderStorm  do you know when my changes will be merged into the master branch?
author_association: CONTRIBUTOR
user: login: CommanderStorm
created_at: 2025-04-26T20:33:20Z
body: Yea, lets move this and other features from 2.1 to 2.0.beta3
author_association: COLLABORATOR