with('campaign')->find($this->recipientId); if (! $recipient || ! $recipient->campaign || $recipient->status !== CampaignRecipient::STATUS_PENDING) { return; } $campaign = $recipient->campaign; if (! in_array($campaign->status, [Campaign::STATUS_QUEUED, Campaign::STATUS_SENDING], true)) { return; } $body = $messages->personalize($campaign->body, $recipient->recipient_name); $subject = $messages->personalize((string) ($campaign->subject ?? ''), $recipient->recipient_name); $costMinor = $pricing->recipientCostMinor($campaign, $campaign->body); if (! $billing->canAfford($campaign->owner_ref, $costMinor)) { $this->markRecipient($recipient, CampaignRecipient::STATUS_SKIPPED, 0, 'Insufficient wallet balance.'); return; } $sent = $campaign->channel === Campaign::CHANNEL_SMS ? $sms->send((string) $recipient->recipient_phone, $body) : $email->send( (string) $recipient->recipient_email, $subject !== '' ? $subject : 'Message from '.config('app.name'), $body, $this->senderName($campaign), $this->senderEmail($campaign), ); if (! $sent) { $this->markRecipient($recipient, CampaignRecipient::STATUS_FAILED, 0, 'Delivery could not be confirmed.'); return; } $charged = $billing->charge( $campaign->owner_ref, $costMinor, $campaign->channel, $campaign->id, $recipient->id, 'CRM campaign "'.$campaign->name.'" to '.$recipient->recipient_name, ); $this->markRecipient( $recipient, CampaignRecipient::STATUS_SENT, $charged ? $costMinor : 0, null, now(), ); $this->logActivity($campaign, $recipient, $subject, $body); } private function markRecipient( CampaignRecipient $recipient, string $status, int $costMinor, ?string $error, ?\DateTimeInterface $sentAt = null, ): void { $recipient->forceFill([ 'status' => $status, 'cost_minor' => $costMinor, 'error_message' => $error, 'sent_at' => $sentAt, ])->save(); $this->refreshCampaignCounters($recipient->campaign()->first()); } private function refreshCampaignCounters(?Campaign $campaign): void { if (! $campaign) { return; } DB::transaction(function () use ($campaign) { $campaign = Campaign::query()->lockForUpdate()->find($campaign->id); if (! $campaign) { return; } $counts = $campaign->recipients() ->selectRaw(" SUM(CASE WHEN status = ? THEN 1 ELSE 0 END) as sent_count, SUM(CASE WHEN status = ? THEN 1 ELSE 0 END) as failed_count, SUM(CASE WHEN status = ? THEN 1 ELSE 0 END) as skipped_count, SUM(cost_minor) as actual_cost_minor ", [ CampaignRecipient::STATUS_SENT, CampaignRecipient::STATUS_FAILED, CampaignRecipient::STATUS_SKIPPED, ]) ->first(); $pending = $campaign->recipients()->where('status', CampaignRecipient::STATUS_PENDING)->count(); $campaign->forceFill([ 'sent_count' => (int) ($counts->sent_count ?? 0), 'failed_count' => (int) ($counts->failed_count ?? 0), 'skipped_count' => (int) ($counts->skipped_count ?? 0), 'actual_cost_minor' => (int) ($counts->actual_cost_minor ?? 0), ]); if ($pending === 0 && in_array($campaign->status, [Campaign::STATUS_QUEUED, Campaign::STATUS_SENDING], true)) { $campaign->status = Campaign::STATUS_COMPLETED; $campaign->completed_at = now(); } elseif ($campaign->status === Campaign::STATUS_QUEUED) { $campaign->status = Campaign::STATUS_SENDING; } $campaign->save(); }); } private function logActivity(Campaign $campaign, CampaignRecipient $recipient, string $subject, string $body): void { $title = $campaign->channel === Campaign::CHANNEL_SMS ? 'Campaign SMS: '.$campaign->name : 'Campaign email: '.($subject !== '' ? $subject : $campaign->name); $payload = [ 'owner_ref' => $campaign->owner_ref, 'type' => $campaign->channel, 'direction' => 'out', 'title' => $title, 'body' => $body, 'completed_at' => now(), ]; if ($recipient->customer_id) { Activity::create([ ...$payload, 'subject_type' => 'contact', 'subject_id' => $recipient->customer_id, ]); } if ($recipient->lead_id) { Activity::create([ ...$payload, 'subject_type' => 'lead', 'subject_id' => $recipient->lead_id, ]); } } private function senderName(Campaign $campaign): ?string { return User::query()->where('public_id', $campaign->owner_ref)->value('name'); } private function senderEmail(Campaign $campaign): ?string { return User::query()->where('public_id', $campaign->owner_ref)->value('email'); } }