Skip to main content

Soft deletion for MENA SaaS: restore without surprises

A practical lifecycle for Arabic-English SaaS: recovery permissions, identifier reuse, purge races and backup reconciliation.

Updated 9 min readMarkdown
Part of seriesBuilder Guides
Soft deletion for MENA SaaS: restore without surprises

A customer administrator deletes a supplier, then asks to bring it back. The row still exists, so the restore looks easy. Meanwhile, someone has reused the supplier code, an export still contains the old details and a background worker is about to send a notification.

A deleted_at column does not answer any of those questions. Soft deletion needs a lifecycle contract: what disappears, what remains usable, who can restore it and when recovery stops being possible.

This guide proposes that contract for ordinary reference records in Arabic-English SaaS products serving MENA customers. A Saudi workspace and a UAE workspace are illustrative, separate tenant configurations. The design is not a retention rule for either country, and it should not be applied unchanged to invoices, identity evidence or regulated records. This is practical engineering information, not legal advice.

Cover: original SultanByte artwork showing the separate paths from an active record to restricted recovery or permanent removal.

Decide what the Delete button promises

Use four explicit states in the proposed design: active, recoverable, purge pending and purged. A recoverable record is unavailable to ordinary business operations but remains available through a separately authorised recovery flow. Purge pending means recovery has closed and removal work has begun. Purged means the defined removal scope has completed, not that every historical copy everywhere has vanished.

Write that scope before choosing the interface label. A product might remove a supplier from new purchase requests while preserving existing order references. Another might require all open work to be reassigned first. Neither behaviour follows automatically from adding a timestamp.

HTTP does not fill this gap. RFC 9110's DELETE semantics describe removing the association between a resource and its current functionality; they do not require destruction of every underlying representation or reclamation of storage. An API can implement a reversible delete, but its documentation must explain the recovery contract.

For this proposed product, the confirmation should say that the supplier will leave active selection lists and whether recovery is available. Give permanent removal a different action and permission. Avoid promising “deleted forever” when the implementation only hides a database row.

Treat recovery access as a separate permission

Apply the active-state filter to ordinary lists, detail routes, autocomplete, exports and worker queries. An ORM default scope can help, but review every path that bypasses it, including raw SQL and administrative tools.

Keep visibility and authority separate. OWASP's authorisation guidance calls for permission checks on every request. In this design, knowing a deleted record's identifier does not grant access to its contents, and permission to edit active records does not automatically grant recovery access.

Scope recovery queries to the authenticated tenant. A supervisor who works across Saudi and UAE customer workspaces must choose the relevant workspace; the Arabic or English interface does not decide which records they may recover. Recheck permission when the restore executes, not only when the recovery screen opens.

Review existing download paths too. Hiding the parent record is not a complete revocation strategy for an attachment already exposed through a separate delivery path. SultanByte's private-download guide covers that boundary. Define whether deleting the supplier should also revoke document access, or whether authorised historical-order viewers should retain it.

Choose whether identifiers can be reused

Suppose supplier code SUP-204 belonged to a now-recoverable record. Can someone create another active supplier with that code? There are two defensible policies, with different recovery costs.

Reserving the code across active and recoverable records makes restoration simpler, but users may need to recover or resolve the old record before using the code again. Allowing reuse makes active operations more flexible, but restoration can conflict with the replacement.

PostgreSQL 18 supports partial unique indexes that enforce uniqueness only among rows satisfying the index predicate. An active-only index on tenant and supplier code is one possible implementation of the reuse policy. It is a product decision expressed as a constraint, not a universally better schema.

If a replacement holds the code, keep the old record recoverable and return a reviewable conflict. Do not rename it silently, merge the suppliers or delete the replacement to make restoration pass. Let an authorised user choose a permitted resolution, then validate it again when committing.

Keep stable record identity separate from a reusable business code. PostgreSQL's foreign-key documentation requires a suitable primary key, unique constraint or non-partial unique index on the referenced columns. An active-only partial unique index is not that reference target. Historical orders should point to the stable supplier identity, with tenant-safe relationships, rather than start referring to a new supplier merely because its code matches.

Restore a valid record, not an old snapshot of authority

Before restoring, check the current tenant, permission, record state, recovery deadline and relevant dependencies. Revalidate business rules that may have changed while the record was hidden. A restored supplier should not automatically regain an expired approval or reactivate a disabled integration.

Make the transition atomic with the checks that protect it. A preliminary “code is available” query is not enough if another request can claim the code before the restore commits. Let the database enforce uniqueness, and translate a rejected write into a safe conflict message. Avoid exposing another tenant's record through that explanation.

Relationships need their own policy. PostgreSQL documents ON DELETE CASCADE as acting when a referenced row is deleted. Updating a deleted_at field is not that operation. It will not automatically hide child contacts, cancel queued jobs or resolve open purchase requests.

For each dependent record, choose whether to preserve it as history, block the parent transition, retire it explicitly or detach it where the model permits. Track which children changed because of this deletion if restoration is meant to reverse those changes. Restoring every hidden child indiscriminately can revive a contact that was removed for a different reason.

Store a transition record with the actor, tenant, stable record ID, prior and resulting state, and operation identifier. Keep the payload minimal. This receipt should explain the action without copying the entire supplier record into an indefinitely retained audit log.

A proposed soft-deletion decision framework: active records can become recoverable; restoration requires current permission, valid dependencies and an available identifier; purge pending closes recovery; backup copies follow a separate lifecycle.

Original infographic: SultanByte. Proposed lifecycle informed by PostgreSQL 18, OWASP, IETF HTTP semantics and AWS Backup documentation, November 2026. It is an engineering design, not a statutory retention schedule.

Make restore and purge compete at one boundary

A cleanup job must not select an expired record, pause, and later purge it after a user has successfully restored it. Give restore and purge a shared concurrency boundary.

For the proposed design, the worker atomically claims an eligible recoverable record by moving it to purge pending. The claim checks the current recovery deadline, applicable hold state and record version. Restore uses the same protected state transition. Once purge pending commits, ordinary restoration is unavailable. If restore wins first, the purge claim must fail.

Use a transaction or another deliberate concurrency strategy for related mutable policy checks. A version on the supplier row alone does not protect a hold stored in an unrelated table. Define what happens if a new hold arrives after removal has started; do not pretend that already completed external deletion can always be reversed.

Then perform bounded, retryable removal across the declared stores. Keep durable progress for database records, attachments and derived search entries, with an explicit policy for external processors. A failed attachment removal should remain visible as unfinished work rather than being swallowed behind a successful database transaction.

Maintain enough tenant-scoped tombstone information to reject late jobs that would recreate the deleted entity. Use stable IDs and versioned events; a delayed “supplier updated” message must not overwrite a newer deletion state. Retain that prevention record only for a justified period and with minimal data, because a tombstone is itself retained information.

Put backup recovery in the same product discussion

An operational purge and a backup's lifecycle are separate. AWS documents that Backup Vault Lock in compliance mode, after its grace period, prevents alteration or deletion of the lock; protected backups cannot simply be removed before their lifecycle completes. Governance mode has a different permission model. These are provider controls, not evidence that a particular customer's retention obligations have been met.

Before promising a removal deadline, inventory backup copies, restore environments, exported files and downstream systems. Record what can be removed immediately, what expires under a separate policy and who can access those retained copies. Assess each customer deployment separately rather than inventing a single MENA retention period.

Plan for a disaster restore that brings back records removed after the backup was taken. In this proposed runbook, restore into an isolated environment, reconcile against a protected deletion ledger that survives independently of the restored snapshot, and apply the required removals before reopening ordinary access. If that ledger cannot be recovered, the team cannot honestly claim that the restored system has preserved later deletion decisions.

A production-row count alone is therefore a poor completion signal. The outcome record should state the verified scope and outstanding exceptions. Avoid an unqualified “all data erased” message when backups or approved historical records remain.

Make recovery understandable in both languages

Use clear, distinct Arabic and English labels for removal from active use, restoration and permanent-removal processing. Show the affected record and workspace, the recovery deadline where applicable, and the reason restoration is blocked. Keep business codes visually distinguishable from translated names.

Provide a persistent result screen when cleanup continues after the request. W3C's status-message guidance explains how result and progress messages can be exposed to assistive technology without moving focus. Test the actual recovery interface with keyboard and screen-reader use; a brief colour change is not enough feedback for this workflow.

Before rollout, exercise these proposed acceptance cases with synthetic records:

  • Delete a supplier, then request it through a direct URL, export and background job. Require the intended access policy on every path.
  • Reuse its code, then restore it. Require a conflict without altering the replacement.
  • Race restore against the purge claim. Require one valid transition, with no restored record removed by stale work.
  • Delete a parent whose children have different histories. Require the documented dependency policy rather than a blanket cascade.
  • Lose a purge response and retry. Require recorded progress to prevent inconsistent repeated work.
  • Restore an older backup. Require later deletion decisions to be reconciled before ordinary access resumes.

The product owner should approve the identifier-reuse and recovery policies. Engineering should demonstrate the race and recovery cases against stored state, not just screenshots. Only then does “Restore” describe an operation the system can safely deliver.

About the author

  • I have lead the Engineering for multiple startups in UAE. I also have my own agency qualascend.com.

No comments yet. Be the first to comment.