There are many SaaS applications that allow potential new customers to try out the service using a trial period. Of course, not everybody will convert. After the trial period is over, some people will not use the service anymore.
However, if nothing is being done about it, their email address and other pieces of (personal) data remain in the database. In this blog post you’ll learn why you should delete this data. I’ll also share how we go about this at Oh Dear.
Why you should remove inactive users and teams #
There are two reasons why you want to delete old users and teams. First, you want to keep your database as small as possible. This has many small benefits. Your queries might execute faster. The backup process will be shorter. Less disk space is needed… Granted, this might be a micro-optimization, as databases are very optimized in performing queries in even large datasets, and the disk space used is probably not too much. Still, the less work a server needs to do, the better.
The second reason is more important: you want to keep only as little personal data as needed for privacy reasons. You should only collect and keep the absolute minimum of data needed to run your application. This keeps your user’s privacy safe and minimizes the risks for you as a company if a security breach happens.
If people, a few months after a trial period, didn’t subscribe, then it’s unlikely that they’ll ever get a subscription. You don’t need their data anymore.
How we take care of deleting users and teams in Oh Dear #
Oh Dear is the SaaS application where my buddy Mattias and I are working on. Our service has a trial that allows you to test out all the functionality. Since launch, a lot of people tried it. We’re lucky that many people did subscribe, but there are a lot of people who didn’t. I guess this is normal for any SaaS.
We’ve never cleaned up the old users and teams, and reading about privacy pushed me to take care of this. I didn’t want this cleanup process to be a one time action but a continuous process.
Deleting unverified users #
Let’s start with the users. Before new users can even create a team, they should verify their email address. This verification is handled by Laravel. There are a lot of users that never verify their email address. I’m assuming that most of them are bots, together with a couple of people that changed their mind about using our service.
Those unverified users have no way of using the application, so it’s safe to delete them.
Here’s the command that will delete all unverified users ten days after they have been created.
<span class="hljs-keyword">namespace</span> <span class="hljs-title">App</span><span class="hljs-title">Domain</span><span class="hljs-title">Team</span><span class="hljs-title">Commands</span><span class="hljs-title">Cleanup</span>;
<p><span class="hljs-keyword">use</span> <span class="hljs-title">App</span><span class="hljs-title">Domain<span class="hljs-title">Team<span class="hljs-title">Models<span class="hljs-title">User;
<span class="hljs-keyword">use</span> <span class="hljs-title">Illuminate</span><span class="hljs-title">Console<span class="hljs-title">Command;</p>
<p><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">DeleteOldUnverifiedUsers</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">Command</span>
</span>{
<span class="hljs-keyword">protected</span> $signature = <span class="hljs-string">'ohdear:delete-old-unverified-users'</span>;</p>
<pre><code><span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">handle</span><span class="hljs-params">()</span>
</span>{
<span class="hljs-keyword">$this</span>->info(<span class="hljs-string">'Deleting old unverified users...'</span>);
$count = User::query()
->whereNull(<span class="hljs-string">'email_verified_at'</span>)
->where(<span class="hljs-string">'created_at'</span>, <span class="hljs-string">'<'</span>, now()->subDays(<span class="hljs-number">10</span>))
->delete();
<span class="hljs-keyword">$this</span>->comment(<span class="hljs-string">"Deleted {$count} unverified users."</span>);
<span class="hljs-keyword">$this</span>->info(<span class="hljs-string">'All done!'</span>);
}
}
The most straightforward way to test the command above would be to
seed a user that should be deleted,
seed another one that shouldn’t be deleted
call the command above
verify if the command only deleted the right user
Instead of that approach, I prefer scenario-based tests that more closely mimic what happens in real life. In the test below, a user is seeded, and by using the spatie/test-time package, we modify the time.
<span class="hljs-keyword">namespace</span> <span class="hljs-title">Tests</span><span class="hljs-title">Feature<span class="hljs-title">Domain<span class="hljs-title">Team<span class="hljs-title">Cleanup;
<p><span class="hljs-keyword">use</span> <span class="hljs-title">App</span><span class="hljs-title">Domain<span class="hljs-title">Team<span class="hljs-title">Commands<span class="hljs-title">Cleanup<span class="hljs-title">DeleteOldUnverifiedUsers;
<span class="hljs-keyword">use</span> <span class="hljs-title">App</span><span class="hljs-title">Domain<span class="hljs-title">Team<span class="hljs-title">Models<span class="hljs-title">User;
<span class="hljs-keyword">use</span> <span class="hljs-title">Spatie</span><span class="hljs-title">TestTime<span class="hljs-title">TestTime;
<span class="hljs-keyword">use</span> <span class="hljs-title">Tests</span><span class="hljs-title">TestCase;</p>
<p><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">DeleteOldUnverifiedUsersTest</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">TestCase</span>
</span>{
<span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">setUp</span><span class="hljs-params">()</span>: <span class="hljs-title">void</span>
</span>{
<span class="hljs-keyword">parent</span>::setUp();</p>
<pre><code> TestTime::freeze(<span class="hljs-string">'Y-m-d H:i:s'</span>, <span class="hljs-string">'2021-01-01 00:00:01'</span>);
}
<span class="hljs-comment">/** <span class="hljs-doctag">@test</span> */</span>
<span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">it_will_delete_unverified_users_after_some_days</span><span class="hljs-params">()</span>
</span>{
$user = User::factory()->create([
<span class="hljs-string">'email_verified_at'</span> => <span class="hljs-keyword">null</span>,
]);
TestTime::addDays(<span class="hljs-number">10</span>);
<span class="hljs-keyword">$this</span>->artisan(DeleteOldUnverifiedUsers::class);
<span class="hljs-keyword">$this</span>->assertTrue($user->exists());
TestTime::addSecond();
<span class="hljs-keyword">$this</span>->artisan(DeleteOldUnverifiedUsers::class);
<span class="hljs-keyword">$this</span>->assertFalse($user->exists());
}
<span class="hljs-comment">/** <span class="hljs-doctag">@test</span> */</span>
<span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">it_will_not_delete_verified_users</span><span class="hljs-params">()</span>
</span>{
$user = User::factory()->create([
<span class="hljs-string">'email_verified_at'</span> => now(),
]);
TestTime::addDays(<span class="hljs-number">20</span>);
<span class="hljs-keyword">$this</span>->artisan(DeleteOldUnverifiedUsers::class);
<span class="hljs-keyword">$this</span>->assertTrue($user->exists());
}
}
Deleting inactive teams #
We currently consider a team inactive when it has never subscribed, and the trial period ended more than six months ago. Deleting inactive teams is slightly more complicated. If a team exists, that means that a verified user, the team owner, has created and configured it.
Because there is a chance that the team owner might want to reactive the team at some point in the future, we don’t want to delete the team immediately. Instead, we’re going to mail the team owners and give them a chance to cancel the automatic deletion. If no response comes in after a week, we’ll delete the team.
Let’s take a look at the code. On our teams
table, we added two columns.
<span class="hljs-keyword">use</span> <span class="hljs-title">Illuminate</span><span class="hljs-title">Database<span class="hljs-title">Migrations<span class="hljs-title">Migration;
<span class="hljs-keyword">use</span> <span class="hljs-title">Illuminate</span><span class="hljs-title">Database<span class="hljs-title">Schema<span class="hljs-title">Blueprint;
<span class="hljs-keyword">use</span> <span class="hljs-title">Illuminate</span><span class="hljs-title">Support<span class="hljs-title">Facades<span class="hljs-title">Schema;
<p><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">AddDeletionMarkerColumnsToTeamsTable</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">Migration</span>
</span>{
<span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">up</span><span class="hljs-params">()</span>
</span>{
Schema::table(<span class="hljs-string">'teams'</span>, <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-params">(Blueprint $table)</span> </span>{
$table->timestamp(<span class="hljs-string">'deleting_soon_mail_sent_at'</span>)->nullable();
$table->timestamp(<span class="hljs-string">'automatically_delete_at'</span>)->nullable();
});
}
}
</p>
deleting_soon_mail_sent_at
will contain the date-time when we mailed the automatic deletion notice to the team. automatically_delete_at
will contain the date on which the team is scheduled to be deleted.
Here’s the shouldBeMarkedForDeletion
function that was added to the Team
model.
Here’s the command that sends out the automatic deletion notices.
<span class="hljs-keyword">namespace</span> <span class="hljs-title">App</span><span class="hljs-title">Domain<span class="hljs-title">Team<span class="hljs-title">Commands<span class="hljs-title">Cleanup;
<p><span class="hljs-keyword">use</span> <span class="hljs-title">App</span><span class="hljs-title">Domain<span class="hljs-title">Team<span class="hljs-title">Actions<span class="hljs-title">Cleanup<span class="hljs-title">MarkInactiveTeamForDeletionSoonAction;
<span class="hljs-keyword">use</span> <span class="hljs-title">App</span><span class="hljs-title">Domain<span class="hljs-title">Team<span class="hljs-title">Models<span class="hljs-title">Team;
<span class="hljs-keyword">use</span> <span class="hljs-title">Illuminate</span><span class="hljs-title">Console<span class="hljs-title">Command;</p>
<p><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">MarkInactiveTeamsForDeletionCommand</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">Command</span>
</span>{
<span class="hljs-keyword">protected</span> $signature = <span class="hljs-string">'ohdear:mark-inactive-teams-for-deletion'</span>;</p>
<pre><code><span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">handle</span><span class="hljs-params">()</span>
</span>{
<span class="hljs-keyword">$this</span>->info(<span class="hljs-string">'Starting marking inactive teams for deletion'</span>);
$markedAsDeletionCount = <span class="hljs-number">0</span>;
Team::each(<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-params">(Team $team)</span> <span class="hljs-title">use</span> <span class="hljs-params">(&$markedAsDeletionCount)</span> </span>{
<span class="hljs-keyword">if</span> (! $team->shouldBeMarkedForDeletion()) {
<span class="hljs-keyword">return</span>;
}
<span class="hljs-keyword">$this</span>->comment(<span class="hljs-string">"Marking team {$team->name} ({$team->id}) for deletion"</span>);
(<span class="hljs-keyword">new</span> MarkInactiveTeamForDeletionSoonAction())->execute($team);
$markedAsDeletionCount++;
});
<span class="hljs-keyword">$this</span>->info(<span class="hljs-string">"Marked {$markedAsDeletionCount} teams for deletion!"</span>);
<span class="hljs-keyword">$this</span>->info(<span class="hljs-string">'All done!'</span>);
}
}
Here’s the code of the shouldBeMarkedForDeletion
method that will determine whether a team should be mailed an automatic deletion notice.
<span class="hljs-comment">// on the Team model</span>
<p><span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">shouldBeMarkedForDeletion</span><span class="hljs-params">()</span>: <span class="hljs-title">bool</span>
</span>{
<span class="hljs-comment">// if we've already sent the mail we don't want to resend it</span>
<span class="hljs-keyword">if</span> (<span class="hljs-keyword">$this</span>->deleting_soon_mail_sent_at !== <span class="hljs-keyword">null</span>) {
<span class="hljs-keyword">return</span> <span class="hljs-keyword">false</span>;
}</p>
<pre><code><span class="hljs-keyword">if</span> (<span class="hljs-keyword">$this</span>->hasActiveSubscription()) {
<span class="hljs-keyword">return</span> <span class="hljs-keyword">false</span>;
}
<span class="hljs-keyword">if</span> (<span class="hljs-keyword">$this</span>->wasSubScribedAtSomePointInTime()) {
<span class="hljs-keyword">return</span> <span class="hljs-keyword">false</span>;
}
<span class="hljs-keyword">return</span> <span class="hljs-keyword">$this</span>->created_at->addMonths(<span class="hljs-number">6</span>)->isPast();
}
Here’s the code of the MarkInactiveTeamForDeletionSoonAction
used in the command. If you want to know more about Action classes in general, consider picking up our Laravel Beyond CRUD course where this pattern is explained.
<span class="hljs-keyword">namespace</span> <span class="hljs-title">App</span><span class="hljs-title">Domain<span class="hljs-title">Team<span class="hljs-title">Actions<span class="hljs-title">Cleanup;
<p><span class="hljs-keyword">use</span> <span class="hljs-title">App</span><span class="hljs-title">Domain<span class="hljs-title">Team<span class="hljs-title">Mails<span class="hljs-title">TeamMarkedForDeletionMail;
<span class="hljs-keyword">use</span> <span class="hljs-title">App</span><span class="hljs-title">Domain<span class="hljs-title">Team<span class="hljs-title">Models<span class="hljs-title">Team;
<span class="hljs-keyword">use</span> <span class="hljs-title">Illuminate</span><span class="hljs-title">Support<span class="hljs-title">Facades<span class="hljs-title">Mail;</p>
<p><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">MarkInactiveTeamForDeletionSoonAction</span>
</span>{
<span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">execute</span><span class="hljs-params">(Team $team)</span>
</span>{
$team->update([
<span class="hljs-string">'deleting_soon_mail_sent_at'</span> => now(),
<span class="hljs-string">'automatically_delete_at'</span> => now()->addDays(<span class="hljs-number">7</span>),
]);</p>
<pre><code> Mail::to($team->owner->email)->queue(<span class="hljs-keyword">new</span> TeamMarkedForDeletionMail($team));
}
}
Here’s the mail that gets sent to inactive teams.
To cancel the deletion, team owners should subscribe. Let’s take a look at that delete cancellation code.
We have an event listener that executes when a team subscribes. It will set deleting_soon_mail_sent_at
and automatically_delete_at
to null so that the automatic deletion is effectively cancelled.
<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">CancelAutomaticDeletion</span>
</span>{
<span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">handle</span><span class="hljs-params">(SubscriptionCreated $event)</span>
</span>{
<span class="hljs-comment">/** <span class="hljs-doctag">@var</span> AppDomainTeamModelsTeam $team */</span>
$team = $event->billable;
<pre><code> $team->update([
<span class="hljs-string">'deleting_soon_mail_sent_at'</span> => <span class="hljs-keyword">null</span>,
<span class="hljs-string">'automatically_delete_at'</span> => <span class="hljs-keyword">null</span>,
]);
Now let’s take a look at the command that does the actual deletion if team owners don’t subscribe. It’s pretty simple; it only has to consider the value of automatically_delete_at
on a team.
<span class="hljs-keyword">namespace</span> <span class="hljs-title">App</span><span class="hljs-title">Domain<span class="hljs-title">Team<span class="hljs-title">Commands<span class="hljs-title">Cleanup;
<p><span class="hljs-keyword">use</span> <span class="hljs-title">App</span><span class="hljs-title">Domain<span class="hljs-title">Team<span class="hljs-title">Actions<span class="hljs-title">Cleanup<span class="hljs-title">DeleteInactiveTeamAction;
<span class="hljs-keyword">use</span> <span class="hljs-title">App</span><span class="hljs-title">Domain<span class="hljs-title">Team<span class="hljs-title">Models<span class="hljs-title">Team;
<span class="hljs-keyword">use</span> <span class="hljs-title">Illuminate</span><span class="hljs-title">Console<span class="hljs-title">Command;</p>
<p><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">DeleteInactiveTeamsCommand</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">Command</span>
</span>{
<span class="hljs-keyword">protected</span> $signature = <span class="hljs-string">'ohdear:delete-inactive-teams'</span>;</p>
<pre><code><span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">handle</span><span class="hljs-params">()</span>
</span>{
<span class="hljs-keyword">$this</span>->info(<span class="hljs-string">'Start deleting old teams...'</span>);
Team::query()
->where(<span class="hljs-string">'automatically_delete_at'</span>, <span class="hljs-string">'<'</span>, now())
->each(<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-params">(Team $team)</span> </span>{
<span class="hljs-keyword">$this</span>->comment(<span class="hljs-string">"Deleting team {$team->id}"</span>);
(<span class="hljs-keyword">new</span> DeleteInactiveTeamAction())->execute($team);
});
<span class="hljs-keyword">$this</span>->info(<span class="hljs-string">'All done!'</span>);
}
}
Here’s the code of the DeleteInactiveTeamAction
class used in the command above. In the action, we’ll delete the team. We’ll also delete the owner if it has no other teams.
<span class="hljs-keyword">namespace</span> <span class="hljs-title">App</span><span class="hljs-title">Domain<span class="hljs-title">Team<span class="hljs-title">Actions<span class="hljs-title">Cleanup;
<p><span class="hljs-keyword">use</span> <span class="hljs-title">App</span><span class="hljs-title">Domain<span class="hljs-title">Team<span class="hljs-title">Models<span class="hljs-title">Team;</p>
<p><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">DeleteInactiveTeamAction</span>
</span>{
<span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">execute</span><span class="hljs-params">(Team $team)</span>
</span>{
$teamOwner = $team->owner;</p>
<pre><code> $team->delete();
<span class="hljs-comment">/** <span class="hljs-doctag">@var</span> AppDomainTeamModelsUser $teamOwner */</span>
$teamOwner = $teamOwner->refresh();
<span class="hljs-keyword">if</span> ($teamOwner->allTeams()->count() === <span class="hljs-number">0</span>) {
$teamOwner->delete();
}
}
}
Let’s now test that all the code above is working as intended. Again, we’re going to use the TestTime class to create a “scenario” test where we move forward in time.
<span class="hljs-keyword">namespace</span> <span class="hljs-title">Tests</span><span class="hljs-title">Feature<span class="hljs-title">Domain<span class="hljs-title">Team<span class="hljs-title">Cleanup;
<p><span class="hljs-keyword">use</span> <span class="hljs-title">App</span><span class="hljs-title">Domain<span class="hljs-title">Team<span class="hljs-title">Commands<span class="hljs-title">Cleanup<span class="hljs-title">DeleteInactiveTeamsCommand;
<span class="hljs-keyword">use</span> <span class="hljs-title">App</span><span class="hljs-title">Domain<span class="hljs-title">Team<span class="hljs-title">Commands<span class="hljs-title">Cleanup<span class="hljs-title">MarkInactiveTeamsForDeletionCommand;
<span class="hljs-keyword">use</span> <span class="hljs-title">App</span><span class="hljs-title">Domain<span class="hljs-title">Team<span class="hljs-title">Mails<span class="hljs-title">TeamMarkedForDeletionMail;
<span class="hljs-keyword">use</span> <span class="hljs-title">App</span><span class="hljs-title">Domain<span class="hljs-title">Team<span class="hljs-title">Models<span class="hljs-title">Team;
<span class="hljs-keyword">use</span> <span class="hljs-title">Illuminate</span><span class="hljs-title">Support<span class="hljs-title">Facades<span class="hljs-title">Mail;
<span class="hljs-keyword">use</span> <span class="hljs-title">Spatie</span><span class="hljs-title">TestTime<span class="hljs-title">TestTime;
<span class="hljs-keyword">use</span> <span class="hljs-title">Tests</span><span class="hljs-title">Factories<span class="hljs-title">TeamFactory;
<span class="hljs-keyword">use</span> <span class="hljs-title">Tests</span><span class="hljs-title">TestCase;</p>
<p><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">AutomaticTeamDeletionTest</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">TestCase</span>
</span>{
<span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">setUp</span><span class="hljs-params">()</span>: <span class="hljs-title">void</span>
</span>{
<span class="hljs-keyword">parent</span>::setUp();</p>
<pre><code> TestTime::freeze(<span class="hljs-string">'Y-m-d H:i:s'</span>, <span class="hljs-string">'2020-01-01 00:00:00'</span>);
Mail::fake();
}
<span class="hljs-comment">/** <span class="hljs-doctag">@test</span> */</span>
<span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">it_will_delete_a_team_when_it_has_never_subscribed_and_is_older_than_6_months</span><span class="hljs-params">()</span>
</span>{
<span class="hljs-comment">/** <span class="hljs-doctag">@var</span> AppDomainTeamModelsTeam $teamWithoutSubscription */</span>
$teamWithoutSubscription = Team::factory()->create();
TestTime::addMonths(<span class="hljs-number">6</span>);
<span class="hljs-keyword">$this</span>->artisan(MarkInactiveTeamsForDeletionCommand::class);
<span class="hljs-keyword">$this</span>->assertFalse($teamWithoutSubscription->markedForDeletion());
Mail::assertNothingQueued();
TestTime::addSecond();
<span class="hljs-keyword">$this</span>->artisan(MarkInactiveTeamsForDeletionCommand::class);
<span class="hljs-keyword">$this</span>->assertTrue($teamWithoutSubscription->refresh()->markedForDeletion());
Mail::assertQueued(TeamMarkedForDeletionMail::class);
<span class="hljs-keyword">$this</span>->assertEquals(now()->addDays(<span class="hljs-number">7</span>), $teamWithoutSubscription->refresh()->automatically_delete_at);
TestTime::addDays(<span class="hljs-number">7</span>);
<span class="hljs-keyword">$this</span>->artisan(DeleteInactiveTeamsCommand::class);
<span class="hljs-keyword">$this</span>->assertTrue($teamWithoutSubscription->exists());
TestTime::addSecond();
<span class="hljs-keyword">$this</span>->artisan(DeleteInactiveTeamsCommand::class);
<span class="hljs-keyword">$this</span>->assertFalse($teamWithoutSubscription->exists());
}
<span class="hljs-comment">/** <span class="hljs-doctag">@test</span> */</span>
<span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">it_will_not_mark_teams_that_have_subscription_for_deletion</span><span class="hljs-params">()</span>
</span>{
$teamWithSubscription = TeamFactory::createSubscribedTeam();
TestTime::addMonths(<span class="hljs-number">6</span>)->addSecond();
<span class="hljs-keyword">$this</span>->artisan(MarkInactiveTeamsForDeletionCommand::class);
<span class="hljs-keyword">$this</span>->assertFalse($teamWithSubscription->refresh()->markedForDeletion());
Mail::assertNothingQueued();
TestTime::addDays(<span class="hljs-number">7</span>)->addSecond();
<span class="hljs-keyword">$this</span>->artisan(DeleteInactiveTeamsCommand::class);
<span class="hljs-keyword">$this</span>->assertTrue($teamWithSubscription->exists());
}
}
In closing #
I hope you like this little tour of how we delete inactive users and teams at Oh Dear. If you work on a similar application, I highly encourage you to make sure the old user data is being deleted.