Send Events mail from platform SMTP like Invoice, not owner Bird domain.
Deploy Ladill Events / deploy (push) Successful in 31s
Deploy Ladill Events / deploy (push) Successful in 31s
Use EventMailer with MAIL_FROM and organiser Reply-To instead of the Bird API, which rewrote the From address onto the account's verified domain (e.g. climp.me). Redesign the speaker invitation to use the shared Events email layout. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -58,6 +58,8 @@ class EventCommsService
|
||||
$programmeUrl = $programme?->publicUrl();
|
||||
$joinUrl = $this->primaryJoinUrl($event);
|
||||
$ownerRef = (string) $event->user->public_id;
|
||||
$organizerEmail = $event->user->email;
|
||||
$organizerName = $event->user->name;
|
||||
|
||||
$emailed = 0;
|
||||
$texted = 0;
|
||||
@@ -68,15 +70,15 @@ class EventCommsService
|
||||
|
||||
if ($reg->attendee_email && in_array($mode, [self::MODE_PROGRAMME, self::MODE_BOTH], true) && $programmeUrl) {
|
||||
if ($mode === self::MODE_BOTH) {
|
||||
$sent = $this->email->sendJoinAndProgramme($ownerRef, $reg->attendee_email, $eventName, $joinUrl, $programmeUrl, $reg->attendee_name);
|
||||
$sent = $this->email->sendJoinAndProgramme($ownerRef, $reg->attendee_email, $eventName, $joinUrl, $programmeUrl, $reg->attendee_name, $organizerEmail, $organizerName);
|
||||
} else {
|
||||
$sent = $this->email->sendProgrammeShare($ownerRef, $reg->attendee_email, $eventName, $programmeUrl, $reg->attendee_name);
|
||||
$sent = $this->email->sendProgrammeShare($ownerRef, $reg->attendee_email, $eventName, $programmeUrl, $reg->attendee_name, $organizerEmail, $organizerName);
|
||||
}
|
||||
if ($sent) {
|
||||
$emailed++;
|
||||
}
|
||||
} elseif ($reg->attendee_email && $mode === self::MODE_JOIN && $joinUrl !== '') {
|
||||
$sent = $this->email->sendJoinLink($ownerRef, $reg->attendee_email, $eventName, $joinUrl, $reg->attendee_name);
|
||||
$sent = $this->email->sendJoinLink($ownerRef, $reg->attendee_email, $eventName, $joinUrl, $reg->attendee_name, $organizerEmail, $organizerName);
|
||||
if ($sent) {
|
||||
$emailed++;
|
||||
}
|
||||
|
||||
@@ -2,16 +2,15 @@
|
||||
|
||||
namespace App\Services\Events;
|
||||
|
||||
use App\Services\Billing\PlatformEmailClient;
|
||||
use Illuminate\Support\Facades\View;
|
||||
|
||||
class EventEmailService
|
||||
{
|
||||
public function __construct(private readonly PlatformEmailClient $platform) {}
|
||||
public function __construct(private readonly EventMailer $mailer) {}
|
||||
|
||||
public function lastError(): ?string
|
||||
{
|
||||
return $this->platform->lastError();
|
||||
return $this->mailer->lastError();
|
||||
}
|
||||
|
||||
public function sendProgrammeShare(
|
||||
@@ -20,6 +19,8 @@ class EventEmailService
|
||||
string $eventName,
|
||||
string $programmeUrl,
|
||||
?string $attendeeName = null,
|
||||
?string $organizerEmail = null,
|
||||
?string $organizerName = null,
|
||||
): bool {
|
||||
return $this->send(
|
||||
$ownerPublicId,
|
||||
@@ -27,6 +28,9 @@ class EventEmailService
|
||||
'Programme for '.$eventName,
|
||||
'mail.notifications.event-programme',
|
||||
compact('eventName', 'programmeUrl', 'attendeeName'),
|
||||
$eventName,
|
||||
$organizerEmail,
|
||||
$organizerName,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -36,6 +40,8 @@ class EventEmailService
|
||||
string $eventName,
|
||||
string $joinUrl,
|
||||
?string $attendeeName = null,
|
||||
?string $organizerEmail = null,
|
||||
?string $organizerName = null,
|
||||
): bool {
|
||||
return $this->send(
|
||||
$ownerPublicId,
|
||||
@@ -43,6 +49,9 @@ class EventEmailService
|
||||
'Join '.$eventName,
|
||||
'mail.notifications.event-join',
|
||||
compact('eventName', 'joinUrl', 'attendeeName'),
|
||||
$eventName,
|
||||
$organizerEmail,
|
||||
$organizerName,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -53,6 +62,8 @@ class EventEmailService
|
||||
string $joinUrl,
|
||||
string $programmeUrl,
|
||||
?string $attendeeName = null,
|
||||
?string $organizerEmail = null,
|
||||
?string $organizerName = null,
|
||||
): bool {
|
||||
return $this->send(
|
||||
$ownerPublicId,
|
||||
@@ -60,6 +71,9 @@ class EventEmailService
|
||||
$eventName.' — programme & join link',
|
||||
'mail.notifications.event-join-programme',
|
||||
compact('eventName', 'joinUrl', 'programmeUrl', 'attendeeName'),
|
||||
$eventName,
|
||||
$organizerEmail,
|
||||
$organizerName,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -70,6 +84,8 @@ class EventEmailService
|
||||
string $badgeCode,
|
||||
?string $joinUrl = null,
|
||||
?string $attendeeName = null,
|
||||
?string $organizerEmail = null,
|
||||
?string $organizerName = null,
|
||||
): bool {
|
||||
return $this->send(
|
||||
$ownerPublicId,
|
||||
@@ -77,6 +93,9 @@ class EventEmailService
|
||||
'Registration confirmed — '.$eventName,
|
||||
'mail.notifications.event-registration-confirmed',
|
||||
compact('eventName', 'badgeCode', 'joinUrl', 'attendeeName'),
|
||||
$eventName,
|
||||
$organizerEmail,
|
||||
$organizerName,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -86,6 +105,8 @@ class EventEmailService
|
||||
string $eventName,
|
||||
string $portalUrl,
|
||||
?string $speakerName = null,
|
||||
?string $organizerEmail = null,
|
||||
?string $organizerName = null,
|
||||
): bool {
|
||||
return $this->send(
|
||||
$ownerPublicId,
|
||||
@@ -97,20 +118,35 @@ class EventEmailService
|
||||
'portalUrl' => $portalUrl,
|
||||
'speakerName' => $speakerName,
|
||||
],
|
||||
$eventName,
|
||||
$organizerEmail,
|
||||
$organizerName,
|
||||
);
|
||||
}
|
||||
|
||||
/** @param array<string, mixed> $viewData */
|
||||
private function send(string $ownerPublicId, string $to, string $subject, string $view, array $viewData): bool
|
||||
{
|
||||
private function send(
|
||||
string $ownerPublicId,
|
||||
string $to,
|
||||
string $subject,
|
||||
string $view,
|
||||
array $viewData,
|
||||
?string $fromDisplayName = null,
|
||||
?string $replyToEmail = null,
|
||||
?string $replyToName = null,
|
||||
): bool {
|
||||
$html = View::make($view, $viewData)->render();
|
||||
$text = strip_tags(str_replace(['<br>', '<br/>', '<br />'], "\n", $html));
|
||||
|
||||
return $this->platform->send(
|
||||
return $this->mailer->send(
|
||||
$ownerPublicId,
|
||||
$to,
|
||||
$subject,
|
||||
$html,
|
||||
strip_tags(str_replace(['<br>', '<br/>', '<br />'], "\n", $html)),
|
||||
$text,
|
||||
$fromDisplayName,
|
||||
$replyToEmail,
|
||||
$replyToName,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,98 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services\Events;
|
||||
|
||||
use App\Services\Billing\BillingClient;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Illuminate\Support\Facades\Mail;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
/**
|
||||
* Sends Ladill Events transactional email from the platform mailer (like Invoice),
|
||||
* not the event owner's Bird SMTP domain. Reply-To is set to the organiser.
|
||||
*/
|
||||
class EventMailer
|
||||
{
|
||||
public const EMAIL_PRICE_MINOR = 1;
|
||||
|
||||
private ?string $lastError = null;
|
||||
|
||||
public function __construct(private readonly BillingClient $billing) {}
|
||||
|
||||
public function lastError(): ?string
|
||||
{
|
||||
return $this->lastError;
|
||||
}
|
||||
|
||||
public function isConfigured(): bool
|
||||
{
|
||||
return filter_var((string) config('mail.from.address'), FILTER_VALIDATE_EMAIL) !== false;
|
||||
}
|
||||
|
||||
public function send(
|
||||
string $ownerPublicId,
|
||||
string $to,
|
||||
string $subject,
|
||||
string $html,
|
||||
?string $text = null,
|
||||
?string $fromDisplayName = null,
|
||||
?string $replyToEmail = null,
|
||||
?string $replyToName = null,
|
||||
): bool {
|
||||
$this->lastError = null;
|
||||
|
||||
if (! $this->isConfigured()) {
|
||||
$this->lastError = 'Outbound email is not configured on this Events instance.';
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if (! $this->billing->canAfford($ownerPublicId, self::EMAIL_PRICE_MINOR)) {
|
||||
$this->lastError = 'Insufficient Ladill wallet balance. Add funds in Billing and try again.';
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
$reference = 'events_email:'.Str::uuid();
|
||||
|
||||
try {
|
||||
Mail::html($html, function ($message) use ($to, $subject, $text, $fromDisplayName, $replyToEmail, $replyToName): void {
|
||||
$message->to($to)
|
||||
->subject($subject)
|
||||
->from(
|
||||
(string) config('mail.from.address'),
|
||||
$fromDisplayName ?: (string) config('mail.from.name'),
|
||||
);
|
||||
|
||||
if ($replyToEmail) {
|
||||
$message->replyTo($replyToEmail, $replyToName ?: null);
|
||||
}
|
||||
|
||||
if ($text) {
|
||||
$message->text($text);
|
||||
}
|
||||
});
|
||||
} catch (\Throwable $e) {
|
||||
Log::warning('Events mail send failed', ['error' => $e->getMessage(), 'to' => $to]);
|
||||
$this->lastError = 'The email could not be sent. Please try again.';
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if (! $this->billing->debit(
|
||||
$ownerPublicId,
|
||||
self::EMAIL_PRICE_MINOR,
|
||||
'smtp',
|
||||
'events_email',
|
||||
$reference,
|
||||
null,
|
||||
'Events email to '.$to,
|
||||
)) {
|
||||
$this->lastError = 'Insufficient Ladill wallet balance. Add funds in Billing and try again.';
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -276,6 +276,8 @@ class EventRegistrationService
|
||||
$registration->badge_code,
|
||||
$joinUrl !== '' ? $joinUrl : null,
|
||||
$registration->attendee_name,
|
||||
$registration->qrCode?->user?->email,
|
||||
$registration->qrCode?->user?->name,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -226,7 +226,7 @@ class EventSpeakerInviteService
|
||||
return ['ok' => false, 'error' => 'Could not resolve the event owner account for sending email.'];
|
||||
}
|
||||
|
||||
if (! app(\App\Services\Billing\PlatformEmailClient::class)->isConfigured()) {
|
||||
if (! app(EventMailer::class)->isConfigured()) {
|
||||
return ['ok' => false, 'error' => 'Outbound email is not configured on this Events instance.'];
|
||||
}
|
||||
|
||||
@@ -244,6 +244,8 @@ class EventSpeakerInviteService
|
||||
$eventName,
|
||||
$portalUrl,
|
||||
$speaker['name'] ?? null,
|
||||
$owner->email,
|
||||
$owner->name,
|
||||
);
|
||||
|
||||
if (! $sent) {
|
||||
|
||||
Reference in New Issue
Block a user