On this page

Enqueue options

Enqueue takes a variadic list of Options that control how a task is scheduled and handled once it's in the pipeline: when it becomes available, which queue it lands in, how many times it's retried, whether a duplicate is rejected, and how long a completed task is kept around. Each option is a small optionFunc that mutates the shared enqueueOptions, so they compose freely on a single Enqueue call.

chronos.Enqueue(ctx, client, EmailArgs{...},
	chronos.WithQueue("critical"),          // route to a queue
	chronos.WithMaxRetry(5),                 // retry budget (default 25)
	chronos.WithProcessIn(30*time.Minute),   // run later (delayed)
	chronos.WithUnique(10*time.Minute),      // dedup identical (kind+payload) tasks
	chronos.WithDeadLetterDiscard(),         // drop instead of archive on exhaustion
	chronos.WithRetention(24*time.Hour),     // keep the completed task for inspection
)

Delayed execution: WithProcessIn

chronos.Enqueue(ctx, client, EmailArgs{UserID: "u1", Body: "hi"},
	chronos.WithProcessIn(30*time.Minute),
)

The task first becomes available for processing after d elapses; a non-positive d enqueues it immediately. (There's also WithProcessAt(t time.Time) for an absolute time, if you need it.)

Queue routing: WithQueue

chronos.Enqueue(ctx, client, EmailArgs{UserID: "u1", Body: "hi"},
	chronos.WithQueue("critical"),
)

Routes the task to the named queue instead of the default one. The server only picks up tasks from queues listed in its ServerConfig.Queues.

Retry budget: WithMaxRetry

chronos.Enqueue(ctx, client, EmailArgs{UserID: "u1", Body: "hi"},
	chronos.WithMaxRetry(5),
)

Caps the number of retries before the task is dead-lettered; defaults to DefaultMaxRetry. A negative value is clamped to 0 (no retries).

Uniqueness: WithUnique

chronos.Enqueue(ctx, client, EmailArgs{UserID: "u1", Body: "hi"},
	chronos.WithUnique(10*time.Minute),
)

Deduplicates tasks by (kind + payload): while a matching task is anywhere in the pipeline (pending, retrying, scheduled), enqueueing another returns ErrDuplicateTask. ttl mainly bounds the lock for time spent waiting with no worker renewing it — set it comfortably above the expected total waiting time; for a delayed task the lock TTL is automatically extended to cover the delay.

Discard on exhaustion: WithDeadLetterDiscard

chronos.Enqueue(ctx, client, EmailArgs{UserID: "u1", Body: "hi"},
	chronos.WithDeadLetterDiscard(),
)

Discards the task on retry exhaustion instead of storing it in the archived ZSET. The OnDeadLetter hook on ServerConfig still fires either way.

Retention: WithRetention

chronos.Enqueue(ctx, client, EmailArgs{UserID: "u1", Body: "hi"},
	chronos.WithRetention(24*time.Hour),
)

Keeps a succeeded task in the "completed" set for d so it can be inspected before the janitor removes it; without this option a successful task is deleted immediately. Durations under one second round up to one second. On high-throughput queues, a long retention grows Redis memory — the per-queue MaxCompleted cap on ServerConfig bounds the worst case.

Gotcha: not every option works everywhere

WithUnique and WithTaskID are rejected when a task is enqueued as a step inside a chain — see that doc for why. And a WithUnique lock isn't just a fire-and-forget TTL: once a worker picks the task up, the server's heartbeat renews the lock for as long as processing takes, a detail covered in Retries & reliability. Full option and default values are documented on pkg.go.dev.