Initial commit

This commit is contained in:
Ronald Huynen
2026-03-23 21:37:59 +01:00
commit 2547717edb
2193 changed files with 972171 additions and 0 deletions

View File

@@ -0,0 +1,163 @@
<?php
namespace App\Mail;
use App\Models\MailingBounce;
use Illuminate\Mail\Events\MessageSending;
use Illuminate\Support\Facades\Log;
use Symfony\Component\Mime\Email;
class UniversalBounceHandler
{
/**
* Handle the message sending event to check for suppressed recipients
*/
public function handle(MessageSending $event): bool
{
$message = $event->message;
// Extract all recipients from the message
$allRecipients = $this->extractAllRecipients($message);
$suppressedRecipients = [];
// Check each recipient for suppression
foreach ($allRecipients as $email) {
if (MailingBounce::isSuppressed($email)) {
$suppressedRecipients[] = $email;
}
}
// If any recipients are suppressed, log and potentially block the email
if (!empty($suppressedRecipients)) {
Log::info("Email contains suppressed recipients", [
'total_recipients' => count($allRecipients),
'suppressed_recipients' => $suppressedRecipients,
'subject' => $message->getSubject(),
]);
// If ALL recipients are suppressed, block the email entirely
if (count($suppressedRecipients) === count($allRecipients)) {
Log::info("All recipients suppressed, blocking email", [
'suppressed_recipients' => $suppressedRecipients,
'subject' => $message->getSubject(),
]);
return false; // Block the email
}
// If only some recipients are suppressed, remove them from the message
$this->removeSuppressedRecipients($message, $suppressedRecipients);
}
// Add bounce tracking headers to all outgoing emails
$this->addBounceTrackingHeaders($message);
return true; // Allow the email to be sent
}
/**
* Extract all recipient email addresses from the message
*/
protected function extractAllRecipients($message): array
{
$recipients = [];
// Get TO recipients
if ($message->getTo()) {
foreach ($message->getTo() as $address) {
$recipients[] = $address->getAddress();
}
}
// Get CC recipients
if ($message->getCc()) {
foreach ($message->getCc() as $address) {
$recipients[] = $address->getAddress();
}
}
// Get BCC recipients
if ($message->getBcc()) {
foreach ($message->getBcc() as $address) {
$recipients[] = $address->getAddress();
}
}
return array_unique($recipients);
}
/**
* Remove suppressed recipients from the message
*/
protected function removeSuppressedRecipients($message, array $suppressedEmails): void
{
// Remove from TO recipients
$toAddresses = $message->getTo();
if ($toAddresses) {
$filteredTo = [];
foreach ($toAddresses as $address) {
if (!in_array($address->getAddress(), $suppressedEmails)) {
$filteredTo[] = $address;
}
}
$message->to(...$filteredTo);
}
// Remove from CC recipients
$ccAddresses = $message->getCc();
if ($ccAddresses) {
$filteredCc = [];
foreach ($ccAddresses as $address) {
if (!in_array($address->getAddress(), $suppressedEmails)) {
$filteredCc[] = $address;
}
}
$message->cc(...$filteredCc);
}
// Remove from BCC recipients
$bccAddresses = $message->getBcc();
if ($bccAddresses) {
$filteredBcc = [];
foreach ($bccAddresses as $address) {
if (!in_array($address->getAddress(), $suppressedEmails)) {
$filteredBcc[] = $address;
}
}
$message->bcc(...$filteredBcc);
}
}
/**
* Add bounce tracking headers to outgoing emails
*/
protected function addBounceTrackingHeaders($message): void
{
$bounceEmail = timebank_config('mailing.bounce_address');
if (!$bounceEmail) {
return;
}
if ($message instanceof Email) {
$headers = $message->getHeaders();
// Add Return-Path for bounce routing (if not already set)
if (!$headers->has('Return-Path')) {
$headers->addPathHeader('Return-Path', $bounceEmail);
}
// Add bounce tracking headers
$headers->addTextHeader('X-Bounce-Tracking', 'universal');
$headers->addTextHeader('X-Bounce-Handler', 'timebank-cc');
// Add recipient tracking for the first recipient (for bounce processing)
$recipients = $this->extractAllRecipients($message);
if (!empty($recipients)) {
$headers->addTextHeader('X-Primary-Recipient', $recipients[0]);
}
// Set Return-Path on the message
$message->returnPath($bounceEmail);
}
}
}