Getting started
Quickstart
Run your first background job in about five minutes: get a key, start a worker on your machine, queue a job and watch it succeed.
On this page
Before you start
You need a Halyard account and one of the supported runtimes: Node.js 20.11+, Python 3.10+, Go 1.22+ or Ruby 3.2+. Everything in this guide runs against the sandbox, so it is free and safe to repeat.
The example sends a welcome email when a user signs up. Your web server creates a job on the emails queue, and a worker process on your machine picks it up and runs the send-welcome-email task.
Run your first job
Five steps, each a minute or so. Keep two terminals open: one for the worker, one for creating jobs.
Get a test key
Sign in to the dashboard, open Developers → API keys and copy the secret test key. Test keys start with
hy_test_. Put it in a git-ignored.env.localfile at the root of your project..env.localHALYARD_KEY=hy_test_51Hq8Zx2rTmV9cKpL3dPrefer the terminal?
halyard logindoes the same thing; see Install the CLI.Install the SDK
Add the official SDK for your language. Each one wraps the REST API and includes the worker runtime.
npm install @halyard/nodepip install halyardgo get halyard.example/go/halyard@v3gem install halyardWrite a worker
A worker connects to Halyard, claims jobs from the queues you list and calls the handler registered for each task. Whatever the handler returns is stored as the job's
result; if it throws, the attempt fails and the job retries.import { Worker } from "@halyard/node"; const worker = new Worker(process.env.HALYARD_KEY, { queues: ["emails"] }); worker.task("send-welcome-email", async (job) => { const { user_id } = job.payload; job.log(`Sending welcome email to ${user_id}`); await sendEmail({ to: user_id, template: "welcome" }); return { delivered: true };}); await worker.start();import osfrom halyard import Worker worker = Worker(os.environ["HALYARD_KEY"], queues=["emails"]) @worker.task("send-welcome-email")def send_welcome_email(job): user_id = job.payload["user_id"] job.log(f"Sending welcome email to {user_id}") send_email(to=user_id, template="welcome") return {"delivered": True} worker.run()package main import ( "context" "os" "halyard.example/go/halyard") func main() { w := halyard.NewWorker(os.Getenv("HALYARD_KEY"), halyard.WorkerOptions{ Queues: []string{"emails"}, }) w.Handle("send-welcome-email", func(ctx context.Context, job *halyard.Job) (any, error) { userID := job.Payload["user_id"].(string) job.Logf("Sending welcome email to %s", userID) if err := sendEmail(ctx, userID, "welcome"); err != nil { return nil, err } return map[string]any{"delivered": true}, nil }) w.Run(context.Background())}require "halyard" worker = Halyard::Worker.new(ENV.fetch("HALYARD_KEY"), queues: ["emails"]) worker.task("send-welcome-email") do |job| user_id = job.payload["user_id"] job.log("Sending welcome email to #{user_id}") send_email(to: user_id, template: "welcome") { delivered: true }end worker.runStart it with
halyard dev, which runs your worker against the sandbox and restarts it when files change. Leave it running in its own terminal:halyard dev halyard dev› Using test key hy_test_51Hq…L3d (sandbox)› Loaded halyard.yaml · 1 queue, 1 task✓ Worker connected · listening on emails (concurrency 5) Waiting for jobs… press Ctrl+C to stopCreate a job
From a second terminal or your app, create a job. Name the queue and task, and pass any JSON payload up to 256 KB.
curl https://api.halyard.example/v3/jobs \ -H "Authorization: Bearer hy_test_51Hq8Zx2rTmV9cKpL3d" \ -H "Content-Type: application/json" \ -d '{ "queue": "emails", "task": "send-welcome-email", "payload": { "user_id": "usr_8KfQ2" } }'import { Halyard } from "@halyard/node"; const halyard = new Halyard(process.env.HALYARD_KEY); const job = await halyard.jobs.create({ queue: "emails", task: "send-welcome-email", payload: { user_id: "usr_8KfQ2" },}); console.log(job.id, job.status); // job_3Rk9Tq7Xw2 queuedimport osfrom halyard import Halyard halyard = Halyard(os.environ["HALYARD_KEY"]) job = halyard.jobs.create( queue="emails", task="send-welcome-email", payload={"user_id": "usr_8KfQ2"},) print(job.id, job.status) # job_3Rk9Tq7Xw2 queuedclient := halyard.New(os.Getenv("HALYARD_KEY")) job, err := client.Jobs.Create(ctx, &halyard.JobParams{ Queue: "emails", Task: "send-welcome-email", Payload: map[string]any{"user_id": "usr_8KfQ2"},})if err != nil { log.Fatal(err)}fmt.Println(job.ID, job.Status) // job_3Rk9Tq7Xw2 queuedhalyard = Halyard::Client.new(ENV.fetch("HALYARD_KEY")) job = halyard.jobs.create( queue: "emails", task: "send-welcome-email", payload: { user_id: "usr_8KfQ2" }) puts job.id, job.status # job_3Rk9Tq7Xw2 queuedThe API answers with
201 Createdas soon as the job is stored durably, usually in under 20 ms:201 Created{ "id": "job_3Rk9Tq7Xw2", "object": "job", "queue": "emails", "task": "send-welcome-email", "status": "queued", "payload": { "user_id": "usr_8KfQ2" }, "attempts": 0, "max_attempts": 8, "created_at": "2026-10-14T09:12:04.381Z"}Watch it run
Your worker terminal picks the job up within a second. Each transition is printed with the job ID, so you can follow one job across logs.
halyard dev 09:12:04.402 job_3Rk9Tq7Xw2 emails/send-welcome-email running attempt 1/809:12:04.405 job_3Rk9Tq7Xw2 log Sending welcome email to usr_8KfQ209:12:04.814 job_3Rk9Tq7Xw2 emails/send-welcome-email succeeded 412 msTo check a job from code, retrieve it by ID. You get the status, the handler's result and timing.
curl https://api.halyard.example/v3/jobs/job_3Rk9Tq7Xw2 \ -H "Authorization: Bearer hy_test_51Hq8Zx2rTmV9cKpL3d"const job = await halyard.jobs.retrieve("job_3Rk9Tq7Xw2"); console.log(job.status); // succeededconsole.log(job.result); // { delivered: true }console.log(job.duration_ms); // 412job = halyard.jobs.retrieve("job_3Rk9Tq7Xw2") print(job.status) # succeededprint(job.result) # {"delivered": True}print(job.duration_ms) # 412
Next steps
You have a worker and a job. From here, most teams look at one of these next.