Defining handlers
A handler is the function the worker calls when a job of a given type is
claimed. Register payload handlers with the package-level generic function
worker.Handle.
type WelcomeEmail struct {
UserID string `json:"user_id"`
Email string `json:"email"`
}
worker.Handle(w, "SendWelcomeEmail",
worker.JobConfig{Queue: "default", MaxAttempts: 5, Timeout: 60 * time.Second},
func(ctx context.Context, p WelcomeEmail, jc worker.JobContext) error {
jc.ReportProgress(0.5)
return sendEmail(ctx, p.Email)
})
Handle is a free function (not a method) because Go methods cannot be generic.
It closes over the worker w and captures the payload type at registration time.
The job type is explicit
The second argument — "SendWelcomeEmail" — is the job type, the routing
key that matches jobs to handlers. It is always supplied explicitly. This is the
string used when a job is enqueued (the job_type field), and it is what lets a
job enqueued from any language reach this Go handler. Use the same string on
both sides.
Payload decoding
The payload type is the generic parameter of your handler function. The worker decodes each job's JSON payload into it before the call:
- A JSON object is unmarshalled into your struct using the standard
jsontags. - A
nullor absent payload yields the zero value of the type. - A payload that fails to decode produces a failed outcome with a decode error — the handler is not called.
Use map[string]any if you want the raw decoded payload instead of a struct.
The handler signature
func(ctx context.Context, payload T, jc worker.JobContext) error
ctxis cancelled on timeout, shutdown, or a server cancel. Pass it to every blocking call.payloadis the decoded job payload.jcis the job context: job metadata, a job-scoped logger, andReportProgress.- Returning
nilreports the job as succeeded. Returning an error reports it as failed with the error's message and Go type; the server decides whether to retry based on the attempt count and the job's configured maximum.
A panic inside a handler is recovered and reported as a failed outcome — it never crashes the worker.
Per-handler configuration
worker.JobConfig sets defaults for the handler. Any zero field falls back to
the worker's option defaults.
| Field | Meaning | Default |
|---|---|---|
Queue | Queue this handler serves | "default" |
MaxAttempts | Attempts announced for this job type | worker DefaultMaxAttempts (3) |
Timeout | Per-execution deadline | worker DefaultTimeout (30m) |
When a claimed job carries its own timeout, that value takes precedence over
Timeout for that execution.
Registration rules
- Registering the same job type twice panics — a wiring bug caught at startup.
- An empty job type or a
nilhandler panics. - Register all handlers before calling
Run; registering afterward panics.
Idempotency
Because outcome reporting is best-effort, a job may be delivered more than once.
Make handlers idempotent — dedupe on jc.JobID before performing a side effect:
worker.Handle(w, "ChargeCard",
worker.JobConfig{Queue: "billing"},
func(ctx context.Context, p Charge, jc worker.JobContext) error {
if alreadyCharged(jc.JobID) {
return nil // safe no-op on re-delivery
}
return charge(ctx, p)
})