Oban worker that performs a single Chimeway delivery by delivery_id.
Job args contain only delivery_id (UUID string). Full payload is never
stored in Oban job args — the delivery row is the source of truth.
Transactional enqueue
Insert this worker's job inside the same Ecto.Multi as delivery row creation:
alias Chimeway.Dispatch.ObanWorker
Ecto.Multi.new()
|> Ecto.Multi.insert(:delivery, delivery_changeset)
|> Oban.insert(:job, ObanWorker.new(%{delivery_id: delivery.id}))
|> Chimeway.Repo.transaction()Rolling back the Ecto.Multi also rolls back the job — no orphaned jobs.
Idempotency and terminal-state short-circuit
The worker checks delivery terminal states (via Chimeway.Deliveries.terminal_states/0)
on every execution. A delivery already in :succeeded, :suppressed, or :cancelled
returns :ok immediately with no adapter call and no new attempt row.
Phase 14 retry contract (REL-02 / REL-03)
OSS Oban 2.21.1 has no exhausted callback. This worker uses an in-band
attempt == max_attempts guard inside perform/1 to know when it has reached
the final retry — that is the moment the durable :cancelled retries_exhausted
state is written via Deliveries.exhaust_delivery/1.
Return-value contract:
- Successful delivery ->
:ok. - Permanent / bounced failure ->
:ok(record_attempt already converged the delivery to:cancelledwith the appropriate suppression_reason; Oban does not retry). - Transient failure with retries remaining (
attempt < max_attempts) ->{:error, reason}; Oban schedules a retry under its default exponential- with-jitterOban.Worker.backoff/1. - Transient failure on the final attempt (
attempt == max_attempts) ->Deliveries.exhaust_delivery/1writes the:cancelled retries_exhaustedterminal state, then this function returns:okso the Oban job is marked:completedinstead of:discarded(RESEARCH Pitfall 1: keeps operator telemetry dashboards clean — the durable explanation lives on the delivery row, not on the Oban job).