Cleaning Up Test Tenants and Entra Identities
Status: Living reference, started 2026-08-09. Feeds TRA-377 (the not-yet-built automated stale-tenant cleanup process) — this document covers the manual version of that same problem.
The core thing to understand
Deleting a Tenants/TenantUsers row in the database and deleting the corresponding Entra identity are two completely separate actions. A database delete does nothing to Entra. An orphaned Entra identity left behind after a database cleanup is exactly the vulnerability TRA-375 was built to catch — the database says "nothing here," but Entra still remembers the account, and a fresh registration attempt with that same email can either get silently attached to it (the original bug) or correctly rejected (the fix, confirmed working 2026-08-08).
Why an email can't be reused even after the database row is gone
InviteUserAsync (the guest-creation path registration uses) checks Entra's own directory, not RoundTrip's database, before inviting someone. If that email already has any Entra identity — guest or member — the invite fails or silently resolves to the existing one, regardless of whether any TenantUsers row exists anywhere. Confirmed real-world cases: support@traxsgroup.com, pete.carroll@traxsgroup.com, hello@traxsgroup.com — all pre-existing Microsoft 365 member accounts, all blocked from registration for this reason (see TRA-384).
Actually freeing up an email — the real, confirmed steps
Deleting a user in the Entra admin center is a soft delete by default — the account moves to a suspended "Deleted users" state for 30 days before Microsoft automatically purges it. Whether the email is immediately reusable during that window is genuinely unclear from Microsoft's own documentation — don't assume it is.
The reliable path:
- Entra admin center → Users → delete the account normally
- Users → Deleted users → find the same account → Delete permanently
Step 2 is the one that actually guarantees the identifier is free — a real, documented hard-delete action, not a workaround.
Why this is safe to do manually, but wasn't built into automated cleanup
TRA-375/377's DeactivateUserAsync deliberately only disables accounts (accountEnabled: false), never deletes — that decision was specifically about what unsupervised, automated cleanup code should be trusted to do, where a bad staleness criterion could silently disable the wrong account with nobody watching. A human reviewing and deleting a specific, known test account directly in the portal is a different, lower-risk situation — exactly the kind of manual escape hatch that design intentionally left available outside application code.
What deleting an Entra identity does not affect
roundtripapp.onmicrosoft.com is RoundTrip's own separate Entra External ID (CIAM) tenant. A "Member" account created via CreateMemberUserAsync is a self-contained login identity that happens to use a real email address as its sign-in identifier — it is not the actual mailbox. Deleting it has no effect on real email delivery to that address, wherever that's actually hosted.
Before deleting anything — check what's still legitimately in use
Cross-reference against current Tenants/TenantUsers data before assuming an Entra identity is orphaned. Real categories to watch for:
- Still-active team members on a kept tenant — e.g., a technician invited onto a tenant you're intentionally keeping. Deleting their Entra identity breaks their real login.
- Real business-function accounts — e.g., an address also used for live support routing (Crisp), even if it also happens to be blocked from RoundTrip registration testing. No benefit to deleting, some risk.
- Federated identities (
signInType: federated,issuer: ExternalAzureAD) — behave differently than local CIAM accounts; worth understanding before touching, not assumed to be the same as a throwaway test account. - Ambiguous account types — a Member-type account tied to an email that should logically correspond to a Guest-created tenant admin is a signal something doesn't match expectations; worth confirming what it's actually for before deleting, not assuming it's safe.