Add per-customer SMS and Bird credentials to Frontdesk Integrations.
Deploy Ladill Frontdesk / deploy (push) Successful in 45s

NotificationDispatcher sends via customer relay and skips Frontdesk wallet debit when tenant keys are configured.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
isaacclad
2026-07-12 17:05:54 +00:00
co-authored by Cursor
parent 0b45e08016
commit 8300cafc36
17 changed files with 1110 additions and 40 deletions
@@ -10,15 +10,17 @@ use App\Models\User;
use App\Models\Visitor;
use App\Models\Visit;
use App\Notifications\FrontdeskAlertNotification;
use App\Services\Comms\EmailService;
use App\Services\Comms\SmsService;
use App\Services\Messaging\CustomerEmailClient;
use App\Services\Messaging\CustomerSmsClient;
use App\Services\Messaging\MessagingCredentialsService;
use Illuminate\Support\Facades\Log;
class NotificationDispatcher
{
public function __construct(
protected EmailService $email,
protected SmsService $sms,
protected MessagingCredentialsService $credentials,
protected CustomerEmailClient $customerEmail,
protected CustomerSmsClient $customerSms,
protected NotificationPreferenceService $preferences,
protected NotificationBillingService $billing,
) {}
@@ -194,40 +196,94 @@ class NotificationDispatcher
): bool {
$notified = false;
$reference = $visit ? 'visit-'.$visit->id.'-'.($event ?? 'alert') : 'host-'.$host->id;
$credential = $this->credentials->forOrganization($organization);
if (in_array('email', $channels, true) && $host->email) {
if (! $this->billing->canAffordEmail($organization)) {
Log::info('Frontdesk host email skipped — insufficient wallet balance', [
if (! $credential->hasValidBird()) {
Log::info('Frontdesk host email skipped — Ladill Bird not connected', [
'host_id' => $host->id,
'organization_id' => $organization->id,
]);
} else {
try {
$this->email->send($host->email, $title, $message);
if ($this->billing->chargeEmail($organization, $reference, "Host alert: {$title}")) {
$notified = true;
$apiKey = $credential->birdApiKey();
if (! $apiKey) {
Log::info('Frontdesk host email skipped — Bird credentials could not be decrypted', [
'host_id' => $host->id,
'organization_id' => $organization->id,
]);
} elseif (! $this->billing->canAffordEmail($organization)) {
Log::info('Frontdesk host email skipped — insufficient wallet balance', [
'host_id' => $host->id,
'organization_id' => $organization->id,
]);
} else {
try {
$html = nl2br(e($message));
$sent = $this->customerEmail->send(
$apiKey,
(string) $credential->bird_from_email,
$credential->bird_from_name,
(string) $host->email,
$title,
$html,
$message,
);
if (! $sent) {
Log::warning('Frontdesk host email failed', [
'host_id' => $host->id,
'error' => $this->customerEmail->lastError(),
]);
} elseif ($this->billing->chargeEmail($organization, $reference, "Host alert: {$title}")) {
$notified = true;
}
} catch (\Throwable $e) {
Log::warning('Frontdesk host email failed', ['host_id' => $host->id, 'error' => $e->getMessage()]);
}
} catch (\Throwable $e) {
Log::warning('Frontdesk host email failed', ['host_id' => $host->id, 'error' => $e->getMessage()]);
}
}
}
if (in_array('sms', $channels, true) && $host->phone) {
$smsBody = "{$title}: {$message}";
if (! $this->billing->canAffordSms($organization, $smsBody)) {
Log::info('Frontdesk host SMS skipped — insufficient wallet balance', [
if (! $credential->hasValidSms()) {
Log::info('Frontdesk host SMS skipped — Ladill SMS not connected', [
'host_id' => $host->id,
'organization_id' => $organization->id,
]);
} else {
try {
$this->sms->send($host->phone, $smsBody);
if ($this->billing->chargeSms($organization, $smsBody, $reference, "Host SMS: {$title}")) {
$notified = true;
$apiKey = $credential->smsApiKey();
if (! $apiKey) {
Log::info('Frontdesk host SMS skipped — SMS credentials could not be decrypted', [
'host_id' => $host->id,
'organization_id' => $organization->id,
]);
} elseif (! $this->billing->canAffordSms($organization, $smsBody)) {
Log::info('Frontdesk host SMS skipped — insufficient wallet balance', [
'host_id' => $host->id,
'organization_id' => $organization->id,
]);
} else {
try {
$sent = $this->customerSms->send(
$apiKey,
(string) $host->phone,
$smsBody,
(string) $credential->sms_sender_id,
);
if (! $sent) {
Log::warning('Frontdesk host SMS failed', [
'host_id' => $host->id,
'error' => $this->customerSms->lastError(),
]);
} elseif ($this->billing->chargeSms($organization, $smsBody, $reference, "Host SMS: {$title}")) {
$notified = true;
}
} catch (\Throwable $e) {
Log::warning('Frontdesk host SMS failed', ['host_id' => $host->id, 'error' => $e->getMessage()]);
}
} catch (\Throwable $e) {
Log::warning('Frontdesk host SMS failed', ['host_id' => $host->id, 'error' => $e->getMessage()]);
}
}
}