A small C#/.NET worker that listens to Frigate MQTT events, applies custom notification rules, and sends web-push notifications through Frigate's existing saved browser/device subscriptions.
This project was made with Codex.
The default configuration is intentionally safe for first rollout: Listener:DryRun is true, so the service logs would-be notifications instead of sending pushes.
Tested with Frigate 0.17.1-416a9b7.
This project depends on Frigate's MQTT frigate/events payload and the SQLite user.notification_tokens storage used by Frigate web-push notifications. Other Frigate 0.17.x builds are expected to work, but check the logs in dry-run mode before enabling real pushes.
- Dockerized .NET 8 worker service.
- Prebuilt container image published to GitHub Container Registry.
- MQTT subscription using
MQTTnet. - Frigate event parsing from
frigate/events. - Label and sub-label filtering.
- Optional notification when a Frigate
sub_labelappears later on an already-seen event. - Per-camera 5-minute cooldown and duplicate event suppression.
- Read-only Frigate SQLite subscription lookup.
- Web-push sending using Frigate's existing VAPID key material.
- Local Mosquitto broker in Docker Compose.
.
├── Dockerfile
├── docker-compose.yml
├── mosquitto/mosquitto.conf
└── src/FrigateMqttPushListener
├── Program.cs
├── appsettings.json
├── Events/
├── Filtering/
├── Mqtt/
├── Options/
├── Push/
└── State/
Primary config lives in src/FrigateMqttPushListener/appsettings.json and can be overridden with environment variables.
Important settings:
{
"Listener": {
"DryRun": true,
"CooldownSeconds": 300,
"MinimumScore": 0.8,
"AllowedLabels": [ "person" ],
"IgnoredLabels": [ "bird", "mouse" ],
"AllowedSubLabels": [],
"IgnoredSubLabels": [],
"NotifyOnSubLabelChange": true,
"NotifyTypes": [ "new", "update" ]
},
"Mqtt": {
"Host": "mosquitto",
"Port": 1883,
"Topic": "frigate/events"
},
"Push": {
"FrigateDatabasePath": "/frigate/frigate.db",
"NotificationsPemPath": "/frigate/notifications.pem",
"Subject": "mailto:admin@example.com"
}
}Environment override examples:
Listener__DryRun=false
Listener__CooldownSeconds=300
Listener__MinimumScore=0.8
Mqtt__Host=mosquitto
Push__Subject=mailto:admin@example.comDocker Compose reads these values from .env if present. Start from the example file:
cp .env.example .envThen update FRIGATE_CONFIG_DIR to the host directory that contains your Frigate frigate.db, frigate.db-wal, frigate.db-shm, and notifications.pem.
Common Frigate config paths:
/opt/frigate/config
/home/<user>/frigate/config
/path/to/your/frigate/config
The directory is mounted read-only into the listener container as /frigate.
- Create a project folder:
mkdir frigate-mqtt-push-listener
cd frigate-mqtt-push-listener- Download the Compose and environment examples:
curl -fsSLO https://raw.githubusercontent.com/darthza/FrigatePushNotificationHook/main/docker-compose.yml
curl -fsSLo .env.example https://raw.githubusercontent.com/darthza/FrigatePushNotificationHook/main/.env.example
cp .env.example .env- Edit
.env:
LISTENER_DRY_RUN=true
LISTENER_MINIMUM_SCORE=0.8
PUSH_SUBJECT=mailto:admin@example.com
FRIGATE_CONFIG_DIR=/opt/frigate/config- Start Mosquitto and the listener:
docker compose up -d
docker compose logs -f frigate-mqtt-push-listener-
Enable MQTT in Frigate and restart Frigate.
-
Watch the listener logs. It should first run in dry-run mode and log decisions without sending pushes.
-
When the filters look right, set
LISTENER_DRY_RUN=falsein.envand recreate the listener:
docker compose up -d frigate-mqtt-push-listenerStart the broker and listener:
docker compose up -d
docker compose logs -f frigate-mqtt-push-listenerBy default, the listener runs in dry-run mode:
LISTENER_DRY_RUN=trueWhen logs show the expected notification decisions, enable real pushes:
LISTENER_DRY_RUN=falseThen recreate the listener:
docker compose up -d frigate-mqtt-push-listenerIf you already have an MQTT broker, you can run only the listener container.
Pull the image:
docker pull ghcr.io/darthza/frigatepushnotificationhook:latestRun it:
mkdir -p ./state
docker run -d \
--name frigate-mqtt-push-listener \
--restart unless-stopped \
--user 1000:1000 \
-e Listener__DryRun=true \
-e Listener__MinimumScore=0.8 \
-e Mqtt__Host=<your-mqtt-host> \
-e Mqtt__Port=1883 \
-e Push__Subject=mailto:admin@example.com \
-v /path/to/frigate/config:/frigate:ro \
-v "$PWD/state:/app/state" \
ghcr.io/darthza/frigatepushnotificationhook:latestWatch logs:
docker logs -f frigate-mqtt-push-listenerAfter the dry-run output looks right, recreate the container with:
-e Listener__DryRun=falseImages are published to GitHub Container Registry:
ghcr.io/darthza/frigatepushnotificationhook
Available tag patterns:
latest: currentmainbranch build.X.Y.Z: exact release version.X.Y: release series tag.
For a stable deployment, pin a release tag instead of latest:
image: ghcr.io/darthza/frigatepushnotificationhook:0.1.0Frigate needs MQTT enabled and must be able to reach the broker.
If using the included Docker Compose broker, attach Frigate to the frigate-listener Docker network and configure Frigate like this:
mqtt:
enabled: true
host: mosquitto
port: 1883
topic_prefix: frigateDo not expose MQTT to the public internet.
If Frigate is managed by another Compose file, attach it to the same external network:
services:
frigate:
networks:
- frigate-listener
networks:
frigate-listener:
external: trueThen use host: mosquitto in Frigate's MQTT config.
Run your own MQTT broker or publish this Compose broker only on a trusted LAN interface. Then point Frigate and the listener at that broker:
MQTT_HOST=<your-mqtt-host>
MQTT_PORT=1883Keep the broker private. MQTT should not be exposed to the public internet.
Check containers:
docker compose psWatch listener logs:
docker compose logs -f frigate-mqtt-push-listenerExpected dry-run examples:
Skipping Frigate event ...: label 'bird' is ignored
DRY RUN push: FrontYard: person detected - person detected on FrontYard
Optional synthetic test event:
docker exec frigate-listener-mosquitto mosquitto_pub \
-h 127.0.0.1 \
-t frigate/events \
-m '{"type":"new","after":{"id":"test-event-1","camera":"TestCamera","label":"person","entered_zones":[]}}'The title and body support simple placeholders:
{camera}{label}{sub_label}{sub_label_score}{person}{score}{type}{id}
{sub_label} resolves to the known-person name when Frigate provides one.
{sub_label_score} resolves to the known-person confidence as a percentage, for example 99%.
{person} resolves to the known-person name with confidence when available, for example Pieter (99%), otherwise it falls back to the object label.
{score} resolves to the Frigate object confidence as a percentage, for example 83%.
Example:
{
"TitleTemplate": "{camera}: {person} detected",
"BodyTemplate": "{person} detected on {camera}"
}Build locally:
dotnet buildBuild the container locally:
docker build -t frigate-mqtt-push-listener:local .Publish locally:
dotnet publish src/FrigateMqttPushListener/FrigateMqttPushListener.csproj -c ReleaseRun locally against a reachable broker:
Mqtt__Host=localhost \
Push__FrigateDatabasePath=/path/to/frigate.db \
Push__NotificationsPemPath=/path/to/notifications.pem \
dotnet run --project src/FrigateMqttPushListener- Keep
DryRunenabled until real Frigate MQTT traffic has been observed and filtering is trusted. - Mount Frigate database and VAPID key read-only.
- Do not remove or rotate Frigate's
notifications.pem; existing browser/device subscriptions depend on it. - Do not commit
.env, Frigate databases, VAPID keys, logs, or listener state. - Do not expose MQTT publicly.