Packages
Event-driven notification system for Ash Framework with multiple transport types
Current section
Files
Jump to
Current section
Files
lib/sms_backend.ex
defmodule AshDispatch.SMSBackend do
@moduledoc """
The behaviour an SMS backend implements.
config :ash_dispatch, :sms_backend, MyApp.SMS
`AshDispatch.SMSBackend.Elks` ships with the library and covers 46elks; this
behaviour is for every other provider.
## Where it is called from
From `AshDispatch.Workers.SendSMS`, not from the transport. The transport
enqueues a job and marks the receipt `:scheduled`; the worker marks
`:sending` and calls `deliver/4`. A backend therefore does not have to worry
about holding a database transaction open — but it must not assume it runs
synchronously with the action that triggered the event either.
Requires an Oban queue named `:sms`.
The job carries only the receipt id, so the context the worker passes is
reconstructed from the receipt: `event_id` and `audience` are real, but
`data` and `variables` are empty. A backend that needs either should read
`receipt.content`, which was frozen when the receipt was created and
therefore survives a retry.
## The contract
- Read `receipt.recipient` (the phone number) and the message body from
`receipt.content`. Use `AshDispatch.ContentMap.get_content/2`: the column
is JSONB, so a freshly built struct carries atom keys while one read back
from Postgres carries string keys. The dispatcher writes `:message`.
- Send through the provider's API.
- **Mark the receipt yourself.** The backend is what knows which failures are
permanent:
- delivered: `ReceiptStatus.mark_sent(receipt, %{"id" => provider_id})`
- worth retrying: `ReceiptStatus.mark_failed(receipt, reason)`
- never going to work: `ReceiptStatus.mark_failed_permanent(receipt, reason)`
- Return `{:ok, updated_receipt}` or `{:error, reason}`.
An `{:error, _}` makes the worker mark `:failed` and lets Oban retry. A
malformed phone number or bad credentials belong in `mark_failed_permanent`
— five retries will not fix them, and while they sit as `:failed` they only
delay telling the person who can.
## The recipient field
Without an `:sms` entry in `recipient_fields`, **every** recipient raises
`"No identifier field configured for sms transport"`, and the error does not
say where to look:
config :ash_dispatch,
recipient_fields: [
sms: [identifier: :phone, name: [:display_name, :name]]
]
"""
@callback deliver(
receipt :: struct(),
context :: AshDispatch.Context.t(),
channel :: AshDispatch.Channel.t(),
event_config :: map()
) :: {:ok, struct()} | {:error, term()}
end