Fast and reliable
background jobs in Go.

Atomic, transaction-safe, robust job queueing for Go applications. Backed by PostgreSQL and built to scale.

Transactional enqueueing simplifies everything.

River jobs never run before your transaction completes, and are never lost. If your API's transaction succeeds, your job will be enqueued—period.

Compared to working with external job queues like Redis, this means a simpler development model that lets you build more reliable applications more quickly. For Go applications built on Postgres, this also means no added services to manage.

func createUser( /* ... */) (*User, error) {
tx, err := dbPool.Begin(ctx)
if err != nil {
return nil, err
defer tx.Rollback(ctx)
// Create the new user record:
user, err := insertUser(ctx, tx, email)
if err != nil {
return nil, err
// Enqueue a job to send the user an onboarding email:
_, err = riverClient.InsertTx(ctx, tx,
SendOnboardingEmailArgs{UserID: user.ID},
if err != nil {
return nil, err
// Atomically commit both the user record and the job:
if err := tx.Commit(ctx); err != nil {
return nil, err
return user, nil

Batteries included

The features you need.

River is fully-featured so you can focus on building your app instead of chasing down bugs and distributed systems edge cases.

Web interface
Easier development and operations.
Error handling and reporting
Connects to your bug tracking service to catch issues.
Automatic retries
Erroring jobs are automatically retried with a customizable exponential backoff.
Multiple isolated queues
Each named queue is processed independently and won't block other queues.
Unique jobs
Ensure that a job is enqueued only once in a given time period.
Jobs are worked according to assigned priority. Important jobs are processed first.
Recurring Jobs
Set up a schedule for recurring jobs, including advanced cron scheduling and time zone handling.
Configurable job timeouts
Handle jobs that are lightning fast or very slow. Ensure they don't run forever.
Graceful shutdown
Waits for active jobs to complete before exiting.
No added service dependencies
River runs in your app's primary Postgres database, which means there are no new operational dependencies.

Thoughtfully designed for modern Go.

River is designed by experienced Go developers to fit the conventions of the Go ecosystem. It leverages generics to provide strongly typed, structured arguments to workers.

// Each worker has a corresponding args type:
type SendEmailArgs struct {
UserID int64
func (args SendEmailArgs) Kind() string { return "SendEmail" }
type SendEmailWorker struct {
// Work receives a typed *Job[T] to process:
func (w *SendEmailWorker) Work(ctx context.Context, j *Job[SendEmailArgs]) error {
user, err := getUser(ctx, j.Args.UserID)
if err != nil {
return err
return sendOnboardingEmail(

Built for humans

Easy to operate and develop. ✨

River ships with an optional self-hosted web interface called River UI. Whether you're trying to diagnose an ongoing incident, perusing for details on erroring jobs, or merely trying to see whether your jobs are working correctly in development, a visual interface makes life easier.

Gain insights.
Tune your system by keeping an eye on queue depth and throughput.
Control jobs and queues.
Cancel, retry, or delete jobs. Pause entire queues to halt job processing.
Find bugs.
Discover slow, stuck, or erroring jobs. Uncover hidden production issues.
Faster development.
See whether your jobs are working as you write them. Quickly iterate your way to a working system.
Open source, self-hosted.
Keep your data on your systems. Lightweight and stateless, with no added service dependencies.
Dark mode.
Because nobody wants to use a bright white website when responding to an outage at 2am.

Stay updated.

We're hard at work on more advanced features including database/sql support, workflows, rate limiting, and concurrency controls. Sign up to get updates on our progress.