fix(email): use client/org name as From display, not Ladill product
Deploy Ladill Events / deploy (push) Successful in 45s

Customer-facing outbound mail should appear from the business (organizer,
clinic, company, location), not the Ladill product brand.
This commit is contained in:
isaacclad
2026-07-16 12:01:06 +00:00
parent a5ebd1e414
commit a7231ab16f
4 changed files with 40 additions and 6 deletions
+3 -1
View File
@@ -74,10 +74,12 @@ class PlatformEmailClient
->withHeaders(['Idempotency-Key' => $key]) ->withHeaders(['Idempotency-Key' => $key])
->acceptJson() ->acceptJson()
->timeout(30) ->timeout(30)
// from_name: pass the organizer/company display name. Do not default
// to EVENTS_SMTP_FROM_NAME ("Ladill Events") — attendee mail is from the customer.
->post($this->base().'/messages/send', array_filter([ ->post($this->base().'/messages/send', array_filter([
'user' => $ownerPublicId, 'user' => $ownerPublicId,
'channel' => 'suite', 'channel' => 'suite',
'from_name' => $fromName ?? config('smtp.from_name'), 'from_name' => $fromName,
'to' => $to, 'to' => $to,
'subject' => $subject, 'subject' => $subject,
'html' => $html, 'html' => $html,
@@ -133,6 +133,8 @@ class EventEmailService
$brandingSettings = AccountBranding::forOwnerRef($ownerPublicId); $brandingSettings = AccountBranding::forOwnerRef($ownerPublicId);
$viewData['logoUrl'] = AccountBranding::emailLogoUrl($brandingSettings); $viewData['logoUrl'] = AccountBranding::emailLogoUrl($brandingSettings);
$viewData['companyName'] = AccountBranding::companyName($brandingSettings); $viewData['companyName'] = AccountBranding::companyName($brandingSettings);
// From display: organizer name, else account/company branding — never "Ladill Events".
$fromName = trim((string) ($replyToName ?: $viewData['companyName'] ?? '')) ?: null;
$html = View::make($view, $viewData)->render(); $html = View::make($view, $viewData)->render();
$text = strip_tags(str_replace(['<br>', '<br/>', '<br />'], "\n", $html)); $text = strip_tags(str_replace(['<br>', '<br/>', '<br />'], "\n", $html));
@@ -145,6 +147,7 @@ class EventEmailService
$text, $text,
$replyToEmail, $replyToEmail,
$replyToName, $replyToName,
$fromName,
); );
} }
} }
+23 -4
View File
@@ -5,6 +5,7 @@ namespace App\Services\Events;
use App\Services\Billing\PlatformEmailClient; use App\Services\Billing\PlatformEmailClient;
use App\Services\Messaging\CustomerEmailClient; use App\Services\Messaging\CustomerEmailClient;
use App\Services\Messaging\MessagingCredentialsService; use App\Services\Messaging\MessagingCredentialsService;
use App\Support\AccountBranding;
/** /**
* Attendee/speaker email: prefer platform suite messaging (zero-config mailbox), * Attendee/speaker email: prefer platform suite messaging (zero-config mailbox),
@@ -48,18 +49,20 @@ class EventMailer
?string $text = null, ?string $text = null,
?string $replyToEmail = null, ?string $replyToEmail = null,
?string $replyToName = null, ?string $replyToName = null,
?string $fromName = null,
): bool { ): bool {
$this->lastError = null; $this->lastError = null;
$displayName = $this->resolveFromName($ownerPublicId, $fromName, $replyToName);
if ($this->platformEmail->isConfigured()) { if ($this->platformEmail->isConfigured()) {
$sent = $this->platformEmail->send($ownerPublicId, $to, $subject, $html, $text); $sent = $this->platformEmail->send($ownerPublicId, $to, $subject, $html, $text, $displayName);
if ($sent) { if ($sent) {
return true; return true;
} }
// Suite unavailable (mailbox, allowance) — try Bird power-user path next. // Suite unavailable (mailbox, allowance) — try Bird power-user path next.
$suiteError = $this->platformEmail->lastError(); $suiteError = $this->platformEmail->lastError();
if ($this->tryCustomerBird($ownerPublicId, $to, $subject, $html, $text)) { if ($this->tryCustomerBird($ownerPublicId, $to, $subject, $html, $text, $displayName)) {
return true; return true;
} }
@@ -68,7 +71,22 @@ class EventMailer
return false; return false;
} }
return $this->tryCustomerBird($ownerPublicId, $to, $subject, $html, $text); return $this->tryCustomerBird($ownerPublicId, $to, $subject, $html, $text, $displayName);
}
private function resolveFromName(string $ownerPublicId, ?string $fromName, ?string $replyToName): ?string
{
foreach ([$fromName, $replyToName] as $candidate) {
$name = trim((string) $candidate);
if ($name !== '') {
return $name;
}
}
$settings = AccountBranding::forOwnerRef($ownerPublicId);
$company = trim(AccountBranding::companyName($settings));
return $company !== '' && strcasecmp($company, 'Company') !== 0 ? $company : null;
} }
private function tryCustomerBird( private function tryCustomerBird(
@@ -77,6 +95,7 @@ class EventMailer
string $subject, string $subject,
string $html, string $html,
?string $text, ?string $text,
?string $fromName = null,
): bool { ): bool {
$credential = $this->credentials->forOwner($ownerPublicId); $credential = $this->credentials->forOwner($ownerPublicId);
if (! $credential->hasValidBird()) { if (! $credential->hasValidBird()) {
@@ -95,7 +114,7 @@ class EventMailer
$sent = $this->customerEmail->send( $sent = $this->customerEmail->send(
$apiKey, $apiKey,
(string) $credential->bird_from_email, (string) $credential->bird_from_email,
$credential->bird_from_name, $credential->bird_from_name ?: $fromName,
$to, $to,
$subject, $subject,
$html, $html,
+11 -1
View File
@@ -78,7 +78,10 @@ class EventsSuiteMessagingTest extends TestCase
'platform.test/api/smtp/messages/send' => Http::response(['success' => true]), 'platform.test/api/smtp/messages/send' => Http::response(['success' => true]),
]); ]);
$user = User::factory()->create(['public_id' => 'usr_mailer_suite']); $user = User::factory()->create([
'public_id' => 'usr_mailer_suite',
'name' => 'Acme Events Ltd',
]);
$ok = app(EventMailer::class)->send( $ok = app(EventMailer::class)->send(
$user->public_id, $user->public_id,
'speaker@example.com', 'speaker@example.com',
@@ -89,6 +92,13 @@ class EventsSuiteMessagingTest extends TestCase
$this->assertTrue($ok); $this->assertTrue($ok);
$this->assertTrue(app(EventMailer::class)->isConfiguredForOwner($user->public_id)); $this->assertTrue(app(EventMailer::class)->isConfiguredForOwner($user->public_id));
Http::assertSent(function ($request) {
$data = $request->data();
return str_contains($request->url(), '/messages/send')
&& ($data['channel'] ?? null) === 'suite'
&& ($data['from_name'] ?? null) === 'Acme Events Ltd';
});
} }
public function test_sms_service_uses_suite_when_platform_configured(): void public function test_sms_service_uses_suite_when_platform_configured(): void