AshDispatch.RecipientResolver behaviour (AshDispatch v0.5.0)

View Source

Behaviour and DSL for declarative recipient resolution.

This module provides a declarative way to define how notification recipients are resolved for each audience type, following Ash Framework patterns.

Usage

Create a resolver module in your application:

defmodule MyApp.RecipientResolver do
  use AshDispatch.RecipientResolver,
    user_resource: MyApp.Accounts.User

  audiences do
    # Context-based - extract from event context
    audience :user, from_context: :user
    audience :assignee, from_context: [:user, :assignee]

    # Query-based - query user resource with filter
    audience :admins, query: [role: :admin, is_active: true]

    # Relationship path - follow relationships on resource
    audience :team, path: [:team_members, :user]

    # Composite - union of other audiences
    audience :stakeholders, combine: [:owner, :team]

    # Custom resolver - for complex business logic
    audience :owner, resolve: :resolve_owner

    # Non-user recipient - extract email/name from resource fields
    audience :lead_contact, from_resource: [email: :contact_email, name: :contact_name]
  end

  @impl true
  def to_recipient(%MyApp.Accounts.User{} = user) do
    %{
      id: user.id,
      email: to_string(user.email),
      display_name: user.full_name || to_string(user.email)
    }
  end

  def resolve_owner(resource, context) do
    # Your custom logic
  end
end

Then configure ash_dispatch to use it:

config :ash_dispatch,
  recipient_resolver: MyApp.RecipientResolver

Audience Resolution Strategies

StrategyDSLDescription
from_contextaudience :user, from_context: :userExtract from context.data
from_context (fallback)audience :assignee, from_context: [:user, :assignee]Try each key until non-nil
from_context + extractaudience :participants, from_context: [:meeting, :participants], extract: :userGet collection, extract field
from_resourceaudience :lead_contact, from_resource: [email: :contact_email, name: :contact_name]Extract email/name from resource fields
queryaudience :admins, query: [role: :admin]Query user_resource with Ash filter
pathaudience :team, path: [:team_members, :user]Follow relationship path on resource
combineaudience :stakeholders, combine: [:owner, :team]Union of other audiences (deduped)
resolveaudience :owner, resolve: :resolve_ownerCustom function
resolve + rawaudience :lead_contact, resolve: :fn, raw: trueCustom function, skip to_recipient

Summary

Callbacks

Resolve recipients for an audience.

Convert a user struct to a recipient map.

Callbacks

resolve(audience, resource, context)

(optional)
@callback resolve(audience :: atom(), resource :: struct() | nil, context :: map()) :: [
  map()
]

Resolve recipients for an audience.

This callback is optional - if not implemented, the default resolution logic will be used based on the audiences DSL configuration.

to_recipient(user)

@callback to_recipient(user :: struct()) :: %{
  :id => term(),
  :email => String.t(),
  optional(:display_name) => String.t(),
  optional(:first_name) => String.t()
}

Convert a user struct to a recipient map.

The returned map must have at minimum:

  • :id - unique identifier for deduplication
  • :email - email address for email transport

Optional fields:

  • :display_name - name shown in templates
  • :first_name - first name for personalization