A worker claims one available job
The HTTP server accepts Maya's invitation. The worker performs its saved email job. These processes have different responsibilities and can restart independently.
A worker repeatedly finds a due job, claims it, runs its handler, and records the outcome. Due means that the job's scheduled time has arrived, the job is unfinished, and no valid claim currently owns it.
The race in a plain query
Imagine two workers both select the first unfinished job. Each receives the same row before either updates it. Both send the invitation email. Reading a row does not reserve it.
A claim therefore needs one database operation with coordination. A common Postgres pattern locks the selected row, skips rows another claimant already locked, and updates ownership before committing.
SELECT id
FROM jobs
WHERE completed_at IS NULL
AND available_at <= now()
FOR UPDATE SKIP LOCKED
LIMIT 1;
The claim also needs eligibility checks for existing leases. This abbreviated query shows the locking mechanism, not the complete implementation. Postgres documents SKIP LOCKED as useful for multiple consumers of a queue-like table. It is unsuitable for a general query that must return a consistent view of all records. Postgres locking clauses.
Release the transaction before delivery
The worker commits its claim before waiting on SMTP. Holding a database transaction across a slow external request would retain a connection and locks for the entire network delay. Instead, a lease records temporary ownership after the short claim transaction ends.
An expected observation is that two idle workers can claim different jobs. If one worker stops, the other eventually becomes eligible to reclaim its expired work. Neither process needs the other process's in-memory state.
This coordination protects claims in Postgres. It does not prevent a slow former worker from continuing an external send after its lease expires. The next lesson separates database ownership from external execution.
Read the claim operation in Read api/src/jobs.ts and worker startup in Read api/src/worker-main.ts.
A worker claims work through shared durable state. A plain select followed by a later update leaves a race.
Would a boolean called busy in the worker solve the race?
It can prevent overlapping work inside one process. A second process has its own boolean. Postgres coordinates workers because they share the same job records.