You ship signup with a welcome email and a Slack notification to your team channel. Each request now spends 1.2 seconds talking to SendGrid and Slack before returning the user to the dashboard. The signup form feels slow, the Slack ping sometimes fails and takes the welcome email down with it, and there is no easy way to retry just the email.
This feature installs Oban with database-backed persistence, four pre-configured queues, automatic pruning, and cron support. After installation, "send the welcome email" is a Worker job that runs out-of-band with retries; the signup request returns in milliseconds.
Move work out of the request cycle, reliably
Background work for a SaaS is not optional. Welcome emails, invoice generation, webhook delivery, daily aggregation, post-signup onboarding flows — all of them belong somewhere other than the request that triggered them. Oban is the de-facto Elixir answer: jobs are rows in your existing Postgres database, retries are built in, and concurrency is controllable per queue.
What This Feature Adds
{:oban, "~> 2.20.0"}and{:oban_web, "~> 2.11.0"}added tomix.exs- Oban configured against your existing
MyApp.Repo - A generated migration that creates the
oban_jobstable (and supporting Oban tables) - Four pre-configured queues with sensible concurrency:
default: 10,mailers: 20,high: 50,low: 5 - The Oban pruner plugin with a 24-hour retention so completed jobs do not accumulate forever
- The Oban cron plugin, ready to receive scheduled jobs
- Oban integrated into your application's supervision tree
- Test environment configured for
Oban.Testingin:manualmode, so tests are deterministic
How It Fits Into Your Phoenix Application
A worker is a small module with a perform/1 callback. The rest of your code enqueues jobs through Oban.insert/1:
defmodule MyApp.EmailWorker do
use Oban.Worker, queue: :mailers, max_attempts: 3
@impl Oban.Worker
def perform(%Oban.Job{args: %{"user_id" => user_id}}) do
user = MyApp.Users.get_user!(user_id)
MyApp.UserNotifier.deliver_welcome(user)
:ok
end
end
%{user_id: user.id}
|> MyApp.EmailWorker.new()
|> Oban.insert()
Jobs land in Postgres, run in the queue you specify, and retry on failure up to max_attempts. The mailers queue having higher concurrency than default is deliberate — email work is typically I/O-bound and tolerates more parallelism.
Installation Notes
- No required feature dependencies, but several other features (
error_tracker,notifications,waitlist,html_emails) compose with Oban and are easier to operate when Oban is present. - Run
mix ecto.migrateafter install to create the Oban tables. - Cron is enabled with an empty schedule. Add entries to the cron plugin config when you have scheduled work.
- Test environment is in
:manualmode. UseOban.drain_queue/1or assertions fromOban.Testingto drive jobs in tests deterministically. oban_webis included; mount its dashboard if you want a live job view (theadminfeature mounts it under/adminautomatically).
Build This With Live SaaS Kit
Install saas_kit, then mix saaskit.feature.install oban to move slow work out of the request cycle and stop sending invoice generation through a controller.