Background exports for MENA SaaS: retries and cancellation
Design private report jobs with durable acceptance, retry-safe release, cancellation races and permission checks for Saudi and UAE deployments.

A finance operator requests a customer export, closes the browser and comes back later. Meanwhile, a worker restarts, the queue delivers the task again and an administrator removes the operator's access. Which event decides whether the report becomes downloadable?
For SaaS teams serving Saudi and UAE customers, a background export needs a contract that survives those changes. Moving a query into a queue solves the long HTTP request. It does not, by itself, settle permission, duplicate execution, cancellation or the meaning of the report's date range.
This guide proposes a job-and-artifact design for private reports. The examples concern read-only exports, not payments or other irreversible mutations. The state names and release rules below are application design choices, not a cloud-provider guarantee.
Cover: original SultanByte artwork showing a queued report passing through a release gate before becoming available.
Accept a job without promising a finished report
RFC 9110 defines HTTP 202 as acceptance for processing, not completion. Processing can still fail or be disallowed later. Return a durable job reference and a status endpoint rather than a success message that implies the file already exists.
Microsoft's asynchronous request-reply pattern describes this separation: accept the operation, process it independently and let the client inspect its state. It also discusses polling guidance and cancellation instructions. Use that as an architectural reference, not a requirement to use Azure.
For the proposed report service, distinguish queued, running, cancel_requested, cancelled, ready and failed. Keep artifact expiry separate: a successfully generated report can later become unavailable without its generation having failed.
Authenticate and authorise status requests. An opaque job ID helps avoid accidental exposure, but is not an access-control mechanism. Return a safe error code and support reference when a job fails; do not expose a database exception or an object-store credential.
An Arabic interface and an English interface should display the same underlying job state. Translate the explanation, not the state machine. If the client loses its connection after submission, it needs a way to recover the original job rather than immediately creating another.
Save the export contract before dispatch
Record the tenant, requesting principal, approved dataset, filters, column policy, export format and schema version. Resolve the processing deployment on the server. A client-supplied country or bucket name must not choose where a worker reads customer data.
Choose the report's time semantics explicitly. A request for "yesterday" needs a timezone and resolved interval before it enters the queue. Store those resolved boundaries. Keep the display calendar and language separate from the underlying selection so changing interface language cannot silently change the rows.
There is also a difference between "records matching this interval when execution begins" and "the dataset as it existed when the request was accepted." A timestamp written on the job does not preserve an earlier database state.
PostgreSQL 18's isolation documentation explains that Read Committed queries can see different snapshots across statements, while Repeatable Read provides a stable transaction snapshot. Neither description means an application can reopen a finished transaction's snapshot later merely by supplying a timestamp.
For a multi-page report, choose an implementable consistency policy: a bounded snapshot transaction, an immutable materialised dataset, or a documented live-data export. Evaluate long transactions with the database owner. Do not describe a report as "as of request time" unless the storage and query design can actually reproduce that view.
Close the database-to-queue gap
A job can be saved successfully while sending its queue message fails. Reversing those operations creates the opposite problem: a worker sees a message whose job never committed.
AWS's transactional outbox guidance addresses this dual-write problem. In its relational form, the business record and an outbox record commit together; another process forwards committed outbox entries to the queue. That forwarding can still produce duplicates.
For exports, put the job and dispatch intent in the same local transaction. Keep the queue message small, containing a job reference rather than the customer rows or a reusable download credential. The worker then loads the authoritative contract from the application's store.
Define retry behaviour for submission too. A proposed application idempotency key should be scoped to the tenant, principal and operation. Bind it to the validated export contract. Reusing a key with different filters should produce a conflict, not silently return somebody's earlier report. Document how long the key remains usable.
A reconciliation process should find committed jobs that never progressed to dispatch. Otherwise the status page can say "queued" indefinitely while no worker has any chance of seeing the work.
Let retries repeat computation, not publication
Queue delivery and application completion are different events. Amazon SQS documents that a message can become visible again if it is not deleted before its visibility timeout expires. It also warns that delivery is not guaranteed to be unique during that timeout.
Google Cloud Tasks likewise documents possible duplicate execution and does not promise ordinary task execution order. These are provider-specific behaviours with the same practical consequence here: a worker must tolerate a job being offered again.
Use a durable claim with an attempt identifier or generation. Renew it while working, and make every final state transition conditional on still owning that generation. When a replacement worker claims the job, the earlier worker must lose the ability to publish its result, even if it wakes up and finishes later.
Write each attempt to a distinct private staging object. Do not let competing workers overwrite one shared object key. After generation and validation, conditionally commit one exact artifact reference as the job's result. Only that committed reference is eligible for download.
This design can waste some computation during a race, but it should not expose two conflicting results. Test that property in the deployed database and storage combination. A database commit cannot make an object-store write part of the same local transaction; abandoned staging objects need a separate cleanup path.
Original infographic: SultanByte. Proposed design informed by RFC 9110, OWASP, AWS, Microsoft, Google Cloud and PostgreSQL documentation, October 2026. It is not a measured reliability result.
Make cancellation compete with release
"Cancel requested" is an instruction, not evidence that execution stopped. A worker may be inside a database query or completing an upload when the request arrives. Deleting a queue message does not prove that a worker which already received it has stopped.
For this design, cancellation and final release must compete through the same durable job record. A cancellation request changes an eligible job to cancel_requested. Finalisation can commit ready only while the job is still running, the worker owns the current generation and the release policy passes.
If cancellation commits first, the worker cannot release its staged result. It stops at a safe checkpoint and records cancelled; a recovery process must handle that transition if the worker crashes. If release commits first, the API should report that the report is already ready rather than claiming cancellation succeeded.
Keep removal of an already-ready artifact as a different operation with its own permissions and retention behaviour. Cancellation cannot recall a file someone has downloaded.
Avoid holding a database lock for the entire export. Coordinate the short claim, cancellation and finalisation transitions, then test their interleavings. A conditional update is useful only if all competing paths honour the same conditions and interpret a zero-row result correctly.
Recheck access at meaningful boundaries
OWASP's authorisation guidance recommends default denial, least privilege and permission checks on every request. Apply those rules to submission, status, cancellation and download. A successful submission should not turn the job identifier into a permanent credential.
For user-requested private exports, the proposed policy is to recheck current authority before reading data, before release and when a download is requested. Decide how permission changes interact with running work. If a revocation must prevent a concurrent release, coordinate that policy change with finalisation; a detached check followed by an unconditional write leaves a race.
Scheduled organisation reports may use a service identity instead. Give that identity an explicit scope and owner rather than quietly borrowing the privileges of whichever employee created the schedule.
Separate generation from delivery. Notifications can link to the authenticated report page instead of carrying the file or a long-lived bearer URL. SultanByte's private-download guide covers the later choice between delegated links and a streaming proxy. That choice should not be hidden inside the queue handler.
Keep the Saudi and UAE deployments explicit
Consider a hypothetical product with separately approved Saudi and UAE deployments. Maintain a deployment record for each, covering the job store, queue, workers, staging storage, result storage and operational logs. Route the job using that record, and reject a worker that receives a job outside its assigned deployment.
A retry or manual redrive must preserve the assignment. Do not treat a shared emergency queue as permission to move the report to another deployment. Review recovery routes before an incident, including where failed payloads and staging objects remain.
This is an engineering boundary, not a claim that every Saudi or UAE report must stay inside national borders. Applicable contractual, sectoral and legal requirements need a separate assessment. This guide is practical information, not legal advice.
Retain enough metadata to explain the result without retaining every exported row in logs. Record job and attempt references, state transitions, policy outcomes and artifact expiry. Define cleanup for abandoned staging files as well as released reports. Keep the status record long enough to explain expiry to the user rather than making every missing file look like an unknown job.
Test the races before adding a progress bar
Use synthetic records and isolated tenants. Submit the same operation twice, lose the acceptance response and retry. Stop the dispatcher after the database commit, then verify that reconciliation eventually dispatches the same job.
Pause a worker until its claim expires, start a replacement and resume the old worker. Require only the current generation to release a result. Cancel while the upload is finishing and inspect both the job record and storage; a reassuring screen is not proof that the artifact stayed private.
Revoke access during generation, attempt a status lookup from another tenant and redrive a failed job through the wrong deployment. Verify refusal at the service boundary. Change source rows between export pages to check the declared consistency policy.
Finally, open the resulting file in its intended consumer. The queue can deliver a perfectly reliable file that still corrupts identifiers or mishandles Arabic text. SultanByte's Arabic CSV export guide covers that separate output contract.
Product owners should approve what "ready", "cancelled" and "expired" mean. Engineers should prove those meanings under retries and concurrent changes before estimating a completion percentage for the interface.
About the author
- I have lead the Engineering for multiple startups in UAE. I also have my own agency qualascend.com.
Comments
Join the discussionNo comments yet. Be the first to comment.
Builder Guides
Practical architecture, engineering, security and implementation guides for teams building technology in MENA.




