How I noticed the duplicate listener
After registering the custom event, I checked the list of events and listeners:
php artisan event:listThe custom event was connected correctly:
App\Modules\Auth\Domain\Event\UserRegistered└── App\Modules\Notification\Application\Listener\ DispatchNotificationsAfterUserRegistrationListenerBut a little further down I noticed three identical listeners for Laravel’s built-in Registered event:
Illuminate\Auth\Events\Registered├── Illuminate\Auth\Listeners\SendEmailVerificationNotification├── Illuminate\Auth\Listeners\SendEmailVerificationNotification└── Illuminate\Auth\Listeners\SendEmailVerificationNotificationThe first suspect: event cache
My first thought was stale event cache. I cleared the event cache and all bootstrap caches:
php artisan event:clearphp artisan optimize:clear php artisan event:list \ | grep -A 4 "Illuminate\\\\Auth\\\\Events\\\\Registered"But the result did not change. SendEmailVerificationNotification was still registered three times.
That meant the issue was happening during application bootstrapping, not because of stale cache.
Where the duplicate registration came from
In bootstrap/providers.php I had two module event providers registered:
return [ App\Providers\AppServiceProvider::class, App\Modules\Media\Provider\MediaEventProvider::class, App\Modules\Notification\Provider\NotificationEventServiceProvider::class,];Both classes extended Laravel’s standard EventServiceProvider:
use Illuminate\Foundation\Support\Providers\EventServiceProvider; final class MediaEventProvider extends EventServiceProvider{ protected $listen = [ // Media events... ];}use Illuminate\Foundation\Support\Providers\EventServiceProvider; final class NotificationEventServiceProvider extends EventServiceProvider{ protected $listen = [ UserRegistered::class => [ DispatchNotificationsAfterUserRegistrationListener::class, ], ];}Why the email verification listener was duplicated
The issue was not in the $listen arrays.
Each module provider inherited additional framework behavior from Laravel’s EventServiceProvider. While registering such a provider, Laravel also configured the default email verification listener.
Simplified, that behavior looked like this:
Registered::class => SendEmailVerificationNotification::class;As a result, the listener was registered:
by Laravel’s default configuration;
when MediaEventProvider was booted;
when NotificationEventServiceProvider was booted.
That is exactly why event:list showed three identical entries.
A separate EventServiceProvider for modules
<?php declare(strict_types=1); namespace App\Modules\Core\Provider; use Illuminate\Foundation\Support\Providers\EventServiceProvider; abstract class ModuleEventServiceProvider extends EventServiceProvider{ /** * Module providers must not register Laravel's * global email verification listener. */ protected function configureEmailVerification(): void { // Intentionally left blank. }}After that, the module event providers started extending the new base class.
use App\Modules\Core\Provider\ModuleEventServiceProvider; final class MediaEventProvider extends ModuleEventServiceProvider{ protected $listen = [ MediaFolderItemsChangedEvent::class => [ SyncMediaFolderItemsCountListener::class, ], MediaUsageChangedEvent::class => [ SyncMediaUsageCountListener::class, ], ];}use App\Modules\Core\Provider\ModuleEventServiceProvider; final class NotificationEventServiceProvider extends ModuleEventServiceProvider{ protected $listen = [ UserRegistered::class => [ DispatchNotificationsAfterUserRegistrationListener::class, ], ];}Before and after

After changing the inheritance, I cleared the caches once again and checked the event list:
php artisan optimize:clear php artisan event:list \ | grep -A 4 "Illuminate\\\\Auth\\\\Events\\\\Registered"Do all service providers need to be changed?
No. The issue affected only module classes that extended Laravel’s EventServiceProvider.
A regular ServiceProvider does not automatically configure email verification, so those providers do not need to be changed.
use Illuminate\Support\ServiceProvider; final class NotificationServiceProvider extends ServiceProvider{ public function boot(): void { Notification::observe(NotificationObserver::class); }}Takeaway
This was one of those cases where a simple diagnostic command turned out to be more useful than a long search through the codebase. I was checking the new UserRegistered listener, and ended up finding the reason why Laravel’s default email verification flow could potentially run more than once.



