Out of curiosity I've built what @luckow suggested, only with Cloudron API (notifications endpoint) -> n8n -> some magic -> ntfy.sh-app and it looks like this:
f036453c-4190-48d2-bae2-33c5e16133b5-image.png
Action gets triggered (either automatically or manually), n8n checks the cloudron api https://my.example1.com/api/v1/notifications (create a readonly access token in your profile and update the Bearer token with it) with the parameters acknowledged = false, so only unread notifications are collected.
The code-node does the following (filteredTitles because I don't care about app-updates, only actual issues, can be adjusted of course):
const notifications = items[0].json.notifications;
const types = notifications.map(notification => notification.type);
const titles = notifications.map(notification => notification.title);
// Filter notifications to remove those with type "appUpdated"
const filteredNotifications = notifications.filter(notification => notification.type != "appUpdated");
// Extract titles of the filtered notifications
const filteredTitles = filteredNotifications.map(notification => notification.title);
//return [{ json: { types, titles } }];
return [{ json: { filteredTitles } }];
This will result in an array of current notifications, e.g.
[
{
"filteredTitles": [
"Email is not configured properly",
"The app at cms.example.com ran out of memory."
]
}
]
The if-node checks if there's actual content. If it's empty, nothing will happen, if it's not, it will make a http request to your nfty instance and topic of your choice (saved credentials in n8n) and send the notifications:
2ae64242-ff94-44d3-843d-fb0154436eed-image.png
Since I was working with readonly tokens, I had no intentions of marking the notifications as read after sending out the message. So there's room for improvement, but I thought I would throw it in here as a PoC 🙂
Complete code (My_Cloudron_workflow.json) is here:
{
"name": "My Cloudron workflow",
"nodes": [
{
"parameters": {},
"id": "0dcf1d60-f8e7-4ffe-be14-64399a5c924d",
"name": "When clicking \"Test workflow\"",
"type": "n8n-nodes-base.manualTrigger",
"typeVersion": 1,
"position": [
240,
620
]
},
{
"parameters": {
"url": "https://my.example1.com/api/v1/notifications",
"sendQuery": true,
"queryParameters": {
"parameters": [
{
"name": "acknowledged",
"value": "false"
}
]
},
"sendHeaders": true,
"headerParameters": {
"parameters": [
{
"name": "Authorization",
"value": "Bearer 123"
}
]
},
"sendBody": true,
"bodyParameters": {
"parameters": [
{}
]
},
"options": {}
},
"id": "073de174-b658-480e-bb3c-6be103304bd9",
"name": "EX1",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [
460,
620
],
"alwaysOutputData": false
},
{
"parameters": {
"url": "https://my.example2.com/api/v1/notifications",
"sendQuery": true,
"queryParameters": {
"parameters": [
{
"name": "acknowledged",
"value": "false"
}
]
},
"sendHeaders": true,
"headerParameters": {
"parameters": [
{
"name": "Authorization",
"value": "Bearer 456"
}
]
},
"sendBody": true,
"bodyParameters": {
"parameters": [
{}
]
},
"options": {}
},
"id": "c29eae02-51b5-417c-a103-d35b4bcb15e8",
"name": "EX2",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [
460,
780
],
"alwaysOutputData": false
},
{
"parameters": {
"url": "https://my.example3.com/api/v1/notifications",
"sendQuery": true,
"queryParameters": {
"parameters": [
{
"name": "acknowledged",
"value": "false"
}
]
},
"sendHeaders": true,
"headerParameters": {
"parameters": [
{
"name": "Authorization",
"value": "Bearer 789"
}
]
},
"sendBody": true,
"bodyParameters": {
"parameters": [
{}
]
},
"options": {}
},
"id": "a00d01ae-fca9-41e8-b16f-3456459f414a",
"name": "EX3",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [
460,
460
],
"alwaysOutputData": false
},
{
"parameters": {
"jsCode": "const notifications = items[0].json.notifications;\nconst types = notifications.map(notification => notification.type);\nconst titles = notifications.map(notification => notification.title);\n\n// Filter notifications to remove those with type \"appUpdated\"\nconst filteredNotifications = notifications.filter(notification => notification.type != \"appUpdated\");\n\n// Extract titles of the filtered notifications\nconst filteredTitles = filteredNotifications.map(notification => notification.title);\n\n//return [{ json: { types, titles } }];\nreturn [{ json: { filteredTitles } }];"
},
"id": "fb776916-d0d4-46b6-8a7d-7de5694e650c",
"name": "Code",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
620,
460
]
},
{
"parameters": {
"jsCode": "const notifications = items[0].json.notifications;\nconst types = notifications.map(notification => notification.type);\nconst titles = notifications.map(notification => notification.title);\n\n// Filter notifications to remove those with type \"appUpdated\"\nconst filteredNotifications = notifications.filter(notification => notification.type != \"appUpdated\");\n\n// Extract titles of the filtered notifications\nconst filteredTitles = filteredNotifications.map(notification => notification.title);\n\n//return [{ json: { types, titles } }];\nreturn [{ json: { filteredTitles } }];"
},
"id": "968f2566-7983-46ef-b14a-3677a3a6fc88",
"name": "Code1",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
620,
620
]
},
{
"parameters": {
"jsCode": "const notifications = items[0].json.notifications;\nconst types = notifications.map(notification => notification.type);\nconst titles = notifications.map(notification => notification.title);\n\n// Filter notifications to remove those with type \"appUpdated\"\nconst filteredNotifications = notifications.filter(notification => notification.type != \"appUpdated\");\n\n// Extract titles of the filtered notifications\nconst filteredTitles = filteredNotifications.map(notification => notification.title);\n\n//return [{ json: { types, titles } }];\nreturn [{ json: { filteredTitles } }];"
},
"id": "5463ace4-ed1f-408a-a13c-a153fbda2ca3",
"name": "Code2",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
620,
780
]
},
{
"parameters": {
"method": "POST",
"url": "https://ntfy.nftyserver.com/cloudron-notifications",
"authentication": "genericCredentialType",
"genericAuthType": "httpBasicAuth",
"sendHeaders": true,
"headerParameters": {
"parameters": [
{
"name": "Title",
"value": "Current example3.com notifications"
}
]
},
"sendBody": true,
"contentType": "raw",
"rawContentType": "Title",
"body": "={{ $json.filteredTitles.join(\"\\n\"); }}",
"options": {}
},
"id": "83bc0ca8-d7e1-4fb5-8dd9-0b3360f6763e",
"name": "HTTP Request",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [
940,
460
],
"credentials": {
"httpBasicAuth": {
"id": "5tUu1DjoNUxl0M5a",
"name": "ntfy token"
}
}
},
{
"parameters": {
"method": "POST",
"url": "https://ntfy.nftyserver.com/cloudron-notifications",
"authentication": "genericCredentialType",
"genericAuthType": "httpBasicAuth",
"sendHeaders": true,
"headerParameters": {
"parameters": [
{
"name": "Title",
"value": "Current example1.com notifications"
}
]
},
"sendBody": true,
"contentType": "raw",
"rawContentType": "Title",
"body": "={{ $json.filteredTitles.join(\"\\n\"); }}",
"options": {}
},
"id": "92815612-9381-444f-86b5-7c3c3cf8a371",
"name": "HTTP Request1",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [
940,
620
],
"credentials": {
"httpBasicAuth": {
"id": "5tUu1DjoNUxl0M5a",
"name": "ntfy token"
}
}
},
{
"parameters": {
"method": "POST",
"url": "https://ntfy.nftyserver.com/cloudron-notifications",
"authentication": "genericCredentialType",
"genericAuthType": "httpBasicAuth",
"sendHeaders": true,
"headerParameters": {
"parameters": [
{
"name": "Title",
"value": "Current example2.com notifications"
}
]
},
"sendBody": true,
"contentType": "raw",
"rawContentType": "Title",
"body": "={{ $json.filteredTitles.join(\"\\n\"); }}",
"options": {}
},
"id": "bc221e4f-8817-4c19-a74d-9333a4783319",
"name": "HTTP Request2",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [
940,
780
],
"credentials": {
"httpBasicAuth": {
"id": "5tUu1DjoNUxl0M5a",
"name": "ntfy token"
}
}
},
{
"parameters": {
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "strict"
},
"conditions": [
{
"id": "43482a2f-b2b0-4f0a-b970-7b11e8f6ee03",
"leftValue": "={{ $json.filteredTitles }}",
"rightValue": "",
"operator": {
"type": "array",
"operation": "notEmpty",
"singleValue": true
}
}
],
"combinator": "and"
},
"options": {}
},
"id": "af3ebb14-32db-4afb-8c9d-84eb593eae96",
"name": "If1",
"type": "n8n-nodes-base.if",
"typeVersion": 2,
"position": [
780,
620
]
},
{
"parameters": {
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "strict"
},
"conditions": [
{
"id": "1811bb6f-55ca-4b2d-9f6b-97c489ef4d21",
"leftValue": "={{ $json.filteredTitles }}",
"rightValue": "",
"operator": {
"type": "array",
"operation": "notEmpty",
"singleValue": true
}
}
],
"combinator": "or"
},
"options": {}
},
"id": "e354d631-5daf-495d-a5e2-e0fb64ef212d",
"name": "If2",
"type": "n8n-nodes-base.if",
"typeVersion": 2,
"position": [
780,
780
]
},
{
"parameters": {
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "strict"
},
"conditions": [
{
"id": "43482a2f-b2b0-4f0a-b970-7b11e8f6ee03",
"leftValue": "={{ $json.filteredTitles }}",
"rightValue": "",
"operator": {
"type": "array",
"operation": "notEmpty",
"singleValue": true
}
}
],
"combinator": "and"
},
"options": {}
},
"id": "a3fc1a27-f94d-4651-884f-34c683b0a2da",
"name": "If",
"type": "n8n-nodes-base.if",
"typeVersion": 2,
"position": [
780,
460
]
}
],
"pinData": {},
"connections": {
"When clicking \"Test workflow\"": {
"main": [
[
{
"node": "EX1",
"type": "main",
"index": 0
},
{
"node": "EX2",
"type": "main",
"index": 0
},
{
"node": "EX3",
"type": "main",
"index": 0
}
]
]
},
"EX1": {
"main": [
[
{
"node": "Code1",
"type": "main",
"index": 0
}
]
]
},
"EX2": {
"main": [
[
{
"node": "Code2",
"type": "main",
"index": 0
}
]
]
},
"EX3": {
"main": [
[
{
"node": "Code",
"type": "main",
"index": 0
}
]
]
},
"Code1": {
"main": [
[
{
"node": "If1",
"type": "main",
"index": 0
}
]
]
},
"Code2": {
"main": [
[
{
"node": "If2",
"type": "main",
"index": 0
}
]
]
},
"Code": {
"main": [
[
{
"node": "If",
"type": "main",
"index": 0
}
]
]
},
"If1": {
"main": [
[
{
"node": "HTTP Request1",
"type": "main",
"index": 0
}
]
]
},
"If2": {
"main": [
[
{
"node": "HTTP Request2",
"type": "main",
"index": 0
}
]
]
},
"If": {
"main": [
[
{
"node": "HTTP Request",
"type": "main",
"index": 0
}
]
]
}
},
"active": false,
"settings": {
"executionOrder": "v1"
},
"versionId": "b1689af0-81c6-453c-a8aa-1d7c84d7a85e",
"meta": {
"templateCredsSetupCompleted": true,
"instanceId": "8903125d64295093a384f52f480a1738308c71ada8db56c53efba4894aefbff2"
},
"id": "I2Mq9Y8GOPIDIh8i",
"tags": []
}
I threw out a module last minute, so hopefully no oversights in there. Improvements are welcome! 😉
TL;DR: Adjust the URLs to your Cloudrons, create and add readonly tokens, add a ntfy token to your n8n credentials and adjust the URL to your ntfy instance.