MQTT alert daemon with pluggable notification backends
Project description
mqtt-alerts
mqtt-alerts is a small daemon-style CLI that subscribes to MQTT topics, evaluates alert rules, and sends notifications through pluggable backends.
It exists separately from mtr2mqtt on purpose:
mtr2mqttpublishes measurement data to MQTT.mqtt-alertsconsumes MQTT data and turns sustained threshold violations into persistent alert instances and notifications.
That separation keeps the data publishing path simple while allowing alerting and notification behavior to evolve independently.
What It Does
- subscribes to configured MQTT topics
- parses JSON payloads and extracts a configured value field
- evaluates multiple independent rules per sensor
- tracks one persistent alert instance per firing period
- models the alert lifecycle as
pending,firing,acknowledged, andresolved - persists alert and acknowledgement state in SQLite so restart recovery is correct
- sends notifications through pluggable backends
- sends automatic recovery notifications when triggered rules return to normal
- supports Telegram acknowledgement buttons through outbound long polling only
What It Does Not Do
This project intentionally stays small. It still does not include:
- arbitrary expressions or a custom rule language
- multiple payload extraction syntaxes
- dashboards or a web UI
- silencing windows or schedules
- repeated reminders or escalation chains
- inbound webhooks or a public HTTP endpoint
Installation
Using pip
pip install mqtt-alerts
Using uv for development
uv sync --group dev
Basic Usage
Create a config file and run:
mqtt-alerts --config sample_config.yml
CLI flags:
--config,-c: path to the YAML configuration file--debug,-d: enable debug logging--quiet,-q: only log warnings and errors--version,-v: print the installed version
Environment variables:
MQTT_ALERTS_CONFIG_FILEMQTT_ALERTS_DEBUGMQTT_ALERTS_QUIET
The process polls the config file and applies sensor, rule, backend, and topic changes automatically. Changes to MQTT connection settings or the SQLite database path still require a restart.
Alert Lifecycle And Acknowledgements
Acknowledgement is modeled as a state transition of one alert instance, not as a delivery-side flag.
For each (sensor_id, rule_id) pair, mqtt-alerts tracks a lifecycle like this:
pending: the condition is active, but the hold duration has not elapsed yet.firing: the hold duration elapsed and an alert notification was sent.acknowledged: a human acknowledged the still-active alert instance.resolved: the condition cleared.
Important behavior:
- one rule can fire many times over the lifetime of the service
- each firing period gets its own alert instance id
- acknowledgement applies to one active alert instance only
- acknowledgement persists across restart
- acknowledgement does not resolve or clear the alert
- recovery can still be sent after an acknowledged alert later clears
Configuration
The configuration is YAML and keeps MQTT input, persisted state, sensors and rules, and notification backends separate.
mqtt:
host: localhost
port: 1883
topic_prefix: measurements
state:
database: ./mqtt-alerts.sqlite3
notifications:
backends:
- id: main_telegram
type: telegram
bot_token: 123456789:replace-me
chat_id: -1001234567890
polling_enabled: true
polling_timeout_seconds: 1
polling_interval_seconds: 0.0
- id: main_ntfy
type: ntfy
server: https://ntfy.sh
topic: some-secret-topic
sensors:
- id: freezer_1
name: Freezer 1
topic: receiver1/freezer1
value_field: temperature
rules:
- id: high_warn
direction: above
threshold: 5.0
hysteresis: 0.5
for: 15m
severity: warning
backend: main_telegram
enabled: true
title: Freezer 1 warning
message: Temperature is above limit
recovery_enabled: true
recovery_title: Freezer 1 recovered
recovery_message: Temperature is back within range
- id: high_critical
direction: above
threshold: 8.0
for: 10m
severity: critical
backend: main_ntfy
enabled: true
Notes:
mqtt.topic_prefixis optional. If set, sensor topics are resolved under that prefix unless already fully prefixed.- each sensor can have many rules
- each rule has its own severity, backend, timing, and enabled flag
- each rule can define
hysteresisto avoid alert/recovery flapping near the threshold - each rule can customize automatic recovery messages (
recovery_enabled,recovery_title,recovery_message) - state is tracked per
(sensor_id, rule_id)pair, while alert instances are tracked separately for each firing period - Telegram
chat_idmay be a numeric id such as-100...for groups
Notification Backends
The core evaluator emits a generic notification object. Delivery is handled by a backend abstraction so new backends can be added later without redesigning rule evaluation or persistence.
This repository currently ships with:
| Backend | Push notifications | Direct acknowledgement | How interactions arrive | Public internet exposure needed for mqtt-alerts |
Best fit |
|---|---|---|---|---|---|
ntfy |
Yes | No | N/A | No | Outbound notification delivery only |
telegram |
Yes | Yes | Bot API getUpdates long polling |
No | Primary mobile acknowledgement channel |
Practical conclusions:
ntfyis a good outbound notification backend, but it is not the primary interactive acknowledgement channel for this architecture.- Telegram works well here because
mqtt-alertscan poll outward to the Telegram Bot API and receive button presses without exposing a webhook endpoint.
Telegram Setup
1. Create a bot and get a token
- Talk to
@BotFatherin Telegram. - Run
/newbot. - Follow the prompts and copy the bot token.
Treat the bot token like a password. Anyone who has it can control the bot.
2. Find the chat id
For a direct chat:
- Start a chat with your bot.
- Send it any message.
- Query the Bot API:
curl "https://api.telegram.org/bot<YOUR_TOKEN>/getUpdates"
- Look for
message.chat.idin the JSON response.
For a group:
- Add the bot to the group.
- Send a message in the group.
- Query
getUpdatesthe same way. - Use the negative group
chat.id, usually starting with-100.
3. Configure the backend
notifications:
backends:
- id: main_telegram
type: telegram
bot_token: 123456789:replace-me
chat_id: -1001234567890
polling_enabled: true
polling_timeout_seconds: 1
polling_interval_seconds: 0.0
Field meanings:
bot_token: Telegram bot token from@BotFatherchat_id: one target chat for the first versionpolling_enabled: disable only if you want outbound Telegram messages without acknowledgementspolling_timeout_seconds: Bot API long-poll timeout; keep this modest because MQTT and Telegram polling share one process looppolling_interval_seconds: optional delay between polls
4. What the Telegram acknowledgement flow looks like
When a rule fires:
mqtt-alertssends a Telegram message to the configured chat.- The message includes an inline
Acknowledgebutton. - The callback payload contains a compact alert instance id.
When a user taps the button:
mqtt-alertsreceives the callback through Telegram long polling.- The matching active alert instance moves to
acknowledged. - The callback is answered so the Telegram client stops spinning.
- The Telegram message is updated when practical to show that it was acknowledged.
No inbound webhook and no public HTTP endpoint are required.
Security Notes
- keep the Telegram bot token out of screenshots, logs, shell history, and public repositories
- keep the config file readable only by the user or service account that runs
mqtt-alerts - if the bot is added to a shared group, anyone who can press the acknowledgement button in that chat can acknowledge alerts
How It Fits With mtr2mqtt
A typical setup looks like this:
mtr2mqttreads measurements from physical sensors and publishes JSON to MQTT topics.mqtt-alertssubscribes to selected measurement topics.mqtt-alertsextracts the configured value field and evaluates one or more rules per sensor.- When a condition stays active for long enough,
mqtt-alertscreates or updates an alert instance and sends a notification through the configured backend. - If Telegram is configured, acknowledgements flow back through Bot API long polling.
Architecture
The code is intentionally split into small modules:
mqtt_alerts.config: YAML loading and validationmqtt_alerts.engine: per-rule threshold evaluation plus alert lifecycle handlingmqtt_alerts.persistence: SQLite rule state and alert instance storagemqtt_alerts.notifications: backend abstraction plusntfyand Telegram delivery/pollingmqtt_alerts.runtime: MQTT subscription loop and application wiringmqtt_alerts.cli: CLI entrypoint
SQLite persistence now stores:
- current per-rule evaluation state
- the active alert instance id, when one exists
- alert instance lifecycle timestamps
- acknowledgement metadata such as who acknowledged and when
Existing databases are migrated on startup so in-flight rule state is not lost when upgrading.
Docker
Pull the latest image:
docker pull tvallas/mqtt-alerts:latest
Run it with a mounted config:
docker run --rm \
-v "$(pwd)/sample_config.yml:/config/config.yml:ro" \
-v "$(pwd)/state:/state" \
tvallas/mqtt-alerts:latest \
--config /config/config.yml
There is also a sibling docker-compose.yml example in the repository.
Development
Common commands:
make install
make lint
make test
make build
Release Flow
The repository mirrors the current mtr2mqtt approach:
- pull request CI for lint, tests, and package build
- Docker build smoke test for pull requests
- semantic-release driven GitHub and PyPI release flow
- Docker publish after a successful tagged release
- Dependabot for Python, Docker, and GitHub Actions updates
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file mqtt_alerts-0.3.0.tar.gz.
File metadata
- Download URL: mqtt_alerts-0.3.0.tar.gz
- Upload date:
- Size: 33.3 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
dfe0533c1968ca49dc8d20a3d4f347e8a8bded16d38a93b10c4e81233bda3a03
|
|
| MD5 |
479f56d45f6d341735ca2f4bdc583207
|
|
| BLAKE2b-256 |
7349f247d85e8e7d98fbff266b3a1ec7d80b1c78b24017290dee7234cec87fe6
|
Provenance
The following attestation bundles were made for mqtt_alerts-0.3.0.tar.gz:
Publisher:
semantic_release.yml on tvallas/mqtt-alerts
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
mqtt_alerts-0.3.0.tar.gz -
Subject digest:
dfe0533c1968ca49dc8d20a3d4f347e8a8bded16d38a93b10c4e81233bda3a03 - Sigstore transparency entry: 1343443667
- Sigstore integration time:
-
Permalink:
tvallas/mqtt-alerts@05ab4c544eb8f56c7169d7f6bb67ec9debce80a4 -
Branch / Tag:
refs/heads/master - Owner: https://github.com/tvallas
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
semantic_release.yml@05ab4c544eb8f56c7169d7f6bb67ec9debce80a4 -
Trigger Event:
push
-
Statement type:
File details
Details for the file mqtt_alerts-0.3.0-py3-none-any.whl.
File metadata
- Download URL: mqtt_alerts-0.3.0-py3-none-any.whl
- Upload date:
- Size: 25.2 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
d182c00402b525143df233ae81b6b74757963f504a9c9276ef030cd8efd26171
|
|
| MD5 |
cc104df9a5f2e51874dbf622eeda3c4e
|
|
| BLAKE2b-256 |
db3e5b8f8041d381f56c164af97d2541e4367c2136c92534522226b7eddc17f8
|
Provenance
The following attestation bundles were made for mqtt_alerts-0.3.0-py3-none-any.whl:
Publisher:
semantic_release.yml on tvallas/mqtt-alerts
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
mqtt_alerts-0.3.0-py3-none-any.whl -
Subject digest:
d182c00402b525143df233ae81b6b74757963f504a9c9276ef030cd8efd26171 - Sigstore transparency entry: 1343443677
- Sigstore integration time:
-
Permalink:
tvallas/mqtt-alerts@05ab4c544eb8f56c7169d7f6bb67ec9debce80a4 -
Branch / Tag:
refs/heads/master - Owner: https://github.com/tvallas
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
semantic_release.yml@05ab4c544eb8f56c7169d7f6bb67ec9debce80a4 -
Trigger Event:
push
-
Statement type: