# v0.9.0 (2026-05-28) A feature + observability release. The headline additions are per-receiver Telegram options (silent send and forum-topic routing), an oversized-video fallback that bypasses Telegram's 50 MB `sendVideo` cap by switching to `sendDocument`, partial-failure visibility on the dashboard via a new `dispatch_summary` block on every `EventLog` row, and an admin diagnostic-mode panel for temporary per-module DEBUG logging with auto-revert. End-to-end correlation IDs (`dispatch_id`, `X-Request-Id`) now tie log lines to the database rows they produced. No breaking changes; no migrations required. ## User-facing changes ### Features - **Per-receiver Telegram options.** Each Telegram chat receiver can now be configured to send silently (`disable_notification` — no sound or vibration on the recipient) and to route into a specific forum topic (`message_thread_id`) on supergroups with topics enabled. A new cog-icon button on the receiver row opens an inline editor; active options surface as a bell-off icon and a `#thread-id` chip on the receiver header. The plumbing uses a `ContextVar` bound at the public send entry points, so every internal payload builder (`sendMessage`, `sendPhoto`/`Video`/`Document`, `sendMediaGroup`, cache-hit POST) picks them up without a signature change ([6a8f374](https://git.dolgolyov-family.by/alexei.dolgolyov/notify-bridge/commit/6a8f374)) - **Send oversized videos as documents.** A new per-target toggle, `send_large_videos_as_documents`, falls back to `sendDocument` when a video would exceed Telegram's 50 MB `sendVideo` limit. Useful for archival use cases (Immich library shares with users on free Telegram accounts) where the video would otherwise be silently dropped or noisily 413'd. Pairs with the existing `send_large_photos_as_documents` toggle. Translated copy lives under `targets.sendLargeVideosAsDocuments` ([6a8f374](https://git.dolgolyov-family.by/alexei.dolgolyov/notify-bridge/commit/6a8f374)) - **Diagnostic mode for temporary DEBUG logging.** A new Diagnostics cassette on the Settings page lets an admin flip one module to DEBUG for a bounded window (1 minute to 4 hours) with an automatic revert. Useful for investigating a specific dispatch failure without flooding stderr; the revert reads the current DB-configured `log_levels` at expiry so a setting change made *during* the window is honored. State is in-memory only — a restart wipes overrides, and `setup_logging()` re-applies the DB baseline at boot, so a forgotten override cannot silently survive a deploy ([6a8f374](https://git.dolgolyov-family.by/alexei.dolgolyov/notify-bridge/commit/6a8f374)) - **Partial-delivery visibility in the dashboard.** Every event-, watcher-, scheduled-, deferred-, action-, and command-dispatch path now writes a `dispatch_summary` block onto `EventLog.details`: per-target succeeded/failed counts, Telegram media `delivered_count` / `skipped_count` / `failed_count`, and a truncated list of error strings. Partial outcomes (one target out of three failed, two of ten assets dropped) are no longer indistinguishable from a clean success ([6a8f374](https://git.dolgolyov-family.by/alexei.dolgolyov/notify-bridge/commit/6a8f374)) - **Inbound request correlation IDs.** A new middleware accepts `X-Request-Id` from the inbound request (so an upstream proxy with its own correlation system can propagate its id) and echoes the value back on the response. Values are sanitised to a bounded charset to prevent CR/LF injection into log lines. The id is bound into log context for every request and copied onto any `EventLog` row written during the same request, so the SPA can surface it for bug reports ([6a8f374](https://git.dolgolyov-family.by/alexei.dolgolyov/notify-bridge/commit/6a8f374)) ### Bug Fixes - **sendMediaGroup byte-budget enforcement.** Telegram's `sendMediaGroup` envelope tops out near 50 MB total (multipart bytes including form overhead). Previously the per-item budget admitted items that, when summed, busted Telegram's request cap and 413'd. A new 45 MB total-bytes budget (`TELEGRAM_MAX_GROUP_TOTAL_BYTES`) splits groups before the overhead pushes us over, with safety margin ([6a8f374](https://git.dolgolyov-family.by/alexei.dolgolyov/notify-bridge/commit/6a8f374)) - **Per-item fallback inside `sendMediaGroup`.** A stale `file_id` reference (cache poisoning by Telegram's GC, or a video that's no longer downloadable from the cached URL) used to silently lose the cached asset because the failed chunk had no re-download path. Each cached item now retains its `source_url` + `download_headers` so the per-item fallback can re-fetch and retry as a single send when its `file_id` POST returns transient errors ([6a8f374](https://git.dolgolyov-family.by/alexei.dolgolyov/notify-bridge/commit/6a8f374)) --- ## Development / Internal ### Observability - **Shared `dispatch_id` across log lines and `EventLog` rows.** A `disp:<12 hex>` correlation id is bound at the top of every dispatch entry point (`dispatch_provider_event`, `check_tracker`, `dispatch_scheduled_for_tracker`, `_process_row` in deferred dispatch, `run_action`, command handler, HA status logger) via `ContextVar`. Nested dispatcher calls reuse the bound id instead of generating their own, so a single dispatch's log lines and the `EventLog.details.dispatch_id` field share one searchable id ([6a8f374](https://git.dolgolyov-family.by/alexei.dolgolyov/notify-bridge/commit/6a8f374)) - **`enrich_details_with_correlation()` helper.** New helper in `notify_bridge_core.log_context` merges the bound `dispatch_id` and `request_id` onto `EventLog.details` dicts at write time without overwriting caller-provided keys. Every `EventLog` insertion site has been migrated to use it ([6a8f374](https://git.dolgolyov-family.by/alexei.dolgolyov/notify-bridge/commit/6a8f374)) ### Architecture - **`split_media_by_upload_size` retired.** Per-item upload accounting moved onto the new `_MediaItem` dataclass (`upload_bytes` property) and the splitter logic moved into `_send_media_group`, where the byte budget and per-item fallback live ([6a8f374](https://git.dolgolyov-family.by/alexei.dolgolyov/notify-bridge/commit/6a8f374)) - **API endpoints for diagnostic mode.** New routes under `/api/app-settings/diagnostic-mode` (`GET` list, `POST` activate, `DELETE /{module:path}` revert) with admin-auth requirement and a curated module allowlist that blocks the root logger ([6a8f374](https://git.dolgolyov-family.by/alexei.dolgolyov/notify-bridge/commit/6a8f374)) ### Tests - **New suites:** `test_diagnostic_mode.py`, `test_dispatch_summary.py`, `test_request_correlation.py`, `test_telegram_media_group_partial.py`, `test_telegram_per_send_options.py`. Total: 294 tests passing (up from 283 in v0.8.2) ([6a8f374](https://git.dolgolyov-family.by/alexei.dolgolyov/notify-bridge/commit/6a8f374)) - **Test scaffolding fix:** `_reset_state()` in `test_diagnostic_mode.py` now also clears the `_bg_tasks` set so a long-window schedule from one test doesn't pollute `len(_bg_tasks)` in the next test's assertion. Cross-loop `.cancel()` is intentionally skipped — the prior test's loop is closed and cancelling there raises `RuntimeError` on the next test's setup ([9aada75](https://git.dolgolyov-family.by/alexei.dolgolyov/notify-bridge/commit/9aada75)) ### Documentation - **Six-axis production-readiness review** (`/.claude/reviews/`) covering backend, frontend, security, performance/DB, UI/UX, and bugs+features. Snapshots the v0.8.1 codebase; informed several items shipped in v0.8.2 and this release ([6a8f374](https://git.dolgolyov-family.by/alexei.dolgolyov/notify-bridge/commit/6a8f374)) - **Functional review of Telegram / Immich / Logging subsystems** (`.claude/docs/functional-review-2026-05-28.md`). Captures what's in place, in-flight work, and ranked gaps for each subsystem; pairs with the existing feature backlog ([6a8f374](https://git.dolgolyov-family.by/alexei.dolgolyov/notify-bridge/commit/6a8f374)) ---
All Commits - [6a8f374](https://git.dolgolyov-family.by/alexei.dolgolyov/notify-bridge/commit/6a8f374) — `feat: observability, per-receiver Telegram options, oversized-video fallback` (alexei.dolgolyov) - [9aada75](https://git.dolgolyov-family.by/alexei.dolgolyov/notify-bridge/commit/9aada75) — `fix(tests): clear diagnostic_mode _bg_tasks between cases` (alexei.dolgolyov)