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