Move Slow Work Out of the Request Path
R050 is the CPU-heavy request that blocks every other request in a Node process. R075 is the signup endpoint waiting for an email provider. Both are the same architecture mistake: work that does not need to finish before the HTTP response is sitting inside the response path.
Async syntax does not move CPU work somewhere else. await gives the event loop room while an
external operation is waiting. A large PDF render or image transform still occupies the process.
Split request from work
The request handler should do four things:
- authenticate and validate the input;
- create a stable idempotency key;
- persist a job and its payload;
- return
202 Acceptedwith a job ID and status URL.
The worker claims the job, performs the slow work, records the external result, and moves the job
to succeeded or failed. CPU-heavy work belongs in a worker process or worker thread. Slow email,
webhook, export, and third-party API calls belong in a durable queue.
Do not return 202 before the job is durable. Otherwise the server can crash after the response
and before the work exists anywhere.
Give the job a real state machine
Use visible states:
queued -> running -> succeeded
-> retry_wait -> queued
-> dead_letter
-> cancelled
Record attempt count, next attempt time, last error, worker lease, created time, and final external
ID. A stuck running job needs a lease timeout so another worker can recover it.
Make retries safe
The queue may deliver a job more than once. Design for at-least-once delivery:
- derive or accept an idempotency key;
- store it with a unique constraint;
- pass it to providers that support idempotency;
- save the provider's external ID;
- check success before repeating the side effect.
Retry transient failures with backoff and jitter. Do not retry invalid input, cancellation, or a permanent permission failure forever. Move exhausted jobs into a visible dead-letter state with an owner and replay button.
Let the user see the truth
The API response should say queued, not completed. The client can poll the status URL, subscribe to updates, or wait for a notification. If email delivery fails later, preserve the account and expose the failed welcome-email job instead of rolling back a successful signup.
Claude can summarize errors or draft a customer message. Code owns validation, state transitions, idempotency, and retry timing.
Run the starter locally
npm test
npm run validate
npm run sample
Docker Compose starts the workflow boundary and n8n. The starter returns a proposed queue contract in dry-run mode and executes no external job.