HEX
Server: LiteSpeed
System: Linux s1049.use1.mysecurecloudhost.com 4.18.0-477.27.2.lve.el8.x86_64 #1 SMP Wed Oct 11 12:32:56 UTC 2023 x86_64
User: xedaptot (3356)
PHP: 8.3.31
Disabled: NONE
Upload Files
File: /home/xedaptot/hi.naniguide.com/app/Services/CampaignService.php
<?php

namespace App\Services;

use App\Jobs\StartCampaignJob;
use App\Models\Campaign;
use App\Models\CampaignRecipient;
use App\Models\Customer;
use App\Models\DeliveryServer;
use App\Models\EmailList;
use App\Models\BounceServer;
use App\Models\SendingDomain;
use App\Models\TrackingDomain;
use App\Models\ListSubscriber;
use App\Models\Template;
use App\Services\AutomationTriggerService;
use App\Services\Tracking\TrackingHasher;
use App\Services\Billing\UsageService;
use Illuminate\Pagination\LengthAwarePaginator;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Mail;

class CampaignService
{
    protected function workspaceQuery(Customer $customer)
    {
        $workspaceId = session('active_workspace_id');

        return Campaign::query()
            ->when($workspaceId, function ($query) use ($workspaceId) {
                $query->where('workspace_id', $workspaceId);
            }, function ($query) use ($customer) {
                $query->where('customer_id', $customer->id)
                    ->whereNull('workspace_id');
            });
    }

    public function __construct(
        private readonly UsageService $usageService,
        private readonly TrackingHasher $trackingHasher,
        private readonly DeliveryServerService $deliveryServerService,
    ) {
    }

    public function ensureCanRun(Campaign $campaign): void
    {
        $campaign->loadMissing('customer', 'deliveryServer.bounceServer', 'sendingDomain', 'trackingDomain', 'bounceServer');
        $customer = $campaign->customer;

        if (!$customer) {
            throw new \RuntimeException('Campaign customer not found.');
        }

        $mustAddDelivery = (bool) $customer->groupSetting('servers.permissions.must_add_delivery_server', false);
        $mustAddBounce = (bool) $customer->groupSetting('servers.permissions.must_add_bounce_server', false);
        $mustAddSending = (bool) $customer->groupSetting('domains.sending_domains.must_add', false);
        $mustAddTracking = (bool) $customer->groupSetting('domains.tracking_domains.must_add', false);
        $canUseSystem = (bool) $customer->groupSetting('servers.permissions.can_use_system_servers', false);

        $querySelectableSendingDomains = static function () use ($customer, $mustAddSending, $canUseSystem) {
            return SendingDomain::query()
                ->where('status', 'verified')
                ->when($mustAddSending, function ($q) use ($customer) {
                    $q->where('customer_id', $customer->id);
                }, function ($q) use ($customer, $canUseSystem) {
                    $q->where(function ($sub) use ($customer, $canUseSystem) {
                        $sub->where('customer_id', $customer->id);
                        if ($canUseSystem) {
                            $sub->orWhereNull('customer_id');
                        }
                    });
                });
        };

        $querySelectableTrackingDomains = static function () use ($customer, $mustAddTracking, $canUseSystem) {
            return TrackingDomain::query()
                ->where('status', 'verified')
                ->when($mustAddTracking, function ($q) use ($customer) {
                    $q->where('customer_id', $customer->id);
                }, function ($q) use ($customer, $canUseSystem) {
                    $q->where(function ($sub) use ($customer, $canUseSystem) {
                        $sub->where('customer_id', $customer->id);
                        if ($canUseSystem) {
                            $sub->orWhereNull('customer_id');
                        }
                    });
                });
        };

        if ($mustAddDelivery) {
            $hasSelectableDelivery = $this->deliveryServerService
                ->querySelectableDeliveryServersForCustomer($customer, $mustAddDelivery, $canUseSystem)
                ->exists();

            if (!$hasSelectableDelivery) {
                throw new \RuntimeException('You must add a delivery server before running a campaign.');
            }

            if (!$campaign->delivery_server_id) {
                throw new \RuntimeException('You must select a delivery server before running a campaign.');
            }

            $deliveryServer = $this->deliveryServerService
                ->querySelectableDeliveryServersForCustomer($customer, $mustAddDelivery, $canUseSystem)
                ->whereKey($campaign->delivery_server_id)
                ->first();

            if (!$deliveryServer) {
                throw new \RuntimeException('Selected delivery server is not available.');
            }
        } else {
            if ($campaign->delivery_server_id) {
                $deliveryServer = DeliveryServer::query()
                    ->whereKey($campaign->delivery_server_id)
                    ->where(function ($q) use ($customer, $canUseSystem) {
                        $q->where('customer_id', $customer->id);
                        if ($canUseSystem) {
                            $q->orWhereNull('customer_id');
                        }
                    })
                    ->where('status', 'active')
                    ->where('use_for', true)
                    ->first();

                if (!$deliveryServer) {
                    throw new \RuntimeException('Selected delivery server is not available.');
                }
            }
        }

        if ($mustAddBounce) {
            $hasOwnBounce = BounceServer::query()
                ->where('customer_id', $customer->id)
                ->where('active', true)
                ->exists();

            if (!$hasOwnBounce) {
                $hasSystemBounce = BounceServer::query()
                    ->whereNull('customer_id')
                    ->where('active', true)
                    ->exists();

                if (!$hasSystemBounce) {
                    throw new \RuntimeException('You must add a bounce server before running a campaign.');
                }
            }

            $bounceServer = null;

            if ($campaign->bounce_server_id) {
                $bounceServer = BounceServer::query()
                    ->whereKey($campaign->bounce_server_id)
                    ->where('active', true)
                    ->first();
            }

            if (!$bounceServer) {
                $fallbackBounce = $campaign->deliveryServer?->bounceServer;
                if ($fallbackBounce && $fallbackBounce->active) {
                    $bounceServer = $fallbackBounce;
                }
            }

            if (!$bounceServer || !$bounceServer->active) {
                throw new \RuntimeException('You must select an active bounce server before running a campaign.');
            }

            $isCustomerBounce = (int) $bounceServer->customer_id === (int) $customer->id;
            $isSystemBounce = $bounceServer->customer_id === null;
            if (!$isCustomerBounce && (!$canUseSystem || !$isSystemBounce)) {
                throw new \RuntimeException('Selected bounce server is not available.');
            }
        }

        if ($mustAddSending) {
            if (!$querySelectableSendingDomains()->exists()) {
                throw new \RuntimeException('You must add and verify a sending domain before running a campaign.');
            }

            if (!$campaign->sending_domain_id) {
                throw new \RuntimeException('You must select a sending domain before running a campaign.');
            }

            $sendingDomain = $querySelectableSendingDomains()
                ->whereKey($campaign->sending_domain_id)
                ->first();

            if (!$sendingDomain) {
                throw new \RuntimeException('Selected sending domain is not available.');
            }
        } else {
            if ($campaign->sending_domain_id) {
                $sendingDomain = $querySelectableSendingDomains()
                    ->whereKey($campaign->sending_domain_id)
                    ->first();

                if (!$sendingDomain) {
                    throw new \RuntimeException('Selected sending domain is not available.');
                }
            }
        }

        if ($mustAddTracking) {
            if (!$querySelectableTrackingDomains()->exists()) {
                throw new \RuntimeException('You must add and verify a tracking domain before running a campaign.');
            }

            if (!$campaign->tracking_domain_id) {
                throw new \RuntimeException('You must select a tracking domain before running a campaign.');
            }

            $trackingDomain = $querySelectableTrackingDomains()
                ->whereKey($campaign->tracking_domain_id)
                ->first();

            if (!$trackingDomain) {
                throw new \RuntimeException('Selected tracking domain is not available.');
            }
        } else {
            if ($campaign->tracking_domain_id) {
                $trackingDomain = $querySelectableTrackingDomains()
                    ->whereKey($campaign->tracking_domain_id)
                    ->first();

                if (!$trackingDomain) {
                    throw new \RuntimeException('Selected tracking domain is not available.');
                }
            }
        }
    }
    /**
     * Get paginated list of campaigns for a customer.
     */
    public function getPaginated(Customer $customer, array $filters = [], int $perPage = 15): LengthAwarePaginator
    {
        $query = $this->workspaceQuery($customer)
            ->with(['emailList']);

        // Apply filters
        if (isset($filters['search'])) {
            $search = $filters['search'];
            $query->where(function ($q) use ($search) {
                $q->where('name', 'like', "%{$search}%")
                    ->orWhere('subject', 'like', "%{$search}%");
            });
        }

        if (isset($filters['status'])) {
            $query->where('status', $filters['status']);
        }

        if (isset($filters['type'])) {
            $query->where('type', $filters['type']);
        }

        return $query->latest()->paginate($perPage);
    }

    /**
     * Create a new campaign.
     */
    public function create(Customer $customer, array $data): Campaign
    {
        // If template_id is provided, load template content
        $htmlContent = $data['html_content'] ?? null;
        $plainTextContent = $data['plain_text_content'] ?? null;
        $templateId = $data['template_id'] ?? null;

        if ($templateId && !$htmlContent) {
            $template = Template::find($templateId);
            if ($template) {
                $htmlContent = $template->html_content;
                $plainTextContent = $template->plain_text_content;
                // Increment template usage
                $template->incrementUsage();
            }
        }

        $campaign = Campaign::create([
            'customer_id' => $customer->id,
            'list_id' => $data['list_id'] ?? null,
            'template_id' => $templateId,
            'delivery_server_id' => $data['delivery_server_id'] ?? null,
            'reply_server_id' => $data['reply_server_id'] ?? null,
            'sending_domain_id' => $data['sending_domain_id'] ?? null,
            'tracking_domain_id' => $data['tracking_domain_id'] ?? null,
            'name' => $data['name'],
            'subject' => $data['subject'],
            'from_name' => $data['from_name'] ?? null,
            'from_email' => $data['from_email'] ?? $customer->email,
            'reply_to' => $data['reply_to'] ?? null,
            'type' => $data['type'] ?? 'regular',
            'status' => $data['status'] ?? 'draft',
            'html_content' => $htmlContent,
            'plain_text_content' => $plainTextContent,
            'template_data' => $data['template_data'] ?? [],
            'scheduled_at' => $data['scheduled_at'] ?? null,
            'send_at' => $data['send_at'] ?? null,
            'track_opens' => $data['track_opens'] ?? true,
            'track_clicks' => $data['track_clicks'] ?? true,
            'segments' => $data['segments'] ?? [],
            'settings' => $data['settings'] ?? [],
        ]);

        $this->dispatchScheduledStartJobIfNeeded($campaign);

        return $campaign;
    }

    /**
     * Update an existing campaign.
     */
    public function update(Campaign $campaign, array $data): Campaign
    {
        // If template_id is provided and changed, load template content
        $templateId = $data['template_id'] ?? null;
        if ($templateId && $templateId != $campaign->template_id && empty($data['html_content'])) {
            $template = Template::find($templateId);
            if ($template) {
                $data['html_content'] = $template->html_content;
                $data['plain_text_content'] = $template->plain_text_content;
                // Increment template usage
                $template->incrementUsage();
            }
        }

        $campaign->update($data);
        $campaign = $campaign->fresh();

        $this->dispatchScheduledStartJobIfNeeded($campaign);

        return $campaign;
    }

    private function dispatchScheduledStartJobIfNeeded(Campaign $campaign): void
    {
        if ($campaign->status !== 'scheduled') {
            return;
        }

        if (!$campaign->scheduled_at) {
            return;
        }

        $queueConnection = config('queue.default', 'sync');
        if ($queueConnection === 'sync') {
            Log::warning(
                "Campaign {$campaign->id} is scheduled but queue is set to sync. " .
                "Auto-start requires a non-sync queue or the Laravel scheduler (campaigns:start-scheduled)."
            );
            return;
        }

        StartCampaignJob::dispatch($campaign)
            ->delay($campaign->scheduled_at)
            ->onQueue('campaigns');
    }

    /**
     * Delete a campaign.
     */
    public function delete(Campaign $campaign): bool
    {
        return $campaign->delete();
    }

    /**
     * Send a campaign.
     */
    public function send(Campaign $campaign): void
    {
        $this->ensureCanRun($campaign);
        // Check if this is an A/B test campaign
        if ($campaign->hasAbTest()) {
            $this->sendAbTest($campaign);
            return;
        }

        // Eager load required relations
        $campaign->loadMissing('customer', 'sendingDomain', 'emailList.sendingDomain', 'deliveryServer.bounceServer');

        // Determine the effective sending domain for this campaign (if verified)
        $sendingDomainOverride = null;
        if ($campaign->sendingDomain && $campaign->sendingDomain->isVerified()) {
            $sendingDomainOverride = $campaign->sendingDomain->domain;
        } elseif ($campaign->emailList && $campaign->emailList->sendingDomain && $campaign->emailList->sendingDomain->isVerified()) {
            $sendingDomainOverride = $campaign->emailList->sendingDomain->domain;
        }

        // Load delivery server with bounce server relationship
        $deliveryServer = $campaign->deliveryServer;

        // From email priority is handled at send-time without rewriting the domain.
        // For delivery-server based sends, the selected server must provide the verified sender.
        if ($deliveryServer) {
            $effectiveFromEmail = trim((string) ($deliveryServer->from_email ?? ''));

            if ($effectiveFromEmail === '') {
                // Fallback: SMTP-authenticated username (e.g. Gmail/Outlook SMTP)
                $serverUsername = trim((string) ($deliveryServer->username ?? ''));
                if ($serverUsername !== '' && filter_var($serverUsername, FILTER_VALIDATE_EMAIL)) {
                    $effectiveFromEmail = $serverUsername;
                }
            }

            if ($effectiveFromEmail === '') {
                $effectiveFromEmail = trim((string) ($campaign->from_email
                    ?: ($campaign->emailList?->from_email ?? '')));
            }

            if ($effectiveFromEmail === '') {
                throw new \RuntimeException(
                    'Delivery server ' . ($deliveryServer->name ?: $deliveryServer->id) . ' does not have a configured verified from email.'
                );
            }
        } else {
            $effectiveFromEmail = (string) ($campaign->from_email
                ?: ($campaign->emailList?->from_email
                    ?: (string) config('mail.from.address')));
        }

        $effectiveFromName = (string) ($deliveryServer?->from_name
            ?: ($campaign->from_name ?: config('mail.from.name')));
        
        // Configure mail system to use the selected delivery server
        if ($deliveryServer) {
            $deliveryServer->load('bounceServer');
            // Configure mail settings based on delivery server type
            $this->deliveryServerService->configureMailFromServer($deliveryServer, $sendingDomainOverride);
        } else {
            // If no delivery server is selected, log a warning
            \Log::warning('Campaign has no delivery server selected', [
                'campaign_id' => $campaign->id,
            ]);
        }

        $subscribers = $this->resolveCampaignSubscribers($campaign);

        $totalRecipients = $subscribers->count();

        // Update campaign status
        $campaign->update([
            'status' => 'sending',
            'started_at' => now(),
            'total_recipients' => $totalRecipients,
            'sent_count' => 0,
        ]);

        // Send emails to each subscriber
        // Note: In production, this should be done via a queue job to handle large lists
        $sentCount = 0;
        foreach ($subscribers as $subscriber) {
            try {
                // Create recipient record for tracking
                $recipient = CampaignRecipient::create([
                    'campaign_id' => $campaign->id,
                    'email' => $subscriber->email,
                    'first_name' => $subscriber->first_name,
                    'last_name' => $subscriber->last_name,
                    'meta' => [
                        'custom_fields' => is_array($subscriber->custom_fields) ? $subscriber->custom_fields : [],
                    ],
                    'status' => 'pending',
                ]);

                $personalization = app(PersonalizationService::class);

                // Prepare email content with personalization
                $htmlContent = $this->personalizeContent($campaign->html_content, $subscriber);
                $plainTextContent = $this->personalizeContent($campaign->plain_text_content, $subscriber);
                $subject = $personalization->personalizeForSubscriber((string) ($campaign->subject ?? ''), $subscriber, false);

                // Add unsubscribe link if HTML content exists
                if ($htmlContent) {
                    $htmlContent = $this->addUnsubscribeLink($htmlContent, $subscriber, $campaign);
                }

                // Inject tracking pixel and rewrite links for tracking using hash-based identifiers
                if ($htmlContent && $campaign->track_opens) {
                    $htmlContent = $this->addOpenTrackingPixel($htmlContent, $campaign, $recipient);
                }

                if ($htmlContent && $campaign->track_clicks) {
                    $htmlContent = $this->rewriteClickTrackingLinks($htmlContent, $campaign, $recipient);
                }

                // Send email
                $effectiveReplyTo = app(ReplyTrackingAddressService::class)
                    ->effectiveReplyTo($campaign, $recipient, $effectiveFromEmail);

                Mail::raw($plainTextContent ?: strip_tags($htmlContent), function ($message) use ($campaign, $subscriber, $htmlContent, $deliveryServer, $recipient, $effectiveFromEmail, $effectiveFromName, $effectiveReplyTo, $subject) {
                    $message->to($subscriber->email, $subscriber->first_name . ' ' . $subscriber->last_name)
                        ->subject($subject)
                        ->from($effectiveFromEmail, $effectiveFromName);

                    if ($effectiveReplyTo !== '') {
                        $message->replyTo($effectiveReplyTo);
                    }

                    // logs if there is bounce server
                    Log::info('Bounce server', [
                        'bounce_server' => $deliveryServer->bounceServer->username,
                    ]);

                    Log::info('Delivery server', [
                        'delivery_server' => $deliveryServer,
                    ]);

                    // Set Return-Path to bounce server email if configured
                    // This ensures bounced emails go to the bounce server's inbox
                    if ($deliveryServer && $deliveryServer->bounceServer && $deliveryServer->bounceServer->username) {
                        $message->returnPath($deliveryServer->bounceServer->username);

                        Log::info('Added Return-Path header to test email', [
                            'return_path' => $deliveryServer->bounceServer->username,
                        ]);
                    }

                    if ($htmlContent) {
                        $message->html($htmlContent);
                    }

                    // Add headers for DKIM signing plugin and bounce tracking
                    $message->getHeaders()->addTextHeader('X-Customer-ID', $campaign->customer_id);
                    if ($campaign->delivery_server_id ?? null) {
                        $message->getHeaders()->addTextHeader('X-Delivery-Server-ID', $campaign->delivery_server_id);
                    }
                    if ($deliveryServer && $deliveryServer->bounce_server_id) {
                        $message->getHeaders()->addTextHeader('X-Bounce-Server-ID', $deliveryServer->bounce_server_id);
                    }
                    
                    // Add campaign tracking headers for bounce processing
                    $message->getHeaders()->addTextHeader('X-Campaign-ID', $campaign->id);
                    if ($campaign->list_id) {
                        $message->getHeaders()->addTextHeader('X-List-ID', $campaign->list_id);
                    }
                    if ($recipient->uuid) {
                        $message->getHeaders()->addTextHeader('X-Recipient-UUID', $recipient->uuid);
                    }
                });

                // Mark recipient and campaign stats
                $recipient->markAsSent();
                $campaign->incrementSentCount();

                try {
                    app(AutomationTriggerService::class)->scheduleNegativeCampaignTriggersForRecipient($campaign, $recipient);
                } catch (\Throwable $e) {
                    Log::warning('Failed scheduling negative campaign automation triggers', [
                        'campaign_id' => $campaign->id,
                        'recipient_id' => $recipient->id,
                        'error' => $e->getMessage(),
                    ]);
                }

                // Log usage for billing (emails sent this month)
                if ($campaign->customer) {
                    $this->usageService->log(
                        $campaign->customer,
                        'emails_sent_this_month',
                        1,
                        ['campaign_id' => $campaign->id]
                    );
                }

                $sentCount++;
            } catch (\Exception $e) {
                // Log error but continue sending to other subscribers
                \Log::error('Failed to send campaign email', [
                    'campaign_id' => $campaign->id,
                    'subscriber_id' => $subscriber->id,
                    'error' => $e->getMessage(),
                ]);
            }
        }

        // Update campaign with final status
        $campaign->update([
            'status' => 'sent',
            'finished_at' => now(),
            'sent_count' => $sentCount,
            'delivered_count' => $sentCount, // Assuming all sent emails are delivered for now
        ]);

        // Check for completion based on recipients
        $campaign->checkCompletion();
    }

    /**
     * Personalize email content with subscriber data.
     */
    protected function personalizeContent(?string $content, ListSubscriber $subscriber): ?string
    {
        if (!$content) {
            return null;
        }

        return app(PersonalizationService::class)->personalizeForSubscriber($content, $subscriber);
    }

    /**
     * Resolve the subscriber audience for a campaign.
     *
     * If campaign segments are selected, restrict recipients to subscribers
     * that belong to at least one selected segment. Otherwise fall back to the
     * full confirmed list audience.
     */
    public function resolveCampaignSubscribers(Campaign $campaign)
    {
        $query = ListSubscriber::query()
            ->where('list_id', $campaign->list_id)
            ->where('status', 'confirmed');

        $segmentIds = collect((array) ($campaign->segments ?? []))
            ->map(fn ($id) => (int) $id)
            ->filter(fn ($id) => $id > 0)
            ->unique()
            ->values();

        if ($segmentIds->isEmpty()) {
            return $query->get();
        }

        return $query->whereHas('segments', function ($segmentQuery) use ($segmentIds) {
            $segmentQuery->whereIn('list_segments.id', $segmentIds->all());
        })->get();
    }

    /**
     * Add unsubscribe link to email content.
     */
    protected function addUnsubscribeLink(string $htmlContent, ListSubscriber $subscriber, Campaign $campaign): string
    {
        // Generate token using the same method as PublicSubscriptionController
        $token = hash('sha256', $subscriber->email . $subscriber->list_id . config('app.key'));
        
        // Generate unsubscribe URL
        $unsubscribeUrl = route('public.unsubscribe', [
            'list' => $subscriber->list_id,
            'email' => $subscriber->email,
            'token' => $token,
        ]);

        $unsubscribeLink = '<p style="font-size: 12px; color: #666; margin-top: 20px; text-align: center;">';
        $unsubscribeLink .= '<a href="' . $unsubscribeUrl . '" style="color: #666; text-decoration: underline;">Unsubscribe from this list</a>';
        $unsubscribeLink .= '</p>';

        // Replace unsubscribe placeholder if it exists, otherwise add at the end
        if (
            stripos($htmlContent, '{unsubscribe_url}') !== false
            || stripos($htmlContent, '{{unsubscribe_url}}') !== false
            || preg_match('/(?<![a-zA-Z0-9_])unsubscribe_url(?![a-zA-Z0-9_])/i', $htmlContent)
        ) {
            $htmlContent = str_ireplace(['{{unsubscribe_url}}', '{unsubscribe_url}'], $unsubscribeUrl, $htmlContent);
            $htmlContent = (string) preg_replace('/(?<![a-zA-Z0-9_])unsubscribe_url(?![a-zA-Z0-9_])/i', $unsubscribeUrl, $htmlContent);
        } else {
            // Add unsubscribe link before closing body tag, or at the end if no body tag
            if (stripos($htmlContent, '</body>') !== false) {
                $htmlContent = str_ireplace('</body>', $unsubscribeLink . '</body>', $htmlContent);
            } else {
                $htmlContent .= $unsubscribeLink;
            }
        }

        return $htmlContent;
    }

    /**
     * Add open tracking pixel to HTML content.
     *
     * For now we use the legacy tracking routes that work with the
     * recipient UUID stored on `CampaignRecipient`. The v2 hash-based
     * routes are used elsewhere (e.g. in the queued sending pipeline).
     */
    protected function addOpenTrackingPixel(string $htmlContent, Campaign $campaign, CampaignRecipient $recipient): string
    {
        $url = route('track.open.legacy', [
            'uuid' => $recipient->uuid,
        ]);

        $pixel = '<img src="' . $url . '" alt="" width="1" height="1" style="display:none;" />';

        if (stripos($htmlContent, '</body>') !== false) {
            return str_ireplace('</body>', $pixel . '</body>', $htmlContent);
        }

        return $htmlContent . $pixel;
    }

    /**
     * Rewrite links in HTML content for click tracking.
     *
     * Same as above, we target the legacy click tracking route which
     * accepts the recipient UUID and encoded URL.
     */
    protected function rewriteClickTrackingLinks(string $htmlContent, Campaign $campaign, CampaignRecipient $recipient): string
    {
        $campaign->loadMissing('trackingDomain');
        $trackingHost = null;
        if ($campaign->trackingDomain && $campaign->trackingDomain->isVerified()) {
            $trackingHost = $campaign->trackingDomain->domain;
        }

        return preg_replace_callback(
            '/<a\s+[^>]*href="([^"]+)"([^>]*)>/i',
            static function (array $matches) use ($recipient, $trackingHost) {
                $originalUrl = $matches[1];

                // Skip if already a tracking URL
                if (str_contains($originalUrl, '/track/click/') || str_contains($originalUrl, '/t/click/')) {
                    return $matches[0];
                }

                if (is_string($trackingHost) && $trackingHost !== '') {
                    $host = parse_url($originalUrl, PHP_URL_HOST);
                    if (is_string($host) && $host !== '' && strcasecmp($trackingHost, $host) === 0) {
                        $path = (string) (parse_url($originalUrl, PHP_URL_PATH) ?? '');
                        if (str_starts_with($path, '/track/click/') || str_starts_with($path, '/t/click/')) {
                            return $matches[0];
                        }
                    }
                }

                $encodedUrl = rtrim(strtr(base64_encode($originalUrl), '+/', '-_'), '=');
                $trackingUrl = route('track.click.legacy', [
                    'uuid' => $recipient->uuid,
                    'url' => $encodedUrl,
                ]);

                return str_replace($originalUrl, e($trackingUrl), $matches[0]);
            },
            $htmlContent
        ) ?? $htmlContent;
    }
}