Skip to content

feat: add native ntfy transport with full test suite#81

Open
lollox80 wants to merge 2 commits intorhizomatics:mainfrom
lollox80:feat/ntfy-transport
Open

feat: add native ntfy transport with full test suite#81
lollox80 wants to merge 2 commits intorhizomatics:mainfrom
lollox80:feat/ntfy-transport

Conversation

@lollox80
Copy link
Copy Markdown
Contributor

PR: feat: add native ntfy transport

Branch: feat/ntfy-transport
Target: rhizomatics/supernotify:main
File aggiunto: custom_components/supernotify/transports/ntfy.py


Motivation

ntfy became an official Home Assistant integration in version 2025.5. It provides
a privacy-first, self-hostable push notification service with a rich action API:
priority levels, action buttons, scheduled delivery, image attachments, and
message update/cancellation via sequence_id.

The existing generic transport can call ntfy.publish, but it cannot:

  • map SuperNotify's 5-level priority to ntfy's integer scale (1–5)
  • validate action button payloads before sending
  • attach camera snapshots via HA's camera.snapshot service
  • apply boolify() for YAML boolean strings

A native transport handles all of this cleanly and establishes the pattern
for similar services (Pushover, Gotify).


Changes

  • custom_components/supernotify/transports/ntfy.py (new file, ~210 lines)

    • NtfyTransport class with name = TRANSPORT_NTFY
    • supported_features: MESSAGE | TITLE | IMAGES | ACTIONS
    • default_config: action ntfy.publish, TargetRequired.NEVER
    • Priority mapping: critical→5, high→4, medium→3, low→2, minimum→1
    • All ntfy-specific data keys extracted via raw_data.pop() before service call
    • _parse_delay(): converts "10m" / "1h30m""HH:MM" format required by HA
    • _validate_actions(): drops malformed action buttons with a warning (max 3)
    • Camera snapshot: calls camera.snapshot via hass_api, attaches via public URL
    • Residual raw_data keys passed through to allow forward-compatible extensions
  • custom_components/supernotify/const.py (existing file)

    • Add TRANSPORT_NTFY = "ntfy" constant
    • Add TRANSPORT_NTFY to TRANSPORT_VALUES list
  • custom_components/supernotify/notify.py (existing file)

    • Add NtfyTransport import
    • Register NtfyTransport in transport registry

Data keys

All keys are optional unless noted. Keys with prefix ntfy_ are extracted
by the transport and never forwarded to ntfy.publish.

Key Type Default Description
ntfy_device_id str Required. device_id of the ntfy topic configured in HA
ntfy_priority int 1–5 from SN mapping Override priority (5=urgent, 1=min)
ntfy_tags list[str] [] ntfy emoji shortcodes e.g. ["warning", "house"]
ntfy_click str (URL) URL opened when notification is tapped
ntfy_attach_image bool false Attach camera snapshot (requires media.camera_entity_id)
ntfy_filename str snapshot.jpg Filename for the attachment
ntfy_icon str (URL) JPEG/PNG icon URL
ntfy_markdown bool false Enable Markdown rendering in message body
ntfy_delay str Scheduled delivery: "10m", "1h30m", "HH:MM"
ntfy_sequence_id str Message ID for subsequent update/cancellation
ntfy_email str Email forwarding address
ntfy_actions list[dict] [] Action buttons, max 3 (types: view/http/broadcast/copy)

Testing

Tested on Home Assistant 2026.3.4 with the ntfy official integration connected
to a self-hosted ntfy instance (ntfy v2.x).

Functional tests performed:

  • Base delivery: message and title appear correctly in ntfy app
  • Priority mapping: critical triggers urgent priority (bypasses DND)
  • Priority mapping: minimum delivers silently with no vibration
  • ntfy_tags: emoji tags visible in notification
  • ntfy_click: tap opens correct URL
  • ntfy_attach_image: true + camera.ezviz_ingresso → snapshot attached
  • ntfy_actions with 3 buttons: view, http (webhook), http (snooze)
  • ntfy_delay: "30m" — notification delivered after 30 minutes
  • ntfy_sequence_id — second call with same ID updates the notification
  • ntfy_device_id missing → return False with warning in log
  • ntfy_actions with malformed entry → entry skipped, valid ones delivered
  • YAML boolean strings "true" / "false" for ntfy_attach_image, ntfy_markdownboolify() handles correctly
  • ntfy_priority: 99 out of range → fallback to automatic mapping, warning logged

Example configuration:

# delivery.yaml — minimal
ntfy_home:
  transport: ntfy
  selection: default
  data:
    ntfy_device_id: "abc123def456"
    ntfy_click: "http://homeassistant.local:8123"

# delivery.yaml — security channel with camera snapshot
ntfy_security:
  transport: ntfy
  selection: default
  data:
    ntfy_device_id: "abc123def456"
    ntfy_attach_image: true
    ntfy_click: "http://homeassistant.local:8123/lovelace/sicurezza"
    ntfy_tags:
      - house
      - rotating_light

# delivery.yaml — alarm channel with action buttons
ntfy_alarms:
  transport: ntfy
  selection: scenario
  data:
    ntfy_device_id: "abc123def456"
    ntfy_tags: [rotating_light, sos]
    ntfy_attach_image: true
    ntfy_actions:
      - action: view
        label: "Open dashboard"
        url: "http://homeassistant.local:8123/lovelace/allarmi"
        clear: true
      - action: http
        label: "✅ Acknowledge"
        url: "http://homeassistant.local:8123/api/webhook/ack_alarm"
        method: POST
        clear: true

Example call:

action: notify.supernotify
data:
  message: "Motion detected at entrance"
  title: "📷 Front Camera"
  data:
    media:
      camera_entity_id: camera.ezviz_ingresso

Expected behavior:

  • ntfy app receives push with title, message, attached snapshot, and action buttons
  • Tapping "Open dashboard" navigates to the HA security Lovelace view
  • Tapping "Acknowledge" sends POST to the HA webhook
  • Priority critical bypasses Do Not Disturb on mobile

How to get the device_id

Developer Tools → Template →
{{ device_id('notify.nome_topic') }}

Or: Settings → Devices & Services → ntfy → click the topic device.


Notes

  • TargetRequired.NEVER: the topic target is conveyed via ntfy_device_id in data,
    not via HA's standard target field. This avoids the transport being suppressed
    when no person target is present in the notification call.
  • Camera snapshot uses camera.snapshot to /config/www/ (accessible at /local/),
    then attaches via hass_api.abs_url(). This works for both cloud and local ntfy.
  • _parse_delay() accepts both "10m" / "1h30m" shorthand and "HH:MM" / "HH:MM:SS".
    Unrecognized formats are passed through with a warning.

lollox80 and others added 2 commits April 11, 2026 12:24
New NtfyTransport (206 lines) for push notifications via the official
ntfy HA integration (2025.5+). Uses ntfy.publish action with device_id
target.

Features: priority mapping (5 levels, integer 1-5), ntfy_priority
override with validation and clamping, action buttons (max 3, validated),
camera snapshot with graceful fallback, delay parsing (10m/1h30m/HH:MM),
sequence_id for update/cancel, email forwarding, click URL, icon,
Markdown rendering, boolify() for YAML string safety.

Test suite: 30+ tests covering _parse_delay(), deliver() happy path,
all priority mappings, ntfy_priority override, all optional fields,
action truncation, ntfy_* key isolation, camera snapshot flow, boolify
correctness for YAML strings, error handling.

Co-Authored-By: Claude <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant