Build a Safe SaaS Tenant Offboarding and Data-Retention Workflow on AWS
Quick summary: Delete is a late state. A legal hold stops the machine in HELD. One Scheduler invocation on the retention date sits inside the 14 million free EventBridge Scheduler invocations a month. The first write revokes access. It does not delete objects.
Key Takeaways
- One Scheduler invocation on the retention date sits inside the 14 million free EventBridge Scheduler invocations a month
- On 19 October 2026, the dangerous offboarding is the one that deletes first and looks for the export afterwards
- SES "tenants" are a different product
- They stay on the SES tenant guide
- Walkthrough 1
Table of Contents
On 19 October 2026, the dangerous offboarding is the one that deletes first and looks for the export afterwards. The GDPR guide explains data-subject rights and why backups outlive a delete. This page is the tenant, not one person inside a tenant: the customer is leaving, and the prefix has to follow a state machine.
SES “tenants” are a different product. They stay on the SES tenant guide. Isolation, if you have not picked it, stays on the silo, pool, and bridge post.
Reproduce this — Copy the offboard states. If delete is the first row, the sheet is wrong.
Who this is for
The operator who closes accounts, the compliance partner who asks about legal hold, and the engineer who can disable a user without dropping a bucket.
What you get, and what you do not
You get revoke, a checksummed export, a hold branch, a scheduled delete, and a second approval. You do not get a guarantee that every backup copy is gone. You do not get an undelete.
Opinionated take: two approvals, and hold beats the calendar. The trade-off is a tenant who still occupies storage until the retention date. That storage is cheaper than a deletion you cannot explain.
Architecture
An authorised event starts a Step Functions Standard workflow. The tenant item from the provisioning workflow is the source of status. Cognito AdminDisableUser runs before any export is treated as final, so the tenant cannot write during the copy. A legal-hold flag stops the machine in HELD. Otherwise EventBridge Scheduler gets a one-time schedule at the retention date. The delete function runs only from DELETE_APPROVED.
flowchart LR
request[OffboardRequested] --> revoke[RevokeAccess]
revoke --> export[VerifiedExport]
export --> hold{LegalHold}
hold --> held[Held]
hold --> clock[RetentionSchedule]
clock --> approve[SecondApproval]
approve --> delete[DeletePrefix]Walkthrough
1. Require an approver id on the event. No id, no state change.
2. Revoke. Context: Amazon Cognito AdminDisableUser for the tenant’s user pool. Then revoke refresh tokens if your pool supports the revoke call you have enabled. Write REVOKED only after the disable call succeeds. A failed disable stops the workflow. Do not export a tenant who can still sign in and change data under you.
3. Export and checksum. Copy the tenant prefix to an export bucket the application cannot write. Store the checksum on the item. EXPORTED requires the checksum. The S3 security post is where Object Lock and block-public-access belong. Turn them on for the export bucket before you trust it as evidence.
4. Hold or schedule. If legalHold is true, set HELD and stop. Clearing a hold is a new authorised event, not an edit in the console that the workflow does not see. If there is no hold, create a Scheduler schedule at the retention timestamp with an input that contains the tenant id and nothing else.
5. Second approval, then delete. On the retention date the schedule starts a function that only notifies. A person sends the task token. The delete function reads the item and returns without deleting when status is not DELETE_APPROVED or when legalHold is true. Delete is DeleteObject on keys listed under that tenant prefix, not a bucket delete.
Cancellation before DELETE_APPROVED is a new event that sets ACTIVE and deletes the schedule. It does not recreate objects.
IAM
cognito-idp:AdminDisableUser on the one user pool. s3:GetObject and s3:PutObject on the export prefix. s3:DeleteObject only on the tenant prefix, and only on the delete function’s role. scheduler:CreateSchedule and scheduler:DeleteSchedule on the schedule name prefix offboard-. The delete role must not have s3:DeleteBucket.
Failure and retries
A failed export leaves the tenant REVOKED. That is safer than ACTIVE. The customer cannot write, and you still have the source objects. Retry the copy. Do not skip to delete to “clean up” a failed export.
The delete function is idempotent: a missing key is success. A second run after DELETED returns success and deletes nothing else.
Cost, as an assumption
One one-time Scheduler invocation per closed tenant. The published Scheduler example includes 14 million invocations a month free, then $1.00 per million. The invocation is not the cost. The export copy and the days you keep it are S3 storage and requests. Price those on the S3 pricing page for the Region and storage class you pick. Not a measured saving.
Testing
Run a tenant fixture with no hold. Expect disable, an export object, a schedule, and no deleted source key before the token. Set legalHold and expect HELD with the source still present. Call the delete function directly with status EXPORTED. Expect no deletion.
What broke — Lab, 19 October 2026. Not a customer account. The schedule’s target role could delete any key in the bucket, and a bad tenant id in the input matched an empty prefix that the code treated as “delete all”. Detection: the test aborted because the function’s guard saw a prefix shorter than the tenant id pattern. Recovery: refuse a prefix that does not contain a
ten_id, and scope the role totenants/*. Lesson: an empty prefix is a bucket wipe.
Production checklist
- Approver required at request and again before delete.
- Legal hold cannot be cleared by the delete function.
- Export checksum is present before the schedule exists.
- Delete role cannot delete the bucket or an unscoped prefix.
- Token revoke is explicit, not assumed from disable.
- Backup retention is documented in the GDPR guide, not ignored.
What This Post Doesn’t Cover
Data-subject access requests for a single user. SES tenant teardown. How long a regulation tells you to keep a record. That number comes from counsel, then you type it on the sheet.
What to Do This Week
- Pick one non-production tenant and walk the sheet until
EXPORTED. - Prove the delete function no-ops when the status is wrong.
- Only then attach a schedule.
Closing a tenant cleanly is part of managed services when the account is already in production and the gap is the state machine.
Frequently asked questions
When should we not run this offboarding workflow?
What could go wrong if a lifecycle rule deletes the prefix on the same day?
Can we undelete after DELETE_APPROVED?
Does disabling a Cognito user end every session?

AWS Cloud Architect & AI Expert
AWS-certified cloud architect and AI expert with deep expertise in cloud migrations, cost optimization, and generative AI on AWS.



