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/EmailWarmupService.php
<?php

namespace App\Services;

use App\Models\EmailWarmup;
use App\Models\WarmupLog;
use App\Models\WarmupEmail;
use App\Models\ListSubscriber;
use App\Jobs\ProcessWarmupJob;
use App\Services\EmailitApiService;
use Illuminate\Pagination\LengthAwarePaginator;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Mail;
use Carbon\Carbon;

class EmailWarmupService
{
    protected function workspaceQuery(int $customerId)
    {
        $workspaceId = session('active_workspace_id');

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

    protected array $defaultTemplates = [
        [
            'subject' => 'Quick question about your experience',
            'body' => "Hi there,\n\nI hope this email finds you well. I wanted to reach out and see how things are going.\n\nWould love to hear your thoughts when you have a moment.\n\nBest regards",
        ],
        [
            'subject' => 'Following up on our connection',
            'body' => "Hello,\n\nI hope you're having a great day! Just wanted to touch base and see if there's anything I can help you with.\n\nLooking forward to hearing from you.\n\nWarm regards",
        ],
        [
            'subject' => 'Checking in',
            'body' => "Hi,\n\nI wanted to drop you a quick note to see how everything is going on your end.\n\nFeel free to reach out if you need anything.\n\nBest",
        ],
        [
            'subject' => 'A quick hello',
            'body' => "Hello there,\n\nJust sending a quick hello and hoping all is well with you.\n\nLet me know if there's anything I can assist with.\n\nCheers",
        ],
        [
            'subject' => 'Touching base',
            'body' => "Hi,\n\nI hope this message finds you in good spirits. I wanted to reach out and reconnect.\n\nWould be great to catch up when you have time.\n\nAll the best",
        ],
    ];

    public function getPaginated(int $customerId, array $filters = [], int $perPage = 15): LengthAwarePaginator
    {
        $query = $this->workspaceQuery($customerId);

        if (!empty($filters['search'])) {
            $search = $filters['search'];
            $query->where(function ($q) use ($search) {
                $q->where('name', 'like', "%{$search}%")
                    ->orWhere('from_email', 'like', "%{$search}%");
            });
        }

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

        return $query->with('deliveryServer')
            ->orderByDesc('id')
            ->paginate($perPage)
            ->withQueryString();
    }

    public function create(array $data): EmailWarmup
    {
        $data['email_templates'] = $data['email_templates'] ?? $this->defaultTemplates;
        $data['settings'] = $data['settings'] ?? [
            'reply_tracking' => true,
            'auto_pause_on_high_bounce' => true,
            'bounce_threshold' => 5,
        ];

        return EmailWarmup::create($data);
    }

    public function update(EmailWarmup $warmup, array $data): EmailWarmup
    {
        $warmup->update($data);
        return $warmup->fresh();
    }

    public function delete(EmailWarmup $warmup): bool
    {
        return $warmup->delete();
    }

    public function start(EmailWarmup $warmup): EmailWarmup
    {
        if (!$warmup->canStart()) {
            throw new \Exception('Warmup cannot be started in its current state.');
        }

        $warmup->update([
            'status' => 'active',
            'started_at' => $warmup->started_at ?? now(),
        ]);

        $this->scheduleNextSend($warmup);

        Log::info('Email warmup started', [
            'warmup_id' => $warmup->id,
            'customer_id' => $warmup->customer_id,
        ]);

        return $warmup->fresh();
    }

    public function pause(EmailWarmup $warmup): EmailWarmup
    {
        if (!$warmup->canPause()) {
            throw new \Exception('Warmup cannot be paused in its current state.');
        }

        $warmup->update(['status' => 'paused']);

        Log::info('Email warmup paused', [
            'warmup_id' => $warmup->id,
        ]);

        return $warmup->fresh();
    }

    public function complete(EmailWarmup $warmup): EmailWarmup
    {
        $warmup->update([
            'status' => 'completed',
            'completed_at' => now(),
        ]);

        Log::info('Email warmup completed', [
            'warmup_id' => $warmup->id,
            'total_sent' => $warmup->total_sent,
        ]);

        return $warmup->fresh();
    }

    public function scheduleNextSend(EmailWarmup $warmup): void
    {
        if (!$warmup->isActive()) {
            return;
        }

        $nextDay = $warmup->current_day + 1;

        if ($nextDay > $warmup->total_days) {
            $this->complete($warmup);
            return;
        }

        $sendTime = Carbon::parse($warmup->send_time, $warmup->timezone);
        $now = Carbon::now($warmup->timezone);

        if ($now->gt($sendTime)) {
            $sendTime = $sendTime->addDay();
        }

        $delay = $now->diffInSeconds($sendTime);

        ProcessWarmupJob::dispatch($warmup->id)
            ->delay(now()->addSeconds($delay));

        Log::info('Warmup job scheduled', [
            'warmup_id' => $warmup->id,
            'next_day' => $nextDay,
            'scheduled_for' => $sendTime->toDateTimeString(),
        ]);
    }

    public function processDay(EmailWarmup $warmup): WarmupLog
    {
        if (!$warmup->isActive()) {
            throw new \Exception('Warmup is not active.');
        }

        $nextDay = $warmup->current_day + 1;
        $targetVolume = $warmup->calculateVolumeForDay($nextDay);
        $today = now()->toDateString();

        $existingLog = WarmupLog::where('email_warmup_id', $warmup->id)
            ->where('send_date', $today)
            ->first();

        if ($existingLog && $existingLog->isCompleted()) {
            Log::info('Warmup day already processed', [
                'warmup_id' => $warmup->id,
                'day' => $nextDay,
            ]);
            return $existingLog;
        }

        $log = $existingLog ?? WarmupLog::create([
            'email_warmup_id' => $warmup->id,
            'send_date' => $today,
            'day_number' => $nextDay,
            'target_volume' => $targetVolume,
            'status' => 'pending',
        ]);

        $log->markAsStarted();

        try {
            $sentCount = $this->sendWarmupEmails($warmup, $log, $targetVolume);

            $warmup->update([
                'current_day' => $nextDay,
                'last_sent_at' => now(),
            ]);

            $log->markAsCompleted();

            if ($nextDay >= $warmup->total_days) {
                $this->complete($warmup);
            } else {
                $this->scheduleNextSend($warmup);
            }

            Log::info('Warmup day processed successfully', [
                'warmup_id' => $warmup->id,
                'day' => $nextDay,
                'sent' => $sentCount,
                'target' => $targetVolume,
            ]);

        } catch (\Exception $e) {
            $log->markAsFailed($e->getMessage());

            Log::error('Warmup day processing failed', [
                'warmup_id' => $warmup->id,
                'day' => $nextDay,
                'error' => $e->getMessage(),
            ]);

            throw $e;
        }

        return $log;
    }

    protected function sendWarmupEmails(EmailWarmup $warmup, WarmupLog $log, int $targetVolume): int
    {
        $warmup->loadMissing('deliveryServer', 'emailList');

        if (!$warmup->deliveryServer) {
            throw new \Exception('No delivery server configured for warmup.');
        }

        $recipients = $this->getRecipients($warmup, $targetVolume);

        if (empty($recipients)) {
            throw new \Exception('No recipients available for warmup emails.');
        }

        $deliveryServer = $warmup->deliveryServer;
        $isEmailit = $deliveryServer && $deliveryServer->type === 'emailit';

        if (!$isEmailit) {
            $deliveryServerService = app(DeliveryServerService::class);
            $deliveryServerService->configureMailFromServer($deliveryServer);
        }

        $templates = $warmup->email_templates ?? $this->defaultTemplates;
        $sentCount = 0;

        foreach ($recipients as $recipient) {
            try {
                $template = $templates[array_rand($templates)];

                $warmupEmail = WarmupEmail::create([
                    'warmup_log_id' => $log->id,
                    'email_warmup_id' => $warmup->id,
                    'email' => $recipient,
                    'subject' => $template['subject'],
                    'status' => 'pending',
                ]);

                $this->sendSingleEmail($warmup, $warmupEmail, $template);

                $sentCount++;
                $log->incrementStat('sent');
                $warmup->incrementStats('sent');

                usleep(500000);

            } catch (\Exception $e) {
                Log::warning('Failed to send warmup email', [
                    'warmup_id' => $warmup->id,
                    'recipient' => $recipient,
                    'error' => $e->getMessage(),
                ]);
            }
        }

        return $sentCount;
    }

    protected function sendSingleEmail(EmailWarmup $warmup, WarmupEmail $warmupEmail, array $template): void
    {
        $fromEmail = $warmup->from_email;
        $fromName = $warmup->from_name ?? config('mail.from.name');

        $htmlBody = '<div style="font-family: Arial, sans-serif; line-height: 1.6;">'
            . nl2br(htmlspecialchars($template['body'], ENT_QUOTES, 'UTF-8'))
            . '</div>';

        $deliveryServer = $warmup->deliveryServer;

        if ($deliveryServer && $deliveryServer->type === 'emailit') {
            $message = [
                'from_email' => $fromEmail,
                'from_name' => $fromName,
                'subject' => $template['subject'],
                'htmlbody' => $htmlBody,
                'textbody' => $template['body'],
            ];

            app(EmailitApiService::class)->send($deliveryServer, $message);
            $warmupEmail->markAsSent();
            return;
        }

        Mail::send([], [], function ($message) use ($warmupEmail, $template, $fromEmail, $fromName, $htmlBody) {
            $message->to($warmupEmail->email)
                ->subject($template['subject'])
                ->from($fromEmail, $fromName)
                ->html($htmlBody);
        });

        $warmupEmail->markAsSent();
    }

    protected function getRecipients(EmailWarmup $warmup, int $count): array
    {
        if ($warmup->emailList) {
            return ListSubscriber::where('list_id', $warmup->email_list_id)
                ->where('status', 'confirmed')
                ->inRandomOrder()
                ->limit($count)
                ->pluck('email')
                ->toArray();
        }

        $settings = $warmup->settings ?? [];
        $seedEmails = $settings['seed_emails'] ?? [];

        if (!empty($seedEmails)) {
            $result = [];
            for ($i = 0; $i < $count; $i++) {
                $result[] = $seedEmails[$i % count($seedEmails)];
            }
            return $result;
        }

        return [$warmup->from_email];
    }

    public function getStats(EmailWarmup $warmup): array
    {
        $logs = $warmup->logs()
            ->orderBy('day_number')
            ->get();

        $dailyData = $logs->map(function ($log) {
            return [
                'day' => $log->day_number,
                'date' => $log->send_date->format('M d'),
                'sent' => $log->sent_count,
                'target' => $log->target_volume,
                'opened' => $log->opened_count,
                'clicked' => $log->clicked_count,
                'bounced' => $log->bounced_count,
                'open_rate' => $log->open_rate ?? 0,
                'bounce_rate' => $log->bounce_rate ?? 0,
            ];
        });

        return [
            'total_sent' => $warmup->total_sent,
            'total_opened' => $warmup->total_opened,
            'total_clicked' => $warmup->total_clicked,
            'total_bounced' => $warmup->total_bounced,
            'open_rate' => $warmup->getOpenRate(),
            'click_rate' => $warmup->getClickRate(),
            'bounce_rate' => $warmup->getBounceRate(),
            'health_score' => $warmup->getHealthScore(),
            'progress' => $warmup->getProgressPercentage(),
            'current_day' => $warmup->current_day,
            'total_days' => $warmup->total_days,
            'daily_data' => $dailyData,
        ];
    }

    public function checkAndPauseIfNeeded(EmailWarmup $warmup): bool
    {
        $settings = $warmup->settings ?? [];
        $autoPause = $settings['auto_pause_on_high_bounce'] ?? true;
        $threshold = $settings['bounce_threshold'] ?? 5;

        if (!$autoPause) {
            return false;
        }

        if ($warmup->getBounceRate() > $threshold) {
            $this->pause($warmup);

            Log::warning('Warmup auto-paused due to high bounce rate', [
                'warmup_id' => $warmup->id,
                'bounce_rate' => $warmup->getBounceRate(),
                'threshold' => $threshold,
            ]);

            return true;
        }

        return false;
    }

    public function getDefaultTemplates(): array
    {
        return $this->defaultTemplates;
    }
}