When an API starts work that will finish later, its immediate response answers only one question: was the request accepted? The caller still needs to know whether the work completed, failed, or never started. Treat that gap as part of the product contract.
Consider a report export. The button can respond quickly while the file is built in the background. But a success toast without a durable job ID leaves the user guessing. If they close the tab, refresh, or retry, how will they find the result? If processing fails, where will the failure appear?
Return a handle, then make it useful
An acceptance response should give the caller a stable identifier and a way to check status. The status should distinguish queued, running, succeeded, and failed. It should say when it last changed and, when successful, where to retrieve the result. A failed job should explain what the user can do next without exposing internal errors.
The receipt also helps with retries. If the same action is submitted twice because the first response was lost, the system needs a deliberate answer: return the existing job, reject the duplicate, or create a second one. Make that behavior explicit before adding a retry button.
Test the journey after the request ends
The useful test is not simply “the queue received a message.” Start a job, lose the original page, and recover its status from the receipt. Then test a worker failure, a retry, and a result that expires. Can the user still tell what happened and what action is available?
Background processing moves waiting out of the request. It does not remove the need for a clear answer. A good receipt turns an invisible process into a promise the system can explain and keep.