Fast and reliable
background jobs.

Enqueue jobs transactionally in your existing database. No added services, built to scale. Get durable workflows, sequences, and global concurrency limits with River Pro.

Get started in Go Ruby Rust TypeScript

Runs on Postgres and SQLite

Paper-craft illustration of a river winding through a valley past a village, a red bridge, and a snow-capped mountain at sunrise

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 applications built on Postgres or SQLite, this also means no added services to manage.

func createUser(ctx context.Context, email string) (*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,
		SendEmailArgs{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
}
Available for Go Ruby, Rust, and TypeScript coming soon

A better workflow with River Pro

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

ValidateOrder validate_order ChargePayment charge_card ReserveStock reserve_stock CheckFraud check_fraud SendReceipt send_receipt CreateLabel create_label Waiting for packing Packed by warehouse ShipOrder ship_order

Workflows

Divide and conquer

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

Learn more →

Concurrency limits

Fine-grained control

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.

Thoughtful design.
Modern conventions.

River is designed by experienced developers to follow the best known conventions in all supported languages. Use Go, Ruby, Rust, TypeScript, or all at the same time.

// Each worker has a corresponding args type:
type SendEmailArgs struct {
	UserID int64 `json:"user_id"`
}

func (SendEmailArgs) Kind() string { return "send_email" }

type SendEmailWorker struct {
	river.WorkerDefaults[SendEmailArgs]
}

// Work receives a typed *Job[T] to process:
func (w *SendEmailWorker) Work(ctx context.Context, j *river.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.