Skip to content

Options at a glance

Every knob in one place: job definition options, per-enqueue options, schedule options, worker tuning, and store layer construction. All durations accept Duration.Input ("30 seconds", Duration.minutes(5), millis).

Job.make(name, options)

The definition is the shared contract between producers and runners; see Defining jobs for the full treatment.

OptionDefaultDescription
payloadrequiredPayload schema: a Schema.Struct or its bare fields object
successSchema.VoidSchema for the handler's success value, decodable via awaitResult/attempts
errorSchema.NeverSchema for the handler's typed failure, or a list of schemas unioned for you; round-trips through storage
idempotencyKeynone(payload) => string: derives a stable job id; re-enqueueing the same key is a no-op
dedupenone(payload) => DedupeInput: derives a dedup key (never changes the job id); see Deduplication
metadatanone(payload) => Record<string, string>: queryable business context, indexed by every driver
retryablenone(error) => boolean: returning false for a typed failure skips the remaining retry budget
queue"default"The queue this job runs on
storedefault JobStoreBind to a named store key from JobStore.named(...); see Stores
defaultsnoneDefault per-enqueue options (delay, priority, attempts, backoff, keep, timeout); per-enqueue values override

Per-enqueue options

Accepted by enqueue and execute. enqueueMany takes the same set minus jobId and dedupe: a shared id or dedup key would collapse the batch into one job (per-item dedup still runs via the definition's dedupe callback).

OptionDefaultDescription
jobIdstore-assignedExplicit job id; an existing id is a silent no-op returning it. Overrides idempotencyKey
queuedefinition's queueSend to a different queue
metadatanoneMerged over the definition-derived metadata
dedupedefinition's dedupePer-enqueue dedup key/mode (DedupeInput)
delaynoneRun this long after enqueue (relative)
atnoneRun at an absolute instant (any DateTime.Input); a past at runs immediately
priority0Higher runs first; ties are FIFO
attempts1Total attempts including the first run (1 = no retries)
backoffimmediateRetry delay: { type: "fixed" | "exponential", delay, factor? } (factor defaults to 2)
keepkeep foreverRetention for the terminal record: flat { count, age } or split per state; see Retention
timeoutnonePer-run limit; the worker interrupts the handler fiber past it

delay and at are mutually exclusive: the options type is a union, so setting both is a compile error. See Enqueueing for the details.

ScheduleOptions

Passed to MyJob.schedule(key, options). Set exactly one of cron or every; see Repeatable jobs.

OptionDefaultDescription
cronnone5-field cron expression; first fires at the next matching occurrence
tzUTCIANA time zone for cron (e.g. "America/New_York")
everynoneFixed interval; first fires one interval from now and stays on that grid
payloadrequiredThe payload enqueued for every occurrence
metadatanoneMerged over the definition's metadata
priority0Priority for each occurrence
attempts1Attempt budget for each occurrence
backoffimmediateRetry backoff for each occurrence
keepkeep foreverRetention for each occurrence's terminal record
timeoutnonePer-run limit for each occurrence
groupnoneOwnership label for reconciliation; set by JobSchedules.layer, unlabeled rows are never pruned

JobSchedules.layer(options)

Declarative schedule reconciliation; see Repeatable jobs.

OptionDefaultDescription
grouprequiredOwnership label; the layer only prunes schedules carrying this group
schedulesrequiredThe full declared set: JobSchedules.schedule(job, key, options) entries
removal"warn""warn" logs undeclared group members; "group" prunes them
removeAfternoneGrace window before pruning (requires removal: "group")
storesnoneExtra store keys to reconcile when no entry references them

Flow.make(name, options)

Cross-store parent-child flows; see Parent-child flows.

OptionDefaultDescription
parentrequiredThe parent job; its store owns the flow, and the flow's producer verbs delegate to it
childrenrequiredThe closed set of child definitions fanOut may produce
onChildFailure"continue""continue" hands failures to collect; "fail" settles the parent as failed on the first one and cancels the rest

Flow.children(job, items) builds one fan-out group; each item takes key (unique within the flow; the idempotency mechanism), payload, and per-child options (priority, attempts, backoff, keep, timeout, metadata; no delay, children run immediately). DigestFlow.toLayer({ fanOut, collect }, options?) registers the two phases and accepts the same concurrency/queue options as a job's toLayer.

Worker.layer(options)

All optional. See Workers & handlers for how these interact at runtime.

OptionDefaultDescription
storedefault JobStoreWhich named store this worker claims from
concurrency1Default taker fibers per queue
queuesnonePer-queue overrides: { email: { concurrency: 5 } }
lockDuration30 secondsHow long a claim's lock lasts before the job counts as stalled
lockRenewIntervalhalf of lockDurationHeartbeat cadence; also delivers cross-process cancels
stalledInterval30 secondsHow often to sweep for stalled jobs
maxStalledCount1Stalls tolerated before a job is failed outright
pollInterval5 secondsIdle fallback when no wake-up arrives; wake-ups are push-based and queue-filtered, so the default is fine
scheduleSweepInterval15 secondsHow often to tick due repeatable-job schedules
queueMetricsIntervaloffSample store.counts() per registered queue into the depth gauge at this cadence
handlerSpanName`${name}.run`(context) => string: names the span wrapping each handler run
traceLinking"auto"Parent for immediate jobs, causal link for delayed ones; "parent" / "link" force a mode, "none" disables the cross-trace edge
onJobFailurenoneCallback after each failed run is acked ({ jobId, name, queue, attempt, attemptsMax, willRetry, cause }); runs isolated
flowsnoneFlows whose children this worker runs; gives its relay the parent stores so child results push the moment they ack
flowSweepInterval30 secondsFlow sweeper cadence (reconcile + cascade), the pending age before reconciliation checks a child, and the relay's fallback drain cadence
idrandomIdentifier used in lock tokens

MyJob.toLayer(handler, options) also accepts concurrency (taker fibers for this job's queue; the first registration for a queue decides) and queue (consume a different queue than the definition's).

TIP

Locks, stalls, and retries are covered in depth in Retries & timeouts; the tracing and metrics options in Observability.

Store layer options

Every driver accepts idGenerator (generator for store-assigned ids; default a compact j-<n> sequence), historyTtl (retention ceiling: one duration or a per-state split), and historySweepInterval (sweep cadence, default 1 minute).

OptionMemoryPostgres (drizzle)Redis
idGenerator / historyTtl / historySweepIntervalyesyesyes
Table instances (jobs, attempts, schedules, queues, dedupe, flowChildren, flowOutbox)norequiredno
indexes: list-index opt-out (see Redis)nonoyes
extraValues: fill extended columns at enqueuenoyesno
store: bind to a named store keyvia layerForoptionvia layerFor
validate: probe tables at startup (default true)noyesno
prefix: key namespace (default "effect-mq")nonoyes

Constructors:

ts
MemoryJobStore.layer                        // no options
MemoryJobStore.layerWith({ historyTtl: "7 days" })
MemoryJobStore.layerFor(Durable, options)   // named store

DrizzleJobStore.layer({ jobs, attempts, schedules, queues, dedupe, flowChildren, flowOutbox, ...options })

RedisJobStore.layer({ prefix: "myapp-jobs" })
RedisJobStore.layerFor(Ephemeral, options)  // named store

See Postgres and Redis for full setup, including the drizzle schema factories and the Redis client layer.

Job verbs

Everything a definition class exposes:

VerbPurpose
enqueue(payload, options?)Queue the job; returns the JobId
enqueueMany(payloads, options?)Queue a batch in one store round trip per chunk; ids come back aligned with payloads
execute(payload, options?)enqueue + awaitResult in one call
poll(jobId)Read current status as Option<JobStatus>
awaitResult(jobId, options?)Poll until terminal, then return the typed result (accepts a custom pollSchedule)
attempts(jobId)The decoded run ledger, oldest first
retry(jobId)Re-run a failed job with a fresh attempt budget; ledger preserved
cancel(jobId)Waiting/delayed become cancelled now; a running handler fiber is interrupted on the worker's next heartbeat
cancelByKey(key)Cancel whatever pending job holds this dedup key; idempotent, returns boolean
promote(jobId)Run a delayed job now
schedule(key, options)Create or replace a durable repeatable schedule
unschedule(key)Remove a schedule; false when it did not exist
toLayer(handler, options?)Register the handler, as a layer to provide on top of Worker.layer()

All producer verbs require only the job's store in context, never the Worker. Admin semantics (cancel, promote, pause/resume) are covered in Cancellation & admin.

Where to next