# Ntfy Notification Provider — Implementation Specification ## 1. Introduction ### Overview Add **Ntfy** () as a notification provider in Charon, following the same wrapper pattern used by Gotify, Telegram, Slack, and Pushover. Ntfy is an HTTP-based pub/sub notification service that supports self-hosted and cloud-hosted instances. Users publish messages by POSTing JSON to a topic URL, optionally with an auth token. ### Objectives 1. Users can create/edit/delete an Ntfy notification provider via the Management UI. 2. Ntfy dispatches support all three template modes (minimal, detailed, custom). 3. Ntfy respects the global notification engine kill-switch and its own per-provider feature flag. 4. Security: auth tokens are stored securely (never exposed in API responses or logs). 5. Full E2E and unit test coverage matching the existing provider test suite. --- ## 2. Research Findings ### Existing Architecture Charon's notification engine does **not** use a Go interface pattern. Instead, it routes on string type values (`"discord"`, `"gotify"`, `"webhook"`, etc.) across ~15 switch/case + hardcoded lists in both backend and frontend. **Key code paths per provider type:** | Layer | Location | Mechanism | |-------|----------|-----------| | Model | `backend/internal/models/notification_provider.go` | Generic — no per-type changes needed | | Service — type allowlist | `notification_service.go:139` `isSupportedNotificationProviderType()` | `switch` on type string | | Service — flag routing | `notification_service.go:148` `isDispatchEnabled()` | `switch` → feature flag lookup | | Service — dispatch | `notification_service.go:381` `sendJSONPayload()` | Type-specific validation + URL / header construction | | Feature flags | `notifications/feature_flags.go` | Const strings for settings DB keys | | Router | `notifications/router.go:10` `ShouldUseNotify()` | `switch` on type → flag map lookup | | Handler — create validation | `notification_provider_handler.go:185` | Hardcoded `!=` chain | | Handler — update validation | `notification_provider_handler.go:245` | Hardcoded `!=` chain | | Handler — URL validation | `notification_provider_handler.go:372` | Slack special-case (optional URL) | | Frontend — type array | `api/notifications.ts:3` | `SUPPORTED_NOTIFICATION_PROVIDER_TYPES` const | | Frontend — sanitize | `api/notifications.ts` `sanitizeProviderForWriteAction()` | Token mapping per type | | Frontend — form | `pages/Notifications.tsx` | `