I had to configure the mail on Maravel.
The Lumen usual path would had been:
// bootstrap/app.php
$app->configure('mail');
$app->alias('mail.manager', Illuminate\Mail\MailManager::class);
$app->alias('mail.manager', Illuminate\Contracts\Mail\Factory::class);
$app->alias('mailer', Illuminate\Mail\Mailer::class);
$app->alias('mailer', Illuminate\Contracts\Mail\Mailer::class);
$app->alias('mailer', Illuminate\Contracts\Mail\MailQueue::class);
$app->register(Illuminate\Mail\MailServiceProvider::class);
But this will execute those on each request even if the request will not send any email.
This is where Lumen’s architecture shines because it considers all of its internal service providers as deferred, without them needing to implement \Illuminate\Contracts\Support\DeferrableProvider interface.
The make method from Lumen Application looks in $availableBindings and registers the needed provider/s before resolving from container:
public function make($abstract, array $parameters = [])
{
$abstract = $this->getAlias($abstract);
if (
!$this->bound($abstract) &&
array_key_exists($abstract, $this->availableBindings) &&
!array_key_exists($this->availableBindings[$abstract], $this->ranServiceBinders)
) {
$this->{$method = $this->availableBindings[$abstract]}();
$this->ranServiceBinders[$method] = true;
}
return parent::make($abstract, $parameters);
}
In Maravel, the Lumen Application is extended by \App\Application. To add your bindings without paying a runtime performance penalty, you should redefine the static arrays. (Note: We deliberately copy the core bindings from the parent class rather than using array_merge() in the constructor. In a micro-framework, static property definition is cached by OPcache and costs zero CPU cycles, whereas merging arrays at runtime slows down every single request.)
use Illuminate\Mail\MailServiceProvider;
public $availableBindings = [
// copy from parent
'mailer' => 'registerMailBindings',
'mail.manager' => 'registerMailBindings',
];
protected function registerContainerAliases(): void
{
$this->aliases = [
// copy from parent
\Illuminate\Mail\MailManager::class => 'mail.manager',
\Illuminate\Contracts\Mail\Factory::class => 'mail.manager',
\Illuminate\Mail\Mailer::class => 'mailer',
\Illuminate\Contracts\Mail\Mailer::class => 'mailer',
\Illuminate\Contracts\Mail\MailQueue::class => 'mailer',
];
}
protected function registerMailBindings(): void
{
$this->singleton('mail.manager', static function (self $app) {
return $app->loadComponent('mail', MailServiceProvider::class, 'mail.manager');
});
$this->bind('mailer', static function (self $app) {
return $app->loadComponent('mail', MailServiceProvider::class, 'mailer');
});
}
In this way your mail provider will not be booted on each request keeping the boot time small.
This comes built-in (commented out) in Maravel 10.52.42, so if you need it you just have to uncomment a few lines of code from \App\Application and composer.json exclude-from-classmap:
"vendor/macropay-solutions/maravel-framework/illuminate/Mail/",
Then, regenerate your autoloader:
composer dump-autoload -o
The Bottom Line: If your application only needs a functionality sometimes, you should never register its provider globally. By using Maravel’s built-in deferred bindings, your mail provider will sleep quietly until the exact moment an email is actually sent, keeping your boot times blazing fast.
NOTE
Maravel auto-caches the configs on post-autoload-dump composer event so you have to configure all files in bootstrap/app.php. If you forget, the mail config will NOT be loaded when needed.
if (!$app->configurationIsCached()) {
$app->configure('app');
$app->configure('laravel_crud_wizard');
$app->configure('mail');
}
Update 2026.03.14
Version 10.52.43 of Maravel that requires Maravel Framework 10.66.3 contains a faster version of how to register a deferred provider:
protected function registerMailBindings(): void
{
$this->configure('mail');
$this->register(MailServiceProvider::class);
}

Top comments (0)