Brand Frontdesk Laravel mail for hosts and report recipients.
Deploy Ladill Frontdesk / deploy (push) Has been cancelled

Pass organization logo/name through ContactMessageMail for daily reports and campaigns, and drop the Ladill product footer.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
isaacclad
2026-07-12 20:10:26 +00:00
co-authored by Cursor
parent bc53ff206b
commit 1def8cf716
6 changed files with 78 additions and 11 deletions
@@ -39,7 +39,7 @@ class SendDailyReportCommand extends Command
foreach ($recipients as $address) { foreach ($recipients as $address) {
if (is_string($address) && filter_var($address, FILTER_VALIDATE_EMAIL)) { if (is_string($address) && filter_var($address, FILTER_VALIDATE_EMAIL)) {
$email->send($address, $subject, $body); $email->send($address, $subject, $body, $organization->name, null, $organization);
$sent++; $sent++;
} }
} }
+4
View File
@@ -5,6 +5,7 @@ namespace App\Jobs;
use App\Models\Activity; use App\Models\Activity;
use App\Models\Campaign; use App\Models\Campaign;
use App\Models\CampaignRecipient; use App\Models\CampaignRecipient;
use App\Models\Organization;
use App\Models\User; use App\Models\User;
use App\Services\Campaigns\CampaignBillingService; use App\Services\Campaigns\CampaignBillingService;
use App\Services\Campaigns\CampaignMessageService; use App\Services\Campaigns\CampaignMessageService;
@@ -56,6 +57,8 @@ class SendCampaignMessageJob implements ShouldQueue
return; return;
} }
$organization = Organization::query()->where('owner_ref', $campaign->owner_ref)->first();
$sent = $campaign->channel === Campaign::CHANNEL_SMS $sent = $campaign->channel === Campaign::CHANNEL_SMS
? $sms->send((string) $recipient->recipient_phone, $body) ? $sms->send((string) $recipient->recipient_phone, $body)
: $email->send( : $email->send(
@@ -64,6 +67,7 @@ class SendCampaignMessageJob implements ShouldQueue
$body, $body,
$this->senderName($campaign), $this->senderName($campaign),
$this->senderEmail($campaign), $this->senderEmail($campaign),
$organization,
); );
if (! $sent) { if (! $sent) {
+4
View File
@@ -17,6 +17,8 @@ class ContactMessageMail extends Mailable
public string $bodyText, public string $bodyText,
public ?string $fromName = null, public ?string $fromName = null,
public ?string $replyToAddress = null, public ?string $replyToAddress = null,
public ?string $logoUrl = null,
public ?string $companyName = null,
) {} ) {}
public function envelope(): Envelope public function envelope(): Envelope
@@ -34,6 +36,8 @@ class ContactMessageMail extends Mailable
with: [ with: [
'bodyText' => $this->bodyText, 'bodyText' => $this->bodyText,
'fromName' => $this->fromName, 'fromName' => $this->fromName,
'logoUrl' => $this->logoUrl,
'companyName' => $this->companyName,
], ],
); );
} }
+19 -4
View File
@@ -3,6 +3,8 @@
namespace App\Services\Comms; namespace App\Services\Comms;
use App\Mail\ContactMessageMail; use App\Mail\ContactMessageMail;
use App\Models\Organization;
use App\Support\OrganizationBranding;
use Illuminate\Support\Facades\Log; use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Mail; use Illuminate\Support\Facades\Mail;
@@ -13,18 +15,31 @@ use Illuminate\Support\Facades\Mail;
*/ */
class EmailService class EmailService
{ {
public function send(string $to, string $subject, string $body, ?string $fromName = null, ?string $replyTo = null): bool public function send(
{ string $to,
string $subject,
string $body,
?string $fromName = null,
?string $replyTo = null,
?Organization $organization = null,
): bool {
if (! filter_var($to, FILTER_VALIDATE_EMAIL)) { if (! filter_var($to, FILTER_VALIDATE_EMAIL)) {
return false; return false;
} }
$logoUrl = null;
$companyName = null;
if ($organization) {
$logoUrl = OrganizationBranding::emailLogoUrl($organization);
$companyName = $organization->name;
}
try { try {
Mail::to($to)->send(new ContactMessageMail($subject, $body, $fromName, $replyTo)); Mail::to($to)->send(new ContactMessageMail($subject, $body, $fromName, $replyTo, $logoUrl, $companyName));
return true; return true;
} catch (\Throwable $e) { } catch (\Throwable $e) {
Log::warning('CRM email send failed', ['to' => $to, 'error' => $e->getMessage()]); Log::warning('Frontdesk email send failed', ['to' => $to, 'error' => $e->getMessage()]);
return false; return false;
} }
@@ -4,15 +4,26 @@
<meta charset="utf-8"> <meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">
</head> </head>
<body style="margin:0;background:#f1f5f9;font-family:Arial,Helvetica,sans-serif;color:#0f172a;"> <body style="margin:0;background:#f8fafc;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Helvetica,Arial,sans-serif;color:#0f172a;">
<div style="max-width:560px;margin:0 auto;padding:32px 16px;"> <div style="max-width:600px;margin:0 auto;padding:48px 16px;">
<div style="background:#ffffff;border-radius:16px;padding:28px;border:1px solid #e2e8f0;"> @if (! empty($logoUrl) || ! empty($companyName))
<div style="white-space:pre-wrap;font-size:14px;line-height:1.6;color:#334155;">{{ $bodyText }}</div> <div style="text-align:center;padding:0 0 24px;">
@if (! empty($logoUrl))
<img src="{{ $logoUrl }}" alt="{{ $companyName }}" style="height:42px;max-height:42px;width:auto;">
@else
<div style="font-size:22px;font-weight:700;letter-spacing:-0.02em;color:#0f172a;">{{ $companyName }}</div>
@endif
</div>
@endif
<div style="background:#ffffff;border-radius:16px;padding:40px;border:1px solid #e2e8f0;box-shadow:0 4px 6px -1px rgba(0,0,0,0.1);">
<div style="white-space:pre-wrap;font-size:16px;line-height:1.7;color:#475569;">{{ $bodyText }}</div>
@if (! empty($fromName)) @if (! empty($fromName))
<p style="margin-top:24px;font-size:13px;color:#64748b;"> {{ $fromName }}</p> <p style="margin-top:24px;font-size:14px;color:#64748b;"> {{ $fromName }}</p>
@endif @endif
</div> </div>
<p style="margin-top:16px;text-align:center;font-size:11px;color:#94a3b8;">Sent via Ladill Frontdesk</p> @if (! empty($companyName))
<p style="margin-top:20px;text-align:center;font-size:13px;color:#94a3b8;">{{ $companyName }}</p>
@endif
</div> </div>
</body> </body>
</html> </html>
@@ -121,4 +121,37 @@ class FrontdeskEmailBrandingTest extends TestCase
&& ! str_contains($html, 'Sent via Ladill'); && ! str_contains($html, 'Sent via Ladill');
}); });
} }
public function test_laravel_mail_uses_organization_branding(): void
{
Storage::fake('public');
\Illuminate\Support\Facades\Mail::fake();
$path = UploadedFile::fake()->image('hq-logo.png', 400, 120)
->store('frontdesk/organizations/'.$this->organization->id, 'public');
$this->organization->update(['logo_path' => $path]);
$logoUrl = OrganizationBranding::emailLogoUrl($this->organization->fresh());
$sent = app(\App\Services\Comms\EmailService::class)->send(
'admin@example.com',
'Daily summary',
"Visitors checked in: 3\nDeliveries: 1",
$this->organization->name,
null,
$this->organization->fresh(),
);
$this->assertTrue($sent);
\Illuminate\Support\Facades\Mail::assertSent(\App\Mail\ContactMessageMail::class, function (\App\Mail\ContactMessageMail $mail) use ($logoUrl) {
$html = $mail->render();
return $mail->logoUrl === $logoUrl
&& $mail->companyName === 'Frontdesk HQ'
&& str_contains($html, 'Frontdesk HQ')
&& str_contains($html, $logoUrl)
&& ! str_contains($html, 'Sent via Ladill');
});
}
} }