Jobs
A Job is a named unit of queued work with a payload and a registered handler.
Jobs make background work explicit, observable, retryable, and operable.
When To Use Jobs
| Question | Guidance |
|---|---|
| Use this when | Background work needs a stable name, typed payload, handler, retry behavior, or worker lifecycle. |
| Avoid this when | The behavior is just a local function call or a typed fact that subscribers may observe. |
| Start with | A small payload containing IDs and references to source-of-truth data. |
| Upgrade to | Dedicated queues, retry policy, idempotency keys, and worker process planning as operational risk grows. |
Generated Package
Job code usually lives in:
internal/jobsCreate a job:
forj make:job SendWelcomeEmailStamp a generated dispatch helper with a named queue when the job belongs to a specific operational lane:
forj make:job reports:generate --queue reportsUse category:action for job names, such as emails:send or reports:generate. See Naming Conventions for the full naming map.
Job Shape
const SendWelcomeEmailTypeName = "emails:welcome"
type SendWelcomeEmailPayload struct {
UserID string `json:"user_id"`
}
type SendWelcomeEmail struct {
queues *queues.Manager
users *users.Service
}
func NewSendWelcomeEmail(queues *queues.Manager, users *users.Service) *SendWelcomeEmail {
return &SendWelcomeEmail{queues: queues, users: users}
}Job names should be stable operational identifiers.
Dispatch
Jobs own their dispatch shape:
func (j *SendWelcomeEmail) Queue(ctx context.Context, userID string) error {
payload, err := json.Marshal(SendWelcomeEmailPayload{UserID: userID})
if err != nil {
return err
}
_, err = j.queues.WithContext(ctx).Dispatch(
queue.NewJob(SendWelcomeEmailTypeName).
Payload(payload).
OnQueue("emails"),
)
return err
}Services can call job.Queue(ctx, id) without constructing raw queue messages.
Handling
Handlers bind payloads and delegate business behavior:
func (j *SendWelcomeEmail) HandleTask(ctx context.Context, msg queue.Message) error {
var payload SendWelcomeEmailPayload
if err := msg.Bind(&payload); err != nil {
return fmt.Errorf("bind send welcome email payload: %w", err)
}
return j.users.SendWelcomeEmail(ctx, payload.UserID)
}Return errors when the job should fail and let queue behavior handle retry policy.
Registration
Generated App construction registers framework-owned job handlers before workers start.
App-owned jobs should be registered through generated or documented App extension points before workers start.
Do not register handlers after workers are already running.
Common Mistakes
Common mistakes
- Do not hide job names behind anonymous functions.
- Do not put all business logic in
HandleTask; delegate to services. - Do not use untyped
map[string]anypayloads when a typed payload is clearer. - Do not swallow handler errors that should be retried or observed.
- Do not dispatch jobs from repositories unless persistence code intentionally owns that side effect.
Next Steps
- Queues explains queue configuration.
- Workers explains execution lifecycle.
- Retries and Idempotency explains safe retry behavior.
- Naming Conventions defines stable job names.
