ExNtfy.Publisher (ex_ntfy v0.1.1)

Copy Markdown View Source

Publishing to ntfy topics — usually called through the ExNtfy facade.

Three publish shapes, matching the ntfy API:

  • publish/3 — JSON publish (POST /), the default path.
  • publish_raw/3 — raw-body publish (POST /<topic>): the body passes through byte-identical and options travel as headers. This is the path for templating (:template) and webhook payloads.
  • trigger/2 — webhook-style GET /<topic>/trigger with options only in the query string.

All accept publish options (see ExNtfy.Publish.Options) mixed with client options (see ExNtfy.Config) in one keyword list.

Telemetry

Every request is wrapped in :telemetry.span/3, emitting [:ex_ntfy, :publish, :start], [:ex_ntfy, :publish, :stop], and [:ex_ntfy, :publish, :exception] with metadata %{topic: topic, base_url: base_url} — never credentials or message contents.

Summary

Functions

Clears (marks read and dismisses) a delivered notification: PUT /<topic>/<sequence_id>/clear. The server also accepts a /read alias and a GET form for header-limited clients; this SDK exposes only the canonical endpoint.

Deletes a notification from clients: DELETE /<topic>/<sequence_id>. History remains server-side (storage is append-only), and a deleted sequence revives if republished.

Publishes a message as JSON (POST /).

Same as publish/3, but returns the message directly and raises ExNtfy.Error on failure.

Uploads a binary attachment: PUT /<topic> with the file as the request body (reference §1.4).

Same as publish_file/3, but returns the message directly and raises ExNtfy.Error on failure.

Publishes a raw body to POST /<topic> — the body is sent byte-identical and every publish option becomes its canonical X- header (non-ASCII values RFC 2047-encoded).

Webhook-style publish: GET /<topic>/trigger with every option in the query string and no body. An empty :message defaults to "triggered" server-side.

Updates a delivered notification by publishing again with the same sequence ID (reference §1.7) — sugar over publish/3 with :sequence_id set.

Functions

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

@spec clear(String.t(), String.t(), keyword()) ::
  {:ok, ExNtfy.Message.t()} | {:error, ExNtfy.Error.t()}

Clears (marks read and dismisses) a delivered notification: PUT /<topic>/<sequence_id>/clear. The server also accepts a /read alias and a GET form for header-limited clients; this SDK exposes only the canonical endpoint.

Returns the emitted event: :message_clear message.

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

@spec delete(String.t(), String.t(), keyword()) ::
  {:ok, ExNtfy.Message.t()} | {:error, ExNtfy.Error.t()}

Deletes a notification from clients: DELETE /<topic>/<sequence_id>. History remains server-side (storage is append-only), and a deleted sequence revives if republished.

Returns the emitted event: :message_delete message.

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

@spec publish(String.t(), String.t() | nil, keyword()) ::
  {:ok, ExNtfy.Message.t()} | {:error, ExNtfy.Error.t()}

Publishes a message as JSON (POST /).

message may be nil for option-only publishes. Header-only options (:cache, :firebase, :unified_push, :template, :poll_id) are sent as headers on the same request.

Returns the created message parsed from the server response.

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

@spec publish!(String.t(), String.t() | nil, keyword()) :: ExNtfy.Message.t()

Same as publish/3, but returns the message directly and raises ExNtfy.Error on failure.

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

@spec publish_file(
  String.t(),
  iodata() | Enumerable.t() | {:file, Path.t()},
  keyword()
) ::
  {:ok, ExNtfy.Message.t()} | {:error, ExNtfy.Error.t()}

Uploads a binary attachment: PUT /<topic> with the file as the request body (reference §1.4).

body may be iodata, an Enumerable of chunks, or {:file, path} — the latter streams from disk without reading the whole file into memory, and defaults :filename to Path.basename(path). All publish options ride along as headers (the body is the file, so this is the header path); use :message for the notification text shown alongside the attachment.

Server limits apply (~2 MB per file on ntfy.sh); an oversized upload comes back as {:error, %ExNtfy.Error{http: 413}}.

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

@spec publish_file!(
  String.t(),
  iodata() | Enumerable.t() | {:file, Path.t()},
  keyword()
) ::
  ExNtfy.Message.t()

Same as publish_file/3, but returns the message directly and raises ExNtfy.Error on failure.

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

@spec publish_raw(String.t(), binary(), keyword()) ::
  {:ok, ExNtfy.Message.t()} | {:error, ExNtfy.Error.t()}

Publishes a raw body to POST /<topic> — the body is sent byte-identical and every publish option becomes its canonical X- header (non-ASCII values RFC 2047-encoded).

Use this for templating (reference §1.6), where the body is an arbitrary JSON payload rather than the ntfy publish schema, or for bodies that must not be re-encoded.

trigger(topic, opts \\ [])

@spec trigger(
  String.t(),
  keyword()
) :: {:ok, ExNtfy.Message.t()} | {:error, ExNtfy.Error.t()}

Webhook-style publish: GET /<topic>/trigger with every option in the query string and no body. An empty :message defaults to "triggered" server-side.

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

@spec update(String.t(), String.t(), String.t() | nil, keyword()) ::
  {:ok, ExNtfy.Message.t()} | {:error, ExNtfy.Error.t()}

Updates a delivered notification by publishing again with the same sequence ID (reference §1.7) — sugar over publish/3 with :sequence_id set.

Two idioms: reuse the id of a previously returned message as the sequence ID for follow-ups, or pick your own sequence ID up front on the first publish (sequence_id: option) and keep using it.