Fast and reliable
background jobs in Go.

Atomic, transaction-safe 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.

create_user_api.go
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:
	if _, err = riverClient.InsertTx(ctx, tx,
		SendOnboardingEmailArgs{UserID: user.ID},
		nil,
	); 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.

A better workflow with River Pro

River Pro extends the River open source project with time-saving features and commercial support. Available through a paid subscription.

River UI Workflows

Workflows

Divide and conquer

Break down complex workflows into discrete tasks, including fan-out and fan-in execution.

Learn more →

River Queue Concurrency

Concurrency limits

Globally limit concurrent jobs with granular partitioning. Scale your workloads with precision and confidence.

Learn more →

Sequences

Ordered execution

Run sequences of jobs in strict order, each running independently in parallel.

Learn more →

No limits

Self-hosted and scalable

No new external services—just use your existing database. No per-workflow pricing.

Keep it going

Support River's development

Help ensure the sustainable maintenance and development of River.

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 {
	river.WorkerDefaults[SendEmailArgs]
}

// 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(user.email)
}

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, or adjust concurrency limits (Pro only).
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 rate limiting, and concurrency controls. Sign up to get updates on our progress.