diff --git a/packages/backend/src/apps/ntfy/actions/index.ts b/packages/backend/src/apps/ntfy/actions/index.ts
new file mode 100644
index 00000000..37aeb338
--- /dev/null
+++ b/packages/backend/src/apps/ntfy/actions/index.ts
@@ -0,0 +1,3 @@
+import sendMessage from './send-message';
+
+export default [sendMessage];
diff --git a/packages/backend/src/apps/ntfy/actions/send-message/index.ts b/packages/backend/src/apps/ntfy/actions/send-message/index.ts
new file mode 100644
index 00000000..8cc91584
--- /dev/null
+++ b/packages/backend/src/apps/ntfy/actions/send-message/index.ts
@@ -0,0 +1,103 @@
+import qs from 'qs';
+import defineAction from '../../../../helpers/define-action';
+
+export default defineAction({
+ name: 'Send message',
+ key: 'sendMessage',
+ description: 'Sends a message to a topic you specify.',
+ arguments: [
+ {
+ label: 'Topic',
+ key: 'topic',
+ type: 'string' as const,
+ required: true,
+ description: 'Target topic name.',
+ variables: true,
+ },
+ {
+ label: 'Message body',
+ key: 'message',
+ type: 'string' as const,
+ required: true,
+ description: 'Message body to be sent, set to triggered if empty or not passed.',
+ variables: true,
+ },
+ {
+ label: 'Title',
+ key: 'title',
+ type: 'string' as const,
+ required: false,
+ description: 'Message title.',
+ variables: true,
+ },
+ {
+ label: 'Email',
+ key: 'email',
+ type: 'string' as const,
+ required: false,
+ description: 'E-mail address for e-mail notifications.',
+ variables: true,
+ },
+ {
+ label: 'Click URL',
+ key: 'click',
+ type: 'string' as const,
+ required: false,
+ description: 'Website opened when notification is clicked.',
+ variables: true,
+ },
+ {
+ label: 'Attach file by URL',
+ key: 'attach',
+ type: 'string' as const,
+ required: false,
+ description: 'URL of an attachment.',
+ variables: true,
+ },
+ {
+ label: 'Filename',
+ key: 'filename',
+ type: 'string' as const,
+ required: false,
+ description: 'File name of the attachment.',
+ variables: true,
+ },
+ {
+ label: 'Delay',
+ key: 'delay',
+ type: 'string' as const,
+ required: false,
+ description: 'Timestamp or duration for delayed delivery. For example, 30min or 9am.',
+ variables: true,
+ },
+ ],
+
+ async run($) {
+ const {
+ topic,
+ message,
+ title,
+ email,
+ click,
+ attach,
+ filename,
+ delay
+ } = $.step.parameters;
+ const payload = {
+ topic,
+ message,
+ title,
+ email,
+ click,
+ attach,
+ filename,
+ delay
+ };
+
+ const response = await $.http.post('/', payload);
+
+ $.setActionItem({
+ raw: response.data,
+ });
+ },
+});
diff --git a/packages/backend/src/apps/ntfy/assets/favicon.svg b/packages/backend/src/apps/ntfy/assets/favicon.svg
new file mode 100644
index 00000000..9e5b5136
--- /dev/null
+++ b/packages/backend/src/apps/ntfy/assets/favicon.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/packages/backend/src/apps/ntfy/auth/index.ts b/packages/backend/src/apps/ntfy/auth/index.ts
new file mode 100644
index 00000000..5a96d5bc
--- /dev/null
+++ b/packages/backend/src/apps/ntfy/auth/index.ts
@@ -0,0 +1,39 @@
+import verifyCredentials from './verify-credentials';
+import isStillVerified from './is-still-verified';
+
+export default {
+ fields: [
+ {
+ key: 'serverUrl',
+ label: 'Server URL',
+ type: 'string' as const,
+ required: true,
+ readOnly: false,
+ value: 'https://ntfy.sh',
+ placeholder: null,
+ description: 'ntfy server to use.',
+ clickToCopy: false,
+ },
+ {
+ key: 'username',
+ label: 'Username',
+ type: 'string' as const,
+ required: false,
+ readOnly: false,
+ placeholder: null,
+ clickToCopy: false,
+ },
+ {
+ key: 'password',
+ label: 'Password',
+ type: 'string' as const,
+ required: false,
+ readOnly: false,
+ placeholder: null,
+ clickToCopy: false,
+ },
+ ],
+
+ verifyCredentials,
+ isStillVerified,
+};
diff --git a/packages/backend/src/apps/ntfy/auth/is-still-verified.ts b/packages/backend/src/apps/ntfy/auth/is-still-verified.ts
new file mode 100644
index 00000000..66bb963e
--- /dev/null
+++ b/packages/backend/src/apps/ntfy/auth/is-still-verified.ts
@@ -0,0 +1,9 @@
+import { IGlobalVariable } from '@automatisch/types';
+import verifyCredentials from './verify-credentials';
+
+const isStillVerified = async ($: IGlobalVariable) => {
+ await verifyCredentials($);
+ return true;
+};
+
+export default isStillVerified;
diff --git a/packages/backend/src/apps/ntfy/auth/verify-credentials.ts b/packages/backend/src/apps/ntfy/auth/verify-credentials.ts
new file mode 100644
index 00000000..84d1d623
--- /dev/null
+++ b/packages/backend/src/apps/ntfy/auth/verify-credentials.ts
@@ -0,0 +1,16 @@
+import { IGlobalVariable } from '@automatisch/types';
+
+const verifyCredentials = async ($: IGlobalVariable) => {
+ await $.http.post('/', { topic: 'automatisch' });
+ let screenName = $.auth.data.serverUrl;
+
+ if ($.auth.data.username) {
+ screenName = `${$.auth.data.username} @ ${screenName}`
+ }
+
+ await $.auth.set({
+ screenName,
+ });
+};
+
+export default verifyCredentials;
diff --git a/packages/backend/src/apps/ntfy/common/add-auth-header.ts b/packages/backend/src/apps/ntfy/common/add-auth-header.ts
new file mode 100644
index 00000000..ff194483
--- /dev/null
+++ b/packages/backend/src/apps/ntfy/common/add-auth-header.ts
@@ -0,0 +1,18 @@
+import { TBeforeRequest } from '@automatisch/types';
+
+const addAuthHeader: TBeforeRequest = ($, requestConfig) => {
+ if ($.auth.data.apiBaseUrl) {
+ requestConfig.baseURL = $.auth.data.apiBaseUrl as string;
+ }
+
+ if ($.auth.data?.username && $.auth.data?.password) {
+ requestConfig.auth = {
+ username: $.auth.data.username as string,
+ password: $.auth.data.password as string,
+ }
+ }
+
+ return requestConfig;
+};
+
+export default addAuthHeader;
diff --git a/packages/backend/src/apps/ntfy/index.d.ts b/packages/backend/src/apps/ntfy/index.d.ts
new file mode 100644
index 00000000..e69de29b
diff --git a/packages/backend/src/apps/ntfy/index.ts b/packages/backend/src/apps/ntfy/index.ts
new file mode 100644
index 00000000..1ba23966
--- /dev/null
+++ b/packages/backend/src/apps/ntfy/index.ts
@@ -0,0 +1,18 @@
+import defineApp from '../../helpers/define-app';
+import addAuthHeader from './common/add-auth-header';
+import auth from './auth';
+import actions from './actions';
+
+export default defineApp({
+ name: 'Ntfy',
+ key: 'ntfy',
+ iconUrl: '{BASE_URL}/apps/ntfy/assets/favicon.svg',
+ authDocUrl: 'https://automatisch.io/docs/apps/ntfy/connection',
+ supportsConnections: true,
+ baseUrl: 'https://ntfy.sh',
+ apiBaseUrl: 'https://ntfy.sh',
+ primaryColor: '56bda8',
+ beforeRequest: [addAuthHeader],
+ auth,
+ actions,
+});
diff --git a/packages/docs/pages/.vitepress/config.js b/packages/docs/pages/.vitepress/config.js
index 912ef91b..5e207ea5 100644
--- a/packages/docs/pages/.vitepress/config.js
+++ b/packages/docs/pages/.vitepress/config.js
@@ -69,6 +69,14 @@ export default defineConfig({
{ text: 'Connection', link: '/apps/github/connection' },
],
},
+ {
+ text: 'Ntfy',
+ collapsible: true,
+ items: [
+ { text: 'Actions', link: '/apps/ntfy/actions' },
+ { text: 'Connection', link: '/apps/ntfy/connection' },
+ ],
+ },
{
text: 'RSS',
collapsible: true,
diff --git a/packages/docs/pages/apps/ntfy/actions.md b/packages/docs/pages/apps/ntfy/actions.md
new file mode 100644
index 00000000..efc1bfbc
--- /dev/null
+++ b/packages/docs/pages/apps/ntfy/actions.md
@@ -0,0 +1,12 @@
+---
+favicon: /favicons/ntfy.svg
+items:
+ - name: Send a message
+ desc: Sends a message to a topic you specify.
+---
+
+
+
+
diff --git a/packages/docs/pages/apps/ntfy/connection.md b/packages/docs/pages/apps/ntfy/connection.md
new file mode 100644
index 00000000..d2d81ac5
--- /dev/null
+++ b/packages/docs/pages/apps/ntfy/connection.md
@@ -0,0 +1,10 @@
+# Ntfy
+
+:::info
+This page explains the steps you need to follow to set up the Ntfy
+connection in Automatisch. If any of the steps are outdated, please let us know!
+:::
+
+If you use ntfy.sh, the official public server for this service, you do not need to set up a connection with a custom configuration. It's enough to create one with the default server URL.
+
+However, if you have a ntfy installation, that's different than ntfy.sh, you need to specify your server URL on Automatisch while creating a connection. Additionally, you may need to provide your username and password if your installation requires authentication.
diff --git a/packages/docs/pages/guide/available-apps.md b/packages/docs/pages/guide/available-apps.md
index 42543151..e433054d 100644
--- a/packages/docs/pages/guide/available-apps.md
+++ b/packages/docs/pages/guide/available-apps.md
@@ -11,6 +11,7 @@ Following integrations are currently supported by Automatisch.
- [Flickr](/apps/flickr/triggers)
- [Github](/apps/github/triggers)
- [RSS](/apps/rss/triggers)
+- [Ntfy](/apps/ntfy/triggers)
- [Salesforce](/apps/salesforce/triggers)
- [Scheduler](/apps/scheduler/triggers)
- [Slack](/apps/slack/actions)
diff --git a/packages/docs/pages/public/favicons/ntfy.svg b/packages/docs/pages/public/favicons/ntfy.svg
new file mode 100644
index 00000000..9e5b5136
--- /dev/null
+++ b/packages/docs/pages/public/favicons/ntfy.svg
@@ -0,0 +1 @@
+
\ No newline at end of file