HomeJournalThis post

Account Deletion UX Is a Product Workflow

Design account deletion as a transparent workflow across verification, grace, data dependencies, billing, retention, and completion evidence.

JP
JP Casabianca
AI Engineer and Product Designer · full-stack delivery · Bogotá

Account deletion UX is complete only when the request, verification, grace period, dependency work, retention, and completion status tell one truthful story. This guide turns that lifecycle into a secure product workflow with a user-facing receipt.

Account deletion UX begins before one red button

Account deletion UX is a product workflow that spans identity, subscriptions, user content, shared work, legal retention, support, and notification. A button is only the request entry point. If the product cannot name the records, owners, exceptions, and completion evidence behind that button, it cannot make a truthful deletion promise.

Start with a dependency inventory. List authentication credentials, profile data, uploaded content, analytics identifiers, billing customer records, active subscriptions, team memberships, shared artifacts, backups, and legally retained evidence. For each system, record whether the action is erase, anonymize, detach, transfer, retain with basis, or defer. The first figure uses distinct shapes for identity, data, and billing so the graph remains legible without color.

Treat this as a designed state machine, not a synchronous cascade from the request handler. The illustrative workflow artifact models request, verification, grace period, execution, blocked retention, completion, and cancellation. It proves idempotent commands and state transitions for a synthetic account. It is not legal advice, a production deletion service, or evidence that any real dataset was erased.

Before committing the flow, walk one realistic account type through the inventory: individual subscriber, workspace owner, invited member, and former customer with invoices. These roles expose different transfer and retention edges. The exercise remains a policy review until every dependency owner confirms its terminal vocabulary and evidence boundary.

Account deletion dependency graphA deletion request connects to identity, user data, shared work, billing, backups, and retention review, each with a different terminal action.DELETErequest IDIdentityrevokeUser dataeraseShared worktransferBillingcancelBackupsexpireRetentionrestrict
One request fans out into dependency-specific actions; completion derives from every required terminal result.
Identity
Revoke sessions and credentials.
User data
Erase owned records.
Shared work
Transfer, detach, or anonymize.
Billing
Cancel subscription before minimization.
Backups
Expire under documented policy.
Retention
Restrict the minimum retained record.

Map policy obligations to visible product states

Apple’s guidance for offering account deletion in an app expects users to be able to initiate deletion and explains that temporary deactivation alone is insufficient. Google Play’s account deletion requirement includes an in-app path and a web resource for accounts created through an app. Account deletion UX should map these distribution requirements to the same backend request identity so support and status do not diverge by channel.

Policy language must become explicit states. “Deletion requested” means the request exists, not that erasure is complete. “Verification required” identifies the next action. “Scheduled” names a grace-period deadline. “Processing” means workers are active. “Blocked” names a retained category and lawful or contractual reason without exposing protected details. “Completed” states what was erased, anonymized, or retained.

Do not hide admin behavior. The teams that can pause, resume, or override a request need permissions that explain both capability and consequence. Pair the workflow with admin permissions humans can understand, and put every intervention in an append-only audit history. The user-facing timeline should expose material status without leaking internal operator identities or security-sensitive notes.

Design a verified request without coercion

Account closure is high impact, so verify authority near the deletion action. Recent reauthentication, a second factor, or a signed email link may be appropriate depending on account risk. Verification should not become a retention trap: requiring a password from a user who only used social login, or forcing a support call for a self-service account, makes the advertised path inaccessible.

Explain consequences before the final confirmation. Name subscription handling, shared workspace effects, transfer options, grace period, irreversible point, retained categories, and how completion will be communicated. Avoid guilt, ambiguous double negatives, or a primary button whose visual label understates the result. Account deletion UX earns trust by making the destructive boundary precise rather than emotionally difficult.

Use a one-time request identifier and an idempotency-key lifecycle contract. Repeated submits with the same key should return the same request, while a genuinely new request after cancellation receives a new identity. Rate-limit verification attempts, expire tokens, and clear stale browser state after failure. The workflow artifact includes duplicate-submit behavior and a mismatched-account hostile case so an idempotency key cannot be replayed across identities.

Make the deletion grace period reversible

A deletion grace period can protect against takeover and accidental loss, but only if its duration and cancellation path are clear. Store the scheduled execution instant, the timezone used for display, and the final reversible moment. Account deletion UX should avoid vague language such as “soon” when a deterministic deadline exists.

During the grace period, decide whether login cancels deletion, merely shows status, or is blocked. Automatic cancellation on any session can be unsafe if an attacker already has access; invisible continuation can surprise the legitimate owner. Prefer an explicit authenticated cancellation command, record it, and invalidate pending worker leases. Confirmation should say that the account remains active, not simply remove the warning banner.

The second figure separates requested, verified, scheduled, executing, blocked, completed, and cancelled with labelled arrows. It also marks which transitions are reversible and which require an operator or policy decision. The synthetic artifact uses a fourteen-day window to exercise deadlines, but that number is illustrative. Product policy, risk, and applicable law determine the real value; the interface must state whatever policy actually runs.

Orchestrate erasure across owned systems

Execution should fan out from one durable request into dependency-specific work items. Each item has a stable identity, action, state, attempt count, evidence reference, and terminal result. Workers claim jobs idempotently and report erased, anonymized, detached, retained, or failed. Account deletion UX reaches completed only when every required item is terminal under the declared policy.

Separate identity shutdown from the data erasure flow. Revoking sessions may happen immediately after the irreversible boundary, while large object stores or backup expiry take longer. Shared content may need ownership transfer or anonymization instead of deletion. Billing may require subscription cancellation before a provider record can be minimized. The product workflow should make these dependencies explicit rather than relying on accidental worker order.

Use audit trails for admin products to preserve commands and outcomes without copying the sensitive content being deleted into logs. Store identifiers, category, policy version, actor class, timestamps, and evidence digest. Never log raw access tokens or deleted payloads “for proof.” A completion record is valuable only if it does not recreate the personal data the workflow was meant to remove.

Request-to-erasure state machineA horizontal sequence moves from requested to verified, scheduled, executing, and completed. Scheduled can return to cancelled. Executing can enter blocked and later return to executing.REQUESTEDVERIFIEDSCHEDULEDEXECUTINGCOMPLETECANCELLED ↩BLOCKED !reversible before execution · retained work must be disclosed
The workflow distinguishes request acceptance, reversible grace, irreversible execution, retention blocks, and completion.
  1. Requested: stable request exists.
  2. Verified: authority check passed.
  3. Scheduled: grace deadline is visible and cancellation remains possible.
  4. Executing: dependency jobs have begun; cancellation is closed.
  5. Blocked: a retained or failed category needs a policy outcome.
  6. Completed: every required dependency is terminal.

Represent retention exceptions honestly

Article 17 of the GDPR describes a right to erasure and enumerates circumstances and exceptions; the official regulation text is the authoritative source for that legal boundary. Product copy should not compress a legal determination into “everything is deleted instantly.” Account deletion UX needs a retained state that names the data category, reason class, access restriction, and review or expiry condition appropriate to counsel-approved policy.

Retention should not preserve an active product profile. Minimize retained records, isolate access, disconnect them from routine product use, and prevent marketing or personalization. The workflow may complete user-facing closure while a narrow record remains under a disclosed exception. That distinction must be represented in both the receipt and internal operations.

When a dependency fails, provide an actionable error without exposing infrastructure. The guide to API error design for humans is useful here: stable code, plain-language status, request identifier, and next safe step. Do not tell the user to repeat deletion if the first command may already be executing. Reconcile by request identity and update the same status timeline.

Run the account deletion workflow fixture

The downloadable Node program models one synthetic account and four dependency jobs. Commands are pure state transitions over a versioned record: request, verify, schedule, cancel, begin, settle a dependency, and finalize. Account deletion UX in the fixture refuses finalize while a job is pending, accepts a disclosed retention result, and returns the same request for a repeated idempotency key.

The test executes the program twice and compares normalized output. It changes one dependency result and requires a different receipt. Boundary coverage exercises the exact scheduled instant and cancellation before execution. Hostile coverage rejects an unknown command, a cross-account idempotency key, finalize-before-terminal, and cancellation after the irreversible boundary. The test recomputes completion from every raw job state instead of trusting a top-level completed label.

All accounts, identifiers, timings, and job outcomes are constructed. The program sends no email, reaches no provider, and deletes no data. Its claim boundary is workflow consistency: state labels, guarded transitions, idempotent submission, and completion derivation. A production system still needs authentication, durable queues, provider adapters, counsel-approved policy, and end-to-end deletion verification.

Runnable artifact — Synthetic workflow consistency; not legal advice, provider execution, or proof of data erasure.

import assert from "node:assert/strict";
import { createHash } from "node:crypto";
const sha=value=>createHash("sha256").update(JSON.stringify(value)).digest("hex");
const account="acct-synthetic-908",key="delete-key-908";
function command(state,event){if(!["request","verify","schedule","cancel","begin","settle","finalize"].includes(event.type))throw new Error("unknown-command");const next=structuredClone(state);if(event.type==="request"){if(event.account!==account)throw new Error("account-mismatch");if(next.idempotencyKey&&next.idempotencyKey!==event.key)throw new Error("new-key-on-active-request");next.idempotencyKey=event.key;next.status="requested"}else if(event.type==="verify"){if(next.status!=="requested")throw new Error("verify-state");next.status="verified"}else if(event.type==="schedule"){if(next.status!=="verified")throw new Error("schedule-state");next.status="scheduled";next.executeAt=event.at+1209600000}else if(event.type==="cancel"){if(next.status!=="scheduled")throw new Error("irreversible");next.status="cancelled"}else if(event.type==="begin"){if(next.status!=="scheduled"||event.at<next.executeAt)throw new Error("not-due");next.status="executing"}else if(event.type==="settle"){if(next.status!=="executing"||!next.jobs[event.job]||!["erased","anonymized","detached","retained","failed"].includes(event.result))throw new Error("invalid-settlement");next.jobs[event.job]=event.result}else{if(next.status!=="executing"||Object.values(next.jobs).some(x=>x==="pending"||x==="failed"))throw new Error("not-terminal");next.status="completed";next.completedAt=event.at}next.history.push({type:event.type,status:next.status});return next}
const initial={requestId:"DL-908",status:"idle",idempotencyKey:null,executeAt:null,completedAt:null,jobs:{identity:"pending",content:"pending",billing:"pending",invoice:"pending"},history:[]};
const base=[{type:"request",account,key},{type:"verify"},{type:"schedule",at:0},{type:"begin",at:1209600000},{type:"settle",job:"identity",result:"erased"},{type:"settle",job:"content",result:"erased"},{type:"settle",job:"billing",result:"detached"},{type:"settle",job:"invoice",result:process.argv.includes("--erase-invoice")?"erased":"retained"},{type:"finalize",at:1209601000}];let state=initial;for(const event of base)state=command(state,event);
const duplicate=command(command(initial,{type:"request",account,key}),{type:"request",account,key});assert.equal(duplicate.history.length,2);const hostile={crossAccount:"",earlyFinalize:"",lateCancel:"",unknown:""};try{command(initial,{type:"request",account:"other",key})}catch(error){hostile.crossAccount=error.message}try{command({...initial,status:"executing"},{type:"finalize",at:1})}catch(error){hostile.earlyFinalize=error.message}try{command(state,{type:"cancel"})}catch(error){hostile.lateCancel=error.message}try{command(initial,{type:"destroy"})}catch(error){hostile.unknown=error.message}
const core={schema:"account-deletion-workflow-receipt-v1",fixture:"synthetic account and dependency jobs",request:{id:state.requestId,status:state.status,executeAt:state.executeAt,completedAt:state.completedAt,jobs:state.jobs,history:state.history},idempotency:{key,duplicateStatus:duplicate.status,sameRequest:duplicate.requestId===state.requestId},hostile,claimBoundary:"Workflow consistency only; no real account, provider call, legal determination, or data erasure."};console.log(JSON.stringify({...core,receiptHash:sha(core)},null,2));console.log("PASS: deletion states, idempotent request, terminal derivation, retention disclosure, hostile transitions, and digest verified");

Give the user a deletion status receipt

The final figure is a user-facing deletion status receipt rather than an internal job dump. It includes request ID, submitted time, grace deadline, current status, affected categories, retained-category disclosure, completion time, and a support-safe reference. Icons and text distinguish complete, retained, pending, and failed states. Account deletion UX should let the person revisit this record without reactivating the account.

Keep one canonical timeline across app, web, email, and support. Notifications link to status; they do not invent a parallel truth. If delivery fails, the request still proceeds according to policy. If a completion message bounces, support can resolve the stable identifier without asking for sensitive deleted content. Preserve the minimal receipt for the stated audit period and explain that retention clearly.

The outcome to optimize is understandable closure: the user knows what will happen, when it becomes irreversible, which shared or billing effects need attention, what finished, and what narrowly remains. Run the public workflow fixture, inspect the blocked-retention branch, and use its state names to challenge every vague sentence in your current deletion screen.

User-facing account deletion receiptA receipt card shows request ID DL-908, completed status, submitted and completed dates, and four category outcomes including one restricted retention.DELETION RECEIPTRequest DL-908COMPLETEDSubmittedSep 8, 2026 · 15:25 UTCIdentity✓ revokedUser content✓ erasedSubscription✓ cancelledInvoice record△ restricted retentionCompletedSep 22, 2026 · 15:25 UTCSupport-safe reference: DL-908 · no deleted payload copied
A deletion receipt communicates category outcomes and narrow retention without recreating the deleted personal data.
Semantic data for this figure
CategoryStatus
IdentityRevoked
User contentErased
SubscriptionCancelled
Invoice recordRestricted retention

Illustrative receipt DL-908: submitted September 8 and completed September 22, 2026 UTC.