# ntfy Transport Adaptor

Source: https://supernotify.rhizomatics.org.uk/latest/transports/ntfy/

## Discovery

**Delivery (explicit selection).** If the `ntfy` integration is already configured and no `ntfy` delivery is defined, an `ntfy` delivery is generated automatically — but since `ntfy_device_id` has no automatic mapping to a recipient or entity, it only fires when selected explicitly (`data: {data: {delivery: [ntfy]}}` or a scenario), not by default. You'll still need to supply `ntfy_device_id` yourself.

## 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

## 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:**

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

**Example configuration:**

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

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

# example delivery config — alarm channel with action buttons
ntfy_alarms:
  transport: ntfy
  inclusion: 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:**

```yaml
action: supernotify.notify
data:
  message: "Motion detected at entrance"
  title: "📷 Front Camera"
  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

```text
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.
