Skip to main content

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 json tags.
  • A null or 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
  • ctx is cancelled on timeout, shutdown, or a server cancel. Pass it to every blocking call.
  • payload is the decoded job payload.
  • jc is the job context: job metadata, a job-scoped logger, and ReportProgress.
  • Returning nil reports 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.

FieldMeaningDefault
QueueQueue this handler serves"default"
MaxAttemptsAttempts announced for this job typeworker DefaultMaxAttempts (3)
TimeoutPer-execution deadlineworker 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 nil handler 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)
})

See also​