Three thousand welcome emails. A colleague ran a user import on one project last month: loop over a CSV, $user->save(), go home. Next morning we found the script had also warmed the search index three thousand times and invalidated cache after every row. Nobody wrote that in the import script. The observers did.
Laravel 5.8 came out yesterday, and reading the changelog brought that evening back, so here is the note.
The real cost of model events is readability. You look at $user->save() and see one line. The actual control flow is this line plus every observer registered somewhere in a service provider, plus whatever those observers trigger. To know what save() does, you grep the whole project. The code lies about its own price.
I measured out of curiosity. One save() on that model: eleven SQL queries, two HTTP calls to the search service, one mail dispatched. From one line that looks like a single UPDATE.
My rule after that evening. Model events may only touch the model itself. Fill a slug, normalize a phone number, set a UUID. Things where the model is both the cause and the subject. Everything that reaches outside the row (mail, indexes, other aggregates, cache) goes through an explicit application event:
event(new UserRegistered($user));
The difference looks cosmetic. It is not. UserRegistered is fired from a specific place in a specific use case. Registration fires it. Import does not. Admin edit does not. With observers you get no such choice, every save() is every save, and you end up with unsetEventDispatcher() or a static $importMode flag. A switch like that says the side effects live in the wrong place.
Observers are comfortable. So was the import script.