The export that dies at thirty seconds
An export built into a web request works until the data outgrows the timeout. Then the button does nothing and there is no record that anything was attempted.
Take your data out, or close your account, and it finishes.
Anyone signed in can ask for an export of their own record, memberships, organizations, projects and authored documents, or ask for their account to be deleted. Both run as background jobs with retries, so a restart cannot eat the request, and both show states you can watch instead of a spinner and hope.
1
transaction to accept a request
5
retries per background job
24h
before a download link expires
Without a shared foundation, every team rebuilds these edge cases, differently.
An export built into a web request works until the data outgrows the timeout. Then the button does nothing and there is no record that anything was attempted.
Deletion touches memberships, the teammate row and the sign-in provider. A crash part-way leaves an account that is neither active nor gone, which is worse than either.
When a request gives no feedback, every one of them turns into a question to someone. Visible states answer it without a conversation.
The mechanism end to end, as implemented in the repository.
The request is accepted safely
The server confirms who is asking from the session, then commits the request row, a visible process run and the job trigger in one idempotent transaction.
A job picks it up
The job is enqueued right after the commit, with a scheduled reconciler as a backstop, so the page never waits on the queue.
The work runs with visible progress
An export collects the account, memberships, organizations, projects and authored documents into one schema-versioned artifact in storage, updating states as it goes.
Delivery closes the loop
An email carries a download link that expires after 24 hours. Deletion removes memberships, anonymizes the teammate row and deletes the sign-in account, tolerating an account that is already gone.
Interactive example
This walkthrough demonstrates the implemented backend contract without using an account, credentials, or provider calls.
Interactive example
Step 1 of 4: Request accepted
Deterministic sample data only. No account, provider, or network connection is used by the interaction.
Highlights
Accepting a request commits the request, its visible progress run and its job trigger together. Clicking twice still produces one request.
Export and deletion run with up to five attempts, and each retry re-locks the row and checks whether the request already finished before redoing anything.
Queued, processing and completed are real states on a process run, not a guess drawn by the page.
Engineering guarantees
This is working machinery, not legal sign-off. Which categories of data we hold, how long we keep them, and the wording we give people still need a human review against our own obligations.
The boundary above is code, not a claim. These files carry the contract:
src/features/privacy/server.tssrc/features/privacy/jobs.tssrc/registries/data-exports.tsThese capabilities share contracts and boundaries with this one.
The docs describe the same contracts this page demonstrates.