Initial Ladill Queue release — enterprise QMS standalone app.
Deploy Ladill Queue / deploy (push) Successful in 56s
Deploy Ladill Queue / deploy (push) Successful in 56s
Phases 1–6: tickets, counters, displays, appointments, workflows, rules, analytics, reports, feedback, admin, device API, and Gitea deploy workflow for queue.ladill.com. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services\Comms;
|
||||
|
||||
use App\Mail\ContactMessageMail;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Illuminate\Support\Facades\Mail;
|
||||
|
||||
/**
|
||||
* Outbound email to contacts. Uses the app's configured mailer (set MAIL_* to
|
||||
* a Ladill Bird SMTP credential in production so mail leaves as the account's
|
||||
* sender). Failures are swallowed + logged; the caller decides what to record.
|
||||
*/
|
||||
class EmailService
|
||||
{
|
||||
public function send(string $to, string $subject, string $body, ?string $fromName = null, ?string $replyTo = null): bool
|
||||
{
|
||||
if (! filter_var($to, FILTER_VALIDATE_EMAIL)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
Mail::to($to)->send(new ContactMessageMail($subject, $body, $fromName, $replyTo));
|
||||
|
||||
return true;
|
||||
} catch (\Throwable $e) {
|
||||
Log::warning('CRM email send failed', ['to' => $to, 'error' => $e->getMessage()]);
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services\Comms;
|
||||
|
||||
use Illuminate\Support\Facades\Http;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
/**
|
||||
* Outbound SMS to contacts via Arkesel (the platform SMS provider). Low-volume,
|
||||
* best-effort transactional send — mirrors the platform's direct Arkesel calls;
|
||||
* uses the shared ARKESEL_API_KEY. Failures are swallowed + logged.
|
||||
*/
|
||||
class SmsService
|
||||
{
|
||||
public function send(string $to, string $message): bool
|
||||
{
|
||||
$apiKey = (string) config('arkesel.api_key', '');
|
||||
$sender = (string) config('arkesel.sender_id', 'Ladill');
|
||||
|
||||
$msisdn = $this->normalise($to);
|
||||
if ($apiKey === '' || $msisdn === null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
$res = Http::timeout(20)
|
||||
->withHeaders(['api-key' => $apiKey])
|
||||
->acceptJson()
|
||||
->post(rtrim((string) config('arkesel.base_url', 'https://sms.arkesel.com'), '/').'/api/v2/sms/send', [
|
||||
'sender' => $sender,
|
||||
'message' => $message,
|
||||
'recipients' => [$msisdn],
|
||||
]);
|
||||
|
||||
return $res->successful() && ($res->json('status') === 'success');
|
||||
} catch (\Throwable $e) {
|
||||
Log::warning('CRM SMS send failed', ['to' => $msisdn, 'error' => $e->getMessage()]);
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/** Normalise to E.164-without-plus (e.g. 233XXXXXXXXX). */
|
||||
private function normalise(string $to): ?string
|
||||
{
|
||||
$digits = preg_replace('/\D+/', '', $to) ?? '';
|
||||
$country = (string) config('arkesel.default_country_code', '233');
|
||||
|
||||
if (strlen($digits) < 9) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (str_starts_with($digits, $country)) {
|
||||
return $digits;
|
||||
}
|
||||
|
||||
if (str_starts_with($digits, '0')) {
|
||||
return $country.substr($digits, 1);
|
||||
}
|
||||
|
||||
if (strlen($digits) === 9) {
|
||||
return $country.$digits;
|
||||
}
|
||||
|
||||
return $digits;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user