Skip to content

Instantly share code, notes, and snippets.

@noogen
Created January 30, 2024 16:54
Show Gist options
  • Save noogen/bb57f10d62c1901559c751cafdbbd4b8 to your computer and use it in GitHub Desktop.
Save noogen/bb57f10d62c1901559c751cafdbbd4b8 to your computer and use it in GitHub Desktop.
Laravel CPanel bounce email handler as a artisan command
<?php
namespace App\Console\Commands;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\Http;
class CpanelEmailCallback extends Command
{
/**
* The console command description.
*
* @var string
*/
protected $description = 'Cpanel Email Callback';
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'cpanel_email_callback {path}';
/**
* Create a new command instance.
*
* @return void
*/
public function __construct()
{
parent::__construct();
}
/**
* Execute the console command.
*
* @return mixed
*/
public function handle()
{
$path = $this->argument('path');
$sep = DIRECTORY_SEPARATOR;
$dir = $sep.trim(base_path($path), $sep);
// example: php artisan cpanel_email_callback tests/_data/bounces
// example: php artisan cpanel_email_callback ../mail/mailer-domain.email
$this->processDir("{$dir}{$sep}*");
$this->processDir("{$dir}{$sep}cur{$sep}*");
$this->processDir("{$dir}{$sep}new{$sep}*");
$this->processDir("{$dir}{$sep}.spam{$sep}cur{$sep}*");
$this->processDir("{$dir}{$sep}.spam{$sep}new{$sep}*");
$this->processDir("{$dir}{$sep}.Junk{$sep}cur{$sep}*");
$this->processDir("{$dir}{$sep}.Junk{$sep}new{$sep}*");
}
public function processDir($dir)
{
\Log::info("Processing " . $dir);
if (file_exists($dir)) {
$files = array_filter(glob($dir . DIRECTORY_SEPARATOR . '*'), 'is_file');
foreach($files as $file)
{
\Log::info("{$file} ...");
$this->processFile($file);
}
}
}
public function processFile($file)
{
$message = file_get_contents($file);
$matches = Array();
preg_match('/Final-Recipient:\s?rfc822;\s?(.*)/', $message, $matches);
$address = $matches[1];
$bounceType = $this->bounceType($message);
$env = \App::environment();
if ($bounceType === 'soft' || $bounceType === 'hard') {
$callbackApi = 'https://callback.api/api/v1/bounces';
$url = "{$callbackApi}/{$bounceType}?email=".urlencode($address);
\Log::info("Bounce {$url} ...");
// TODO: upgrade to SES clone and do a POST here
$response = Http::get($url);
if ($env === 'prd') {
unlink($handle);
}
}
}
/**
* Check bounce type by message
*
* @param string $message the entire message string
* @return string soft|hard|unknown
*/
public function bounceType($message)
{
$matches = Array();
preg_match('/Diagnostic-Code:\s?smtp;\s?(.*)/', $message, $matches);
$dcode = $matches[1];
\Log::info("Diagnostic-Code: ${dcode} ...");
$codeStart = $dcode[0];
if ($codeStart == '4') {
return 'soft';
} else if ($codeStart == '5') {
return 'hard';
}
return 'unknown';
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment