That’s pretty much the way to do it: iterate over the array and act on each member.
But, taking the entire thing and making it even more elegant, assuming you don’t need to do anything else with the users after this, don’t even pick up the disabled users from AD in the first place:
$users = Get-ADUser -ErrorAction stop -filter {Enabled -eq $true} -SearchBase "OU=VPN Users,OU=Vendor & Visitor Accounts,DC=domain,DC=com"
$letterbody = "The following users are still active:`n"
$users | foreach{$letterbody += "$_`n"}
Also, you don’t really need to use both a return and a newline…just a newline is enough (the `n character).
If you do plan to use the disabled users later, you could do something like this (using the Where() and ForEach() methods instead of the cmdlets (just because they’re fun to use)):
$letterbody = "The following users are still active:`n"
$users.Where({$_.Enabled -eq $true}).ForEach({$letterbody += "$_`n"})
Or, let’s take it a step further and move the $letterbody outside of the foreach loop entirely:
$letterbody = "The following users are still active:"
$letterbody += $users.Where({$_.Enabled -eq $true}).ForEach({"`n$_"})
Note that I had to move the newline character to make that work properly, though that does have the added benefit of not leaving an extra, unneeded newline at the end of the email.
But if we’re going to do that, why not go all the way and set it all in one go? Not quite as readable, but definitely elegant:
$letterbody = "The following users are still active:$($users.Where({$_.Enabled -eq $true}).ForEach({"`n$_"}))"
Anyway, I’m not posting these specifically as recommendations, so much as playing around with different ways to accomplish this, and hope at least one of them is helpful or useful to you.