AshDispatch.ReceiptStatus (AshDispatch v0.5.0)

View Source

Centralized receipt status management for AshDispatch workers.

This module provides consistent receipt status updates across all delivery workers (email, webhook, etc.), ensuring uniform behavior and reducing code duplication.

Why This Exists

Receipt status updates were duplicated across multiple workers:

Each had identical mark_sending/1, mark_sent/2, mark_failed/2 functions. This module consolidates them into a single source of truth.

Status Flow

pending  sending  sent
                   failed  failed_permanent (after max retries)
         scheduled (for async transports)
         skipped (user opted out, missing config, etc.)

Usage

alias AshDispatch.ReceiptStatus

# In a worker
def perform(%Oban.Job{args: args}) do
  with {:ok, receipt} <- get_receipt(args["receipt_id"]),
       {:ok, receipt} <- ReceiptStatus.mark_sending(receipt),
       {:ok, response} <- do_delivery(receipt) do
    ReceiptStatus.mark_sent(receipt, response)
    :ok
  else
    {:error, reason} ->
      ReceiptStatus.mark_failed(receipt, reason)
      {:error, reason}
  end
end

Summary

Functions

Marks a receipt as "failed" (delivery failed, may retry).

Marks a receipt as "failed_permanent" (no more retries).

Marks a receipt as "scheduled" (job enqueued for later delivery).

Marks a receipt as "sending" (delivery in progress).

Marks a receipt as "sent" (delivery successful).

Marks a receipt as "skipped" (intentionally not sent).

Functions

mark_failed(receipt, reason)

@spec mark_failed(
  struct(),
  any()
) :: struct()

Marks a receipt as "failed" (delivery failed, may retry).

Parameters

  • receipt - The delivery receipt struct
  • reason - Error reason (will be converted to string via inspect/1)

Returns

The updated receipt (raises on failure since this is critical).

mark_failed_permanent(receipt, error_message)

@spec mark_failed_permanent(
  struct(),
  String.t()
) :: struct()

Marks a receipt as "failed_permanent" (no more retries).

Used by retry worker when max retries exceeded.

Parameters

  • receipt - The delivery receipt struct
  • error_message - Final error message

Returns

The updated receipt (raises on failure since this is critical).

mark_scheduled(receipt, opts \\ [])

@spec mark_scheduled(struct(), keyword()) :: struct()

Marks a receipt as "scheduled" (job enqueued for later delivery).

Optionally stores the Oban job ID for tracking.

Parameters

  • receipt - The delivery receipt struct
  • opts - Options keyword list
    • :oban_job_id - The Oban job ID (optional)

Returns

The updated receipt (raises on failure since this is critical).

mark_sending(receipt)

@spec mark_sending(struct()) :: {:ok, struct()} | {:error, Ash.Changeset.t()}

Marks a receipt as "sending" (delivery in progress).

This is called at the start of a delivery attempt.

Returns

  • {:ok, updated_receipt} on success
  • {:error, changeset} on failure

mark_sent(receipt, provider_response)

@spec mark_sent(
  struct(),
  map()
) :: struct()

Marks a receipt as "sent" (delivery successful).

Stores provider response metadata for tracking and debugging.

Parameters

  • receipt - The delivery receipt struct
  • provider_response - Map with provider details, e.g., %{id: "msg_123", ...}

Returns

The updated receipt (raises on failure since this is critical).

mark_skipped(receipt, reason)

@spec mark_skipped(
  struct(),
  String.t()
) :: struct()

Marks a receipt as "skipped" (intentionally not sent).

Used when:

  • User opted out of notifications
  • Required configuration is missing
  • Transport is not implemented

Parameters

  • receipt - The delivery receipt struct
  • reason - Reason for skipping (string)

Returns

The updated receipt (raises on failure since this is critical).