Monitors that cross their threshold, containers that fall behind their registry and new agent releases all raise a notification. The agent sends every one of them to ntfy and to each configured webhook, so you can use one, both or several at the same time.
Everything here lives under notifications in configuration.yml.
notifications:
serverName: MyServer
ntfy:
enabled: true
url: "https://ntfy.sh"
topic: my-custom-globally-unique-topicLeaving topic out generates one from the server id. On a public ntfy instance anyone who knows the topic name can read your notifications, so pick something unguessable or use your own instance.
A protected topic needs credentials. Either an access token:
token: tk_AgQdq7mVBoFD37zQVN29RhuMzNIz2or a username and password:
username: user
password: passwordtoken wins if both are given. Tokens are made under Account → Access tokens in the ntfy web app.
Notifications are sent with ntfy tags for what happened and what it is about — rotating_light and
computer for a cpu alert, white_check_mark when it is back to normal — which ntfy turns into
emoji in front of the title. emoji: false sends no tags.
webhooks is a list, and each entry is one HTTP request the agent makes when something happens:
notifications:
webhooks:
- name: My receiver
enabled: true
url: "https://example.com/hook"
method: POST
contentType: application/json
headers:
X-Api-Key: secret
username: user
password: password
emoji: true
timeoutSeconds: 10
body: '{"text": "{{title}}"}'url is the only field without a useful default. username and password send an Authorization header with basic auth, and anything under headers is sent as given — use that for the token headers most services want. Requests are made in the background, so a receiver that is slow or down never holds up monitoring.
Without a body the agent posts this JSON, which is enough for anything that can read arbitrary fields (n8n, Node-RED, Home Assistant webhooks, your own script):
{
"title": "🚨 CPU load too high on MyServer",
"message": "🖥️ Load went above 80% to 94%",
"priority": 4,
"clickUrl": "https://monitee.app/server/<server id>/monitor/<monitor id>",
"eventType": "ONGOING_EVENT",
"monitorType": "CPU_LOAD",
"timestamp": "2026-08-05T12:34:56Z",
"serverName": "MyServer",
"serverId": "<server id>",
"eventId": "<event id>"
}eventType is one of ONGOING_EVENT, RESOLVED_EVENT, UPDATE_AVAILABLE, MONITORED_ITEM_MISSING, CONTAINER_IMAGE_UPDATE_AVAILABLE, CONTAINER_IMAGE_UPDATE_DIGEST or QUIET_HOURS_SUMMARY. monitorType is null for the events that do not come from a monitor. eventId is the id to pass to the acknowledgeEvent mutation, and null for the container update digest and the quiet hours summary.
The title is prefixed with an emoji for what happened, and the message with one for what it is about, so an alert is recognisable before reading it:
| 🚨 | a monitor went outside its threshold |
| ✅ | a monitor came back inside it |
| 🆕 | a new monitee-agent release |
| 🐳 | container image updates |
| ❓ | a monitored item is gone |
| 🌙 | what happened during quiet hours |
Messages from a monitor carry the metric instead: 🖥️ cpu, 🧠 memory, 💾 file system, 💽 disk, 🌡️ temperature, 🩺 SMART health, 📈 load average, 🌐 network, 🌍 web server, 📡 connectivity, 📍 external ip, 🐳 container, ⚙️ process, 🎮 gpu, 🔋 UPS.
This is on by default and applies to templates too, since it is part of {{title}} and
{{message}}. Set emoji: false on a webhook whose receiver would rather have plain text.
ntfy gets the same emoji as tags rather than as text, since that is how ntfy does it.
Services that expect their own shape get a body template instead. These placeholders are substituted:
{{title}} {{message}} {{priority}} {{clickUrl}} {{eventType}} {{monitorType}} {{timestamp}} {{serverName}} {{serverId}} {{eventId}}
Values are escaped for the contentType, so a message containing quotes or newlines will not break a JSON body. Placeholders in url are escaped for use in a url. Anything the agent does not recognise is left alone.
Gotify — the token goes in the url, or in a X-Gotify-Key header:
- name: Gotify
url: "https://gotify.example.com/message?token=APP_TOKEN"
body: '{"title": "{{title}}", "message": "{{message}}", "priority": {{priority}}}'Discord:
- name: Discord
url: "https://discord.com/api/webhooks/ID/TOKEN"
body: '{"content": "**{{title}}**\n{{message}}\n{{clickUrl}}"}'Slack (and Mattermost, which takes the same shape):
- name: Slack
url: "https://hooks.slack.com/services/T000/B000/XXXX"
body: '{"text": "*{{title}}*\n{{message}}"}'Telegram — chat_id is your own chat, from @userinfobot:
- name: Telegram
url: "https://api.telegram.org/bot<TOKEN>/sendMessage"
body: '{"chat_id": "123456789", "text": "{{title}}\n{{message}}\n{{clickUrl}}"}'Apprise, running as an API server, which reaches the ~100 services it supports from one webhook:
- name: Apprise
url: "https://apprise.example.com/notify/my-config-key"
body: '{"title": "{{title}}", "body": "{{message}}"}'Nextcloud notify_push takes the message as plain text rather than JSON:
- name: Nextcloud
url: "https://cloud.example.com/index.php/apps/uppush/push/DEVICE_KEY"
contentType: text/plain
body: '{{title}}: {{message}}'Home Assistant webhook trigger, where the default body is already what the automation gets as trigger.json:
- name: Home Assistant
url: "https://ha.example.com/api/webhook/WEBHOOK_ID"These are set from the app, next to Snooze, rather than in configuration.yml, and apply to ntfy, webhooks and MQTT alike. Home Assistant users who want MQTT unfiltered can leave quiet hours off and set their own quiet time in Home Assistant.
-
Quiet hours drop notifications between a start and an end time, on the days you pick, in a time zone you pick. A window may cross midnight.
allowCriticallets critical events through. When the window closes, one notification titled While quiet on MyServer lists the events that were raised inside it and are still ongoing and unacknowledged, and nothing is sent when there are none. -
Cooldown stops a flapping monitor from paging repeatedly: after a notification for an event on a monitor, further events on that monitor send nothing for
cooldownMinutes(5 by default, 0 turns it off). The events are still recorded and shown in the app, and the recovery of an event whose notification was dropped is dropped too. - notifyOnRecovery set to false stops the “back to normal” notifications.
Events stay visible in the app either way, and notified on an ongoing event says whether a notification went out for it. notificationServices.policy in the GraphQL API reports the settings and how many notifications each of them has dropped since the agent started.
Acknowledging an ongoing event, from the app or from the Acknowledge action on the ntfy notification (shown after Snooze when actions is on), says you have seen it. It then sends nothing more unless a warning escalates to critical, which clears the acknowledgement and notifies, and it is left out of the quiet hours summary. Its recovery is still sent.
The agent logs every failed delivery with the webhook’s name and the answer it got. A webhook whose url is not a valid http or https url is reported at startup and skipped — the rest keep working. notificationServices in the GraphQL API lists what the agent ended up with.