There are times when you’d like to use sendmail instead of services like Mailgun, given their pricing.
For example, when ordering a VPS at TransIP, they will give you 1000 free mail sends at the moment of writing.
Unfortunately, Laravel’s fallback doesn’t support it when Postfix is down. That is why I + GPT wrote this custom mailer.
Creata a file named App\Mail\ConditionalTransport.php:
php
<?php
namespace App\Mail;
use Illuminate\Mail\MailManager;
use Illuminate\Support\Facades\Cache;
use Symfony\Component\Mailer\SentMessage;
use Symfony\Component\Mailer\Transport\AbstractTransport;
class ConditionalTransport extends AbstractTransport
{
protected $sendmailTransport;
protected $mailgunTransport;
protected $cacheKey;
/**
* Create a new Conditional transport instance.
*/
public function __construct(MailManager $mailManager)
{
parent::__construct();
$this->cacheKey = 'email_sendmail_count_' . date('Y-m-d');
// Create the sendmail transport
$this->sendmailTransport = $mailManager->createSymfonyTransport(['transport' => 'sendmail']);
// Create the mailgun transport
$this->mailgunTransport = $mailManager->createSymfonyTransport(['transport' => 'mailgun']);
}
/**
* Send the given message.
*
* @param \Symfony\Component\Mailer\SentMessage $message
*/
protected function doSend(SentMessage $message): void
{
// Check if Postfix service is running
if ($this->isPostfixRunning()) {
// Get the current count from cache
$count = Cache::get($this->cacheKey, 0);
if ($count < 1000) {
// Send with Sendmail
$this->sendmailTransport->send($message->getOriginalMessage(), $message->getEnvelope());
// Increment the count
Cache::increment($this->cacheKey);
return;
}
}
// If Postfix is not running or count >= 2000, send with Mailgun
$this->mailgunTransport->send($message->getOriginalMessage(), $message->getEnvelope());
}
/**
* Check if Postfix service is running.
*
* @return bool
*/
protected function isPostfixRunning(): bool
{
$isDisabled = in_array('exec', explode(',', ini_get('disable_functions')));
if ($isDisabled) {
// If exec is disabled, we can't check; assume it's not running
return false;
}
try {
$output = [];
$returnVar = null;
exec('systemctl is-active postfix', $output, $returnVar);
if (isset($output[0]) && trim($output[0]) === 'active') {
return true;
}
} catch (\Exception $e) {
// Handle exception or log error if necessary
}
return false;
}
/**
* Get the string representation of the transport.
*/
public function __toString(): string
{
return 'conditional';
}
}
Next, register your custom transport within the boot method of your application’s App\Providers\AppServiceProvider
service provider:
php
use App\Mail\ConditionalTransport;
use Illuminate\Mail\MailManager;
use Illuminate\Support\Facades\Mail;
/**
* Bootstrap any application services.
*/
public function boot(): void
{
Mail::extend('conditional', function (array $config = []) {
return new ConditionalTransport(app(MailManager::class));
});
}
Then, add a new mailer definition to your application’s config/mail.php
configuration file that utilizes the new transport:
php
'mailers' => [
// Other mailers...
'conditional' => [
'transport' => 'conditional',
],
],
With this setup, your Laravel application will use the sendmail driver to send emails if the Postfix service is running and the daily limit hasn’t been reached. Once the limit is exceeded or if Postfix is not running, it will automatically switch to the Mailgun driver.
Notes:
- The
isPostfixRunning
method checks if the Postfix service is running by using theexec
function to callsystemctl is-active postfix
. Adjust this method if your environment uses a different way to manage services or if exec is disabled. - The email count is cached using Laravel’s Cache facade with a key that includes the current date (using the Y-m-d format).
- Make sure your sendmail and mailgun configurations are correctly set up in your
config/mail.php
file.
This implementation adheres to Laravel’s documentation on custom transports and should fit seamlessly into your application.
It selects Mailgun as an alternative, but you can also specify other drivers if you’d like.
It currently has a limit of 1000 sends per day, but change it to your own desired frequency.
Tested with Laravel 10. Use at your own risk, of course :).