The project is in a healthy, maintained state
activejob-temporal bridges Rails ActiveJob with Temporal's durable execution engine. It provides a drop-in ActiveJob adapter, Temporal workflows, and supporting tooling so Rails apps gain fault-tolerant scheduling, retries, and observability with minimal changes.
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
2025
2026
 Dependencies

Development

~> 1.8
~> 13.2
~> 3.12
~> 1.50
~> 0.22
~> 0.9, >= 0.9.42

Runtime

>= 8.1, < 9
>= 8.1, < 9
>= 0.3
~> 3.9
>= 1.4.0, < 1.5
 Project Readme

activejob-temporal

Temporal-powered adapter for Rails ActiveJob.

Gem Version License: MIT CI codecov

This gem is under active development. Expect breaking changes until v1.0.0.

What It Does

activejob-temporal runs Rails ActiveJob work through Temporal workflows and activities. It is intended for jobs where durable execution, crash recovery, long-running retries, cancellation visibility, and Temporal UI history matter more than queue simplicity.

Use a traditional ActiveJob backend when the work is short, simple, and does not justify operating Temporal.

Requirements

  • Ruby >= 4.0
  • Rails >= 7.2 through ActiveJob 8.x
  • Temporal cluster, either self-hosted or Temporal Cloud

Temporal Ruby SDK 1.4.x compatibility is contract-tested against 1.4.0 and 1.4.1.

Install

gem "activejob-temporal"
bundle install

Then configure ActiveJob:

# config/application.rb or config/environments/production.rb
config.active_job.queue_adapter = :temporal

Quick Start

Create a Temporal initializer:

# config/initializers/activejob_temporal.rb
ActiveJob::Temporal.configure do |config|
  config.target = ENV.fetch("ACTIVEJOB_TEMPORAL_TARGET", "127.0.0.1:7233")
  config.namespace = ENV.fetch("ACTIVEJOB_TEMPORAL_NAMESPACE", "default")
  config.task_queue_prefix = ENV.fetch("ACTIVEJOB_TEMPORAL_TASK_QUEUE_PREFIX", nil)

  config.default_activity_timeout = 15.minutes
  config.default_retry_initial_interval = 30.seconds
  config.default_retry_backoff = 2.0
  config.default_retry_max_attempts = 1
end

Register the built-in search attributes once per Temporal cluster:

tctl admin cluster add-search-attributes \
  --name ajClass --type Keyword \
  --name ajQueue --type Keyword \
  --name ajJobId --type Keyword \
  --name ajEnqueuedAt --type Datetime \
  --name ajTenantId --type Int \
  --name ajTags --type KeywordList

Temporal Cloud deployments may use the temporal CLI instead of tctl; keep the same attribute names and types.

Define and enqueue normal ActiveJob jobs:

class SendInvoiceJob < ApplicationJob
  queue_as :billing

  retry_on SomeTransientError, wait: 60.seconds, attempts: 5
  discard_on SomeFatalError

  def perform(invoice_id)
    invoice = Invoice.find(invoice_id)
    InvoiceMailer.invoice_ready(invoice).deliver_now
  end
end

SendInvoiceJob.perform_later(invoice.id)
SendInvoiceJob.set(wait: 5.minutes).perform_later(invoice.id)
SendInvoiceJob.set(tags: [:urgent, :customer_123]).perform_later(invoice.id)

Start a worker for every task queue you use:

ACTIVEJOB_TEMPORAL_TARGET=localhost:7233 \
ACTIVEJOB_TEMPORAL_NAMESPACE=default \
ACTIVEJOB_TEMPORAL_TASK_QUEUE=billing \
bundle exec temporal-worker

Open Temporal UI and look for workflows named ajwf:SendInvoiceJob:<job_id>.

require "activejob/temporal" loads the Rails adapter, enqueue path, schedules, lookup APIs, and shared configuration. Worker-only runtime code is loaded by temporal-worker; custom worker boot code should require activejob/temporal/worker_runtime before registering workflows or activities.

Common Capabilities

Need API Detailed guide
Delay a single job MyJob.set(wait: 5.minutes).perform_later(...) Usage Patterns
Register recurring cron work temporal_schedule cron: "0 2 * * *" and create_temporal_schedule Recurring Jobs
Enqueue only when work exists perform_later_if(condition, *args) Usage Patterns
Enqueue many prepared jobs ActiveJob::Temporal.enqueue_batch(jobs) Usage Patterns
Run sequential jobs in one workflow set(chain: [NextJob]) Usage Patterns
Start child ActiveJob workflows set(child_workflows: [ChildJob]) Usage Patterns
Call external Temporal activities or workflows ActiveJob::Temporal.activity(...), ActiveJob::Temporal.workflow(...) Usage Patterns
Wait for separately enqueued jobs set(depends_on: parent_job) Usage Patterns
Map ActiveJob retries to Temporal retry_on, discard_on Retry Policy Guide
Park exhausted failures config.dead_letter_queue = "failed_jobs" Configuration Reference
Tune activity timeouts temporal_options start_to_close_timeout: ... Usage Patterns
Add throughput limits rate_limit 100, per: :second Configuration Reference
Cancel or inspect jobs cancel, cancel_all, status, running? Usage Patterns
Pause, resume, query, or update workflow state signal, query, update Usage Patterns
Add runtime middleware config.add_middleware MiddlewareClass Middleware
Expose Prometheus metrics config.observability.use :prometheus Metrics Guide
Encrypt job payloads encrypt_payload = true Configuration Reference
Store large payloads externally payload_storage_adapter = MyPayloadStorage.new Configuration Reference

Baseline behavior also includes transaction-aware enqueueing through ActiveJob, GlobalID-compatible argument serialization, structured JSON logs, searchable set(tags:) metadata, and JSON payloads with opt-in MessagePack or Marshal envelopes.

Configuration

The full configuration surface lives in Configuration Reference. The machine-readable schema is docs/config_schema.yaml.

The most common settings are:

ActiveJob::Temporal.configure do |config|
  config.target = "temporal.example.com:7233"
  config.namespace = "production"
  config.task_queue_prefix = "rails-"
  config.priority_task_queues = { 10 => "high_priority", 90 => "low_priority" }
  config.default_activity_timeout = 30.seconds
  config.default_retry_initial_interval = 10.seconds
  config.default_retry_backoff = 1.5
  config.default_retry_max_attempts = 5
end

Workers can also read environment variables such as ACTIVEJOB_TEMPORAL_TARGET, ACTIVEJOB_TEMPORAL_NAMESPACE, ACTIVEJOB_TEMPORAL_TASK_QUEUE, ACTIVEJOB_TEMPORAL_MAX_CONCURRENT_ACTIVITIES, ACTIVEJOB_TEMPORAL_METRICS_PORT, and TLS certificate settings. See Worker Setup for the worker-focused list.

Documentation

Start with docs/README.md for the complete documentation map.

High-use guides:

See examples/basic_rails_app for a Docker Compose Rails app with Temporal, Temporal UI, search attribute setup, workers, seeded GlobalID records, and tests.

Contributing

Install dependencies and run the focused local checks:

rvm 4.0.3 do bundle install
rvm 4.0.3 do bundle exec rake spec:unit
rvm 4.0.3 do bundle exec rubocop
rvm 4.0.3 do bundle exec rake build

For changes near configured mutation subjects, also run:

rvm 4.0.3 do bundle exec rake mutation

Keep docs updated when behavior changes. The scoped mutation task runs on Ruby 4. Mutant 0.16 may warn about an older parser dependency; keep using the Ruby 4 toolchain and treat parser failures on new Ruby syntax as a mutation tooling limitation.

Bug reports and feature requests belong in GitHub issues.

License

MIT. See LICENSE.

Versioning

This project follows Semantic Versioning. See CHANGELOG for release history.

Current version: 0.1.0