ExNtfy (ex_ntfy v0.1.1)

Copy Markdown View Source

An Elixir SDK for ntfy β€” a simple HTTP-based pub-sub notification service.

ExNtfy lets you publish notifications to ntfy topics and subscribe to messages, targeting either the public ntfy.sh instance or a self-hosted server.

Publishing

# The simplest possible publish
{:ok, message} = ExNtfy.publish("mytopic", "Backup completed πŸŽ‰")

# With options (see `ExNtfy.Publish.Options` for the full list)
ExNtfy.publish("alerts", "Disk almost full",
  title: "Server alert",
  priority: :urgent,
  tags: [:warning, :floppy_disk],
  click: "https://grafana.example.com/d/disk"
)

# Action buttons
ExNtfy.publish("deploys", "Deploy finished",
  actions: [
    %ExNtfy.Action{type: :view, label: "Open", url: "https://example.com/deploys/42"}
  ]
)

# Raw body + templating: forward a webhook payload and let the server
# render it (reference Β§1.6)
ExNtfy.publish_raw("builds", github_webhook_json, template: :github)

# Webhook-style GET, e.g. from a curl one-liner or cron job
ExNtfy.trigger("mytopic", message: "cron ran", tags: [:clock])

Attachments

Attach by URL with the :attach option, or upload a binary with publish_file/3 β€” iodata, a stream, or {:file, path} (streamed from disk; filename defaults to the basename):

ExNtfy.publish_file("mytopic", {:file, "/tmp/report.pdf"},
  message: "Monthly report attached",
  title: "Reports"
)

{:ok, message} = ExNtfy.publish_file("mytopic", png_bytes, filename: "graph.png")
message.attachment.url
#=> "https://ntfy.sh/file/oaFAdEY1KC.png"

Lifecycle: update, clear, delete

Messages sharing a sequence ID form a sequence; clients show the latest state. The simplest idiom is to reuse the id of the message you published:

{:ok, message} = ExNtfy.publish("deploys", "Deploy started…")

# Update it in place as things progress (the returned id is the sequence id)
ExNtfy.update("deploys", message.id, "Deploy at 80%…")
ExNtfy.update("deploys", message.id, "Deploy finished βœ…")

# Then dismiss it from clients, or delete it outright
ExNtfy.clear("deploys", message.id)
ExNtfy.delete("deploys", message.id)

Alternatively pick your own sequence ID up front (ExNtfy.publish("deploys", "…", sequence_id: "deploy-42")) and use it for every follow-up.

Polling

Fetch cached messages without holding a connection open (see ExNtfy.Subscribe.Options for the full since/filter surface):

# Everything cached for one or more topics
{:ok, messages} = ExNtfy.poll("mytopic")
{:ok, messages} = ExNtfy.poll(["alerts", "backups"], since: "10m")

# Resume after the last message you processed
ExNtfy.poll("mytopic", since: last_message.id)

# Filters: priority is any-match, tags must all match
ExNtfy.poll("mytopic", priority: [:high, :urgent], tags: [:warning], scheduled: true)

Subscribing (streaming)

Long-lived subscriptions reconnect automatically with backoff, resume from the last seen message (since=<id>), and watch for dead connections via a keepalive watchdog. Three consumption styles:

Message-passing β€” events arrive in the calling process's mailbox:

{:ok, sub} = ExNtfy.subscribe("alerts", since: "10m")

receive do
  {:ntfy, ^sub, %ExNtfy.Message{} = message} -> handle(message)
  {:ntfy_lifecycle, ^sub, :connected} -> :ok
  {:ntfy_lifecycle, ^sub, :disconnected} -> :ok
  {:ntfy_lifecycle, ^sub, {:message_clear, message}} -> dismiss(message)
  {:ntfy_lifecycle, ^sub, {:down, reason}} -> react(reason)
end

ExNtfy.unsubscribe(sub)

The subscription stops when its owner (the caller, or owner:) dies.

Handler behaviour β€” callbacks run in the subscription process; put it in your supervision tree:

children = [
  {ExNtfy.Subscription,
   topics: ["alerts", "backups"],
   handler: {MyApp.NtfyHandler, []},
   auth: {:token, "tk_..."},
   name: MyApp.NtfySub}
]

See ExNtfy.Handler for the callbacks. A crashing handler takes the subscription down with it β€” the supervisor restarts both.

Lazy stream β€” blocks the calling process, halts cleanly:

ExNtfy.stream("alerts")
|> Stream.filter(&(&1.priority >= 4))
|> Enum.take(5)

All subscribe filters (ExNtfy.Subscribe.Options) and client options apply; format: :sse | :raw | :ws selects the other transports (:raw carries bodies only β€” no metadata, no resume; :ws subscribes over WebSocket and requires the optional :mint_web_socket dependency).

Client options mix into the same keyword list β€” for a self-hosted server with authentication:

ExNtfy.publish("private", "hi",
  base_url: "https://ntfy.example.com",
  auth: {:token, "tk_..."}
)

publish/3 returns {:ok, %ExNtfy.Message{}} (the created message as the server recorded it) or {:error, %ExNtfy.Error{}}; publish!/3 returns the message or raises.

Summary

Functions

clear(topic, sequence_id, opts \\ [])

Clears (dismisses) a delivered notification. See ExNtfy.Publisher.clear/3.

delete(topic, sequence_id, opts \\ [])

Deletes a notification from clients. See ExNtfy.Publisher.delete/3.

poll(topics, opts \\ [])

Fetches cached messages one-shot. See ExNtfy.Poller.poll/2.

poll!(topics, opts \\ [])

Like poll/2, but raises on failure. See ExNtfy.Poller.poll!/2.

publish(topic, message, opts \\ [])

Publishes a message as JSON. See ExNtfy.Publisher.publish/3.

publish!(topic, message, opts \\ [])

Like publish/3, but raises ExNtfy.Error on failure. See ExNtfy.Publisher.publish!/3.

publish_file(topic, body, opts \\ [])

Uploads a binary attachment. See ExNtfy.Publisher.publish_file/3.

publish_file!(topic, body, opts \\ [])

Like publish_file/3, but raises on failure. See ExNtfy.Publisher.publish_file!/3.

publish_raw(topic, body, opts \\ [])

Publishes a raw body with options as headers. See ExNtfy.Publisher.publish_raw/3.

stream(topics, opts \\ [])

A lazy stream of messages. See ExNtfy.Subscription.stream/2.

subscribe(topics, opts \\ [])

Starts a streaming subscription. See ExNtfy.Subscription.subscribe/2.

trigger(topic, opts \\ [])

Webhook-style GET publish. See ExNtfy.Publisher.trigger/2.

unsubscribe(subscription)

Stops a subscription cleanly. See ExNtfy.Subscription.unsubscribe/1.

update(topic, sequence_id, message, opts \\ [])

Updates a notification via its sequence ID. See ExNtfy.Publisher.update/4.