AshDispatch.Channel (AshDispatch v0.6.4)

View Source

Represents a delivery channel for an event.

A channel specifies:

  • Transport - How to deliver (email, in_app, discord, etc.)
  • Audience - Who receives (:user, :admin, custom)
  • Time - When to deliver (immediate, delayed, scheduled)
  • Policy - Delivery policy (:always, :skip_if_read)
  • Variant - Template variant (e.g., :admin for admin-specific templates)

Time

  • :immediate (or {:in, 0}) — deliver now
  • {:in, seconds} — deliver after a relative delay
  • {:at, %DateTime{}} — deliver at an absolute time (a time in the past means now)
  • {:window, map}deprecated, delivers immediately

Only asynchronous transports honour the delay; :in_app writes its notification synchronously.

Examples

%Channel{
  transport: :email,
  audience: :user,
  time: {:in, 300},  # Delay 5 minutes
  policy: :skip_if_read
}

%Channel{
  transport: :email,
  audience: :user,
  time: {:at, ~U[2026-09-01 07:00:00Z]}  # Deliver at an absolute time
}

%Channel{
  transport: :discord,
  audience: :admin,
  time: :immediate,
  opts: %{webhook_url: "https://..."}
}

Summary

Functions

Calculates delay in seconds from time specification.

Helper function to create a channel struct. Used in DSL and event modules for cleaner syntax.

Checks if a channel matches given transport and/or audience filters.

Creates a new channel.

Normalizes time specification to internal format.

Logs the {:window, map} deprecation once per boot.

Types

audience()

@type audience() :: :user | :admin | atom()

policy()

@type policy() :: :always | :skip_if_read | {:gate, function()}

t()

@type t() :: %AshDispatch.Channel{
  audience: audience(),
  content: map(),
  deduplicate_group: atom() | nil,
  exclude_actor: boolean(),
  load: [atom() | {atom(), any()}],
  locale: String.t() | nil,
  locale_from: atom() | nil,
  locales: [String.t()],
  metadata: map(),
  optional: boolean(),
  opts: map(),
  policy: policy(),
  time: time(),
  transport: transport(),
  variant: variant(),
  webhook_url: String.t() | nil
}

time()

@type time() ::
  :immediate | {:in, non_neg_integer()} | {:at, DateTime.t()} | {:window, map()}

transport()

@type transport() :: :email | :in_app | :discord | :sms | :slack | :webhook | atom()

variant()

@type variant() :: atom() | nil

Functions

calculate_delay(arg1)

Calculates delay in seconds from time specification.

For {:at, datetime} the result is relative to now and may be negative when the datetime is in the past; callers that schedule work clamp it at zero (see AshDispatch.Transports.Email).

{:window, map} is deprecated and always returns 0 (immediate) — see warn_window_deprecated/0.

Examples

iex> Channel.calculate_delay(%Channel{transport: :email, audience: :user, time: {:in, 300}})
300

iex> Channel.calculate_delay(%Channel{transport: :email, audience: :user, time: :immediate})
0

channel(transport, audience, opts \\ [])

Helper function to create a channel struct. Used in DSL and event modules for cleaner syntax.

Examples

def channels(_ctx) do
  [
    channel(:in_app, :user),
    channel(:email, :user, time: 5.minutes(), skip_if_read: true)
  ]
end

matches?(channel, filters)

Checks if a channel matches given transport and/or audience filters.

Examples

iex> channel = %Channel{transport: :in_app, audience: :user}
iex> Channel.matches?(channel, transport: :in_app)
true

iex> Channel.matches?(channel, transport: :email)
false

iex> Channel.matches?(channel, transport: :in_app, audience: :user)
true

new(transport, audience, opts \\ [])

Creates a new channel.

Examples

iex> Channel.new(:email, :user)
%Channel{transport: :email, audience: :user, time: {:in, 0}}

iex> Channel.new(:email, :user, time: 5.minutes(), skip_if_read: true)
%Channel{
  transport: :email,
  audience: :user,
  time: {:in, 300},
  policy: :skip_if_read
}

normalize_time(seconds)

Normalizes time specification to internal format.

Examples

iex> Channel.normalize_time(:immediate)
{:in, 0}

iex> Channel.normalize_time(300)
{:in, 300}

iex> Channel.normalize_time({:in, 300})
{:in, 300}

warn_window_deprecated()

@spec warn_window_deprecated() :: :ok

Logs the {:window, map} deprecation once per boot.

Business-hours windows were never implemented — a {:window, …} channel has always delivered immediately, silently. Rather than keep lying, the time spec is deprecated: it still delivers immediately (removing it would be a breaking change), but the first channel that uses it after boot says so in the log.

Returns :ok.