Files

9.5 KiB

Notify Bridge

A generic bridge between service providers and notification targets.

Notify Bridge monitors services (Immich, Gitea, Planka, NUT, Google Photos, generic webhooks, and internal scheduler) for changes and dispatches notifications to configurable targets (Telegram, Discord, Slack, Matrix, ntfy, email, generic webhooks) using customizable templates.

Architecture

  • Service Providers — Connectors to external services (Immich, Gitea, Planka, NUT, Google Photos, generic Webhook, internal Scheduler)
  • Trackers — Monitor specific collections within a provider for changes
  • Tracking Configs — Define what events to watch for and scheduling rules
  • Notification Targets — Where to send notifications (Telegram, Discord, Slack, Matrix, ntfy, email, webhook URLs)
  • Template Configs — Jinja2 templates that format notifications per provider type

Project Structure

packages/
  core/       — Shared library: providers, models, notifications, templates
  server/     — FastAPI REST server with SQLite database
frontend/     — SvelteKit dashboard (Svelte 5, Tailwind CSS v4)

Quick Docker Deploy

docker run -d \
  --name notify-bridge \
  --restart unless-stopped \
  -p 8420:8420 \
  -v notify-bridge-data:/data \
  -e NOTIFY_BRIDGE_SECRET_KEY=$(openssl rand -hex 32) \
  -e NOTIFY_BRIDGE_CORS_ALLOWED_ORIGINS=http://localhost:8420 \
  git.dolgolyov-family.by/alexei.dolgolyov/notify-bridge:latest

Then open http://localhost:8420 in your browser.

Environment Variables

Core settings (all prefixed with NOTIFY_BRIDGE_):

Variable Required Default Description
SECRET_KEY Yes Secret for JWT signing (min 32 chars). Default placeholders and known dev-only strings are rejected on startup.
CORS_ALLOWED_ORIGINS Recommended http://localhost:5175 Comma-separated browser origins. Wildcard * is rejected because credentials are enabled. Set this to the URL you load the UI from.
DATA_DIR No /data (in Docker) Directory for SQLite DB, backups, and caches. Mount a volume here.
DATABASE_URL No sqlite+aiosqlite:///<DATA_DIR>/notify_bridge.db Override DB connection string.
HOST No 0.0.0.0 Bind address.
PORT No 8420 Server listen port.
DEBUG No false Enable debug logging.

Reverse proxy / network:

Variable Default Description
FORWARDED_ALLOW_IPS 127.0.0.1 Trusted proxy IPs whose X-Forwarded-For / X-Forwarded-Proto headers are honored. Set to your reverse proxy IP (e.g. 172.17.0.1 for the default Docker bridge). Use * only when the container is not directly internet-reachable.
EXTERNAL_URL Public base URL (e.g. https://notify.example.com). Used to build webhook URLs shown in the UI. Also settable from the Settings page.
ALLOW_PRIVATE_URLS unset Set to 1 to allow requests to RFC1918 / loopback / link-local hosts (homelab scenario: Immich/Gitea on the same LAN). Do not enable on a publicly exposed instance.

Auth & tokens:

Variable Default Description
ACCESS_TOKEN_EXPIRE_MINUTES 15 Lifetime of access JWTs.
REFRESH_TOKEN_EXPIRE_DAYS 30 Lifetime of refresh tokens.
JWT_ISSUER notify-bridge iss claim.
JWT_AUDIENCE notify-bridge-api aud claim.

Logging (all are also live-editable in the Settings page, except log_format):

Variable Default Description
LOG_LEVEL INFO Root level: DEBUG / INFO / WARNING / ERROR.
LOG_FORMAT text text or json. Switching requires a restart.
LOG_LEVELS Per-module overrides, e.g. notify_bridge_core.notifications.telegram.client=DEBUG,sqlalchemy.engine=INFO.

Retention & maintenance:

Variable Default Description
EVENT_LOG_RETENTION_DAYS 30 Days of event_log history to keep. 0 disables the retention job.
PRE_MIGRATE_SNAPSHOT_KEEP 5 Number of pre-migration DB snapshots to keep in <DATA_DIR>/backups/. 0 disables snapshotting.
GRACEFUL_SHUTDOWN_SECONDS 60 Time to wait for in-flight requests / scheduler jobs on SIGTERM before force-killing.

Integrations & misc:

Variable Default Description
TELEGRAM_WEBHOOK_SECRET Shared secret for Telegram bot webhooks. Also settable from the Settings page.
TIMEZONE UTC IANA timezone (e.g. Europe/Warsaw) used by the scheduler. Also settable from the Settings page.
STATIC_DIR /app/static (in Docker) Frontend static files directory. The Docker image sets this; don't override unless you're running outside the image.
SUPERVISED auto-detect Set to 1 to tell the backup endpoint that an external supervisor will restart the process.

Docker Compose

services:
  notify-bridge:
    image: git.dolgolyov-family.by/alexei.dolgolyov/notify-bridge:latest
    container_name: notify-bridge
    restart: unless-stopped
    ports:
      - "8420:8420"
    volumes:
      - notify-bridge-data:/data
    environment:
      # REQUIRED — any 32+ byte random string. `openssl rand -hex 32` is one way.
      - NOTIFY_BRIDGE_SECRET_KEY=${NOTIFY_BRIDGE_SECRET_KEY:?Set NOTIFY_BRIDGE_SECRET_KEY (min 32 chars)}
      # Comma-separated list of allowed browser origins. Wildcard `*` is
      # rejected on startup because credentials are enabled.
      - NOTIFY_BRIDGE_CORS_ALLOWED_ORIGINS=${NOTIFY_BRIDGE_CORS_ALLOWED_ORIGINS:-http://localhost:8420}
      # Trusted proxy IPs whose X-Forwarded-For / X-Forwarded-Proto we honor.
      # Set this to your reverse proxy's IP (e.g. 172.17.0.1 for the default
      # docker bridge, or `*` only if the container is NOT reachable from the
      # public internet).
      - NOTIFY_BRIDGE_FORWARDED_ALLOW_IPS=${NOTIFY_BRIDGE_FORWARDED_ALLOW_IPS:-127.0.0.1}
      # Opt-in SSRF bypass for private/loopback/link-local hosts (homelab
      # scenario — tracking an Immich/Gitea instance on the same LAN). DO NOT
      # enable on a publicly exposed instance.
      # - NOTIFY_BRIDGE_ALLOW_PRIVATE_URLS=1
    healthcheck:
      # Use /api/ready (not /api/health) so the container is only reported
      # healthy after migrations and the scheduler finish booting.
      test: ["CMD", "python", "-c", "import urllib.request; urllib.request.urlopen('http://localhost:8420/api/ready', timeout=3)"]
      interval: 30s
      timeout: 5s
      retries: 3
      start_period: 30s
    read_only: true
    tmpfs:
      - /tmp
    security_opt:
      - no-new-privileges:true
    cap_drop:
      - ALL
    mem_limit: 512m
    cpus: 1.0
    pids_limit: 256

volumes:
  notify-bridge-data:

A ready-to-use docker-compose.yml lives at the repo root.

Health & Readiness

  • GET /api/health — process is up. Use for liveness probes.
  • GET /api/ready — migrations + scheduler have booted. Use for readiness probes and Docker HEALTHCHECK (as the compose example above does).

Quick Start (Development)

# Backend
cd packages/server
pip install -e .
NOTIFY_BRIDGE_DATA_DIR=./test-data NOTIFY_BRIDGE_SECRET_KEY=your-secret-key-min-32chars \
  python -m uvicorn notify_bridge_server.main:app --host 0.0.0.0 --port 8420

# Frontend
cd frontend
npm install
npm run dev

Supported Providers

  • Immich — Photo/video server with album change detection (polling)
  • Gitea — Git server with push / issue / PR / release events (webhook)
  • Planka — Kanban board with card / list / board events (webhook)
  • NUT — Network UPS Tools for battery / power events (polling)
  • Google Photos — Album change detection (polling)
  • Generic Webhook — Catch arbitrary JSON payloads and route them via templates (webhook)
  • Scheduler — Internal provider for time-based scheduled messages

Supported Notification Targets

  • Telegram — Bot API with rich formatting, media groups, and inline commands
  • Discord — Webhook-based delivery with embeds
  • Slack — Incoming webhooks with Block Kit formatting
  • Matrix — Homeserver delivery with HTML formatting
  • ntfy — Self-hostable push notifications
  • Email — SMTP with HTML / plain-text templates
  • Generic Webhook — POST custom JSON payloads to any URL

Bot Commands

Telegram bots can serve interactive commands per provider. All commands use Jinja2 templates that you can customize from the Command Templates page.

Provider Commands
Immich /status /albums /events /summary /latest /memory /random /search /find /person /place /favorites /people /help
Gitea /status /repos /issues /prs /commits /help
Planka /status /boards /cards /lists /help
NUT /status /devices /battery /help
Google Photos /status /albums /latest /search /random /help
Generic Webhook /status /help

Every provider also responds to /start, and rate-limit / empty-result fallback messages are templated as well.

Smart Actions

Beyond notifications, providers can run actions against the source service. Currently implemented:

  • Immich — Auto-Organize — Automatically sort newly-detected assets into albums based on configurable rules. Each rule combines criteria (people in the photo, search query, favorites, date range) with a target album, and can create the album if it doesn't exist. Supports dry-run mode for previewing what would move before committing.