107 lines
4.2 KiB
PHP
107 lines
4.2 KiB
PHP
<?php
|
|
|
|
namespace App\Livewire\Admin;
|
|
|
|
use App\Services\Mail\MailPreviews;
|
|
use Illuminate\Support\Facades\Auth;
|
|
use Illuminate\Support\Facades\Mail;
|
|
use Livewire\Attributes\Layout;
|
|
use Livewire\Component;
|
|
use Throwable;
|
|
|
|
/**
|
|
* Look at a mail before a customer does.
|
|
*
|
|
* There was no way to. An invoice mail needs an invoice, a maintenance
|
|
* announcement needs a window, and "register an account to see whether the
|
|
* confirmation reads well on a phone" is not a workflow — which is how every
|
|
* mail went out with a hard 600px table until somebody opened one on a
|
|
* telephone and had to scroll sideways to read a sentence.
|
|
*
|
|
* Two ways to look: in the browser, which answers the desktop question at once,
|
|
* and as a real mail to the operator's OWN address, which is the only way to
|
|
* answer the phone question. Never to a customer: a preview is for us.
|
|
*/
|
|
#[Layout('layouts.admin')]
|
|
class MailPreview extends Component
|
|
{
|
|
public function mount(): void
|
|
{
|
|
$this->authorize('mail.manage');
|
|
}
|
|
|
|
/**
|
|
* Send one to the signed-in operator.
|
|
*
|
|
* To themselves and nobody else — no address field. A preview that can be
|
|
* addressed is a form for sending mail to strangers from inside the console,
|
|
* which is not what this is for and not something to leave lying about.
|
|
*/
|
|
public function sendToMe(string $key): void
|
|
{
|
|
$this->authorize('mail.manage');
|
|
|
|
$operator = Auth::guard('operator')->user();
|
|
$mailable = app(MailPreviews::class)->make($key);
|
|
|
|
if ($operator === null || $mailable === null) {
|
|
return;
|
|
}
|
|
|
|
try {
|
|
// sendNow(), not send(): every mailable here implements ShouldQueue,
|
|
// and send() silently defers to queue() for those — which would put
|
|
// a failure in a worker's log while the button reported success. The
|
|
// person pressing it is waiting for the mail, so the send happens in
|
|
// their request and its failure reaches them.
|
|
//
|
|
// Through the mailable's OWN mailer. Mail::to() hands back a pending
|
|
// mail bound to the DEFAULT mailer, and sendNow() then ignores the
|
|
// mailer the mailable asked for — so a support mail addressed
|
|
// support@ went out over the no-reply@ login and the server answered
|
|
// "553 Sender address rejected: not owned by user". Every purpose
|
|
// mailbox here has its own SMTP account, and the From has to belong
|
|
// to the account that authenticates.
|
|
Mail::mailer($mailable->mailer ?: null)
|
|
->to($operator->email)
|
|
->sendNow($mailable);
|
|
} catch (Throwable $e) {
|
|
$this->dispatch('notify', message: __('mail_preview.failed', [
|
|
'reason' => mb_substr($e->getMessage(), 0, 160),
|
|
]));
|
|
|
|
return;
|
|
}
|
|
|
|
$this->dispatch('notify', message: __('mail_preview.sent', ['email' => $operator->email]));
|
|
}
|
|
|
|
public function render()
|
|
{
|
|
$this->authorize('mail.manage');
|
|
|
|
$previews = app(MailPreviews::class)->all();
|
|
|
|
return view('livewire.admin.mail-preview', [
|
|
'previews' => $previews,
|
|
// Read off the mailables rather than kept as a second list: the
|
|
// sender is decided by the purpose mailbox, and a table here would
|
|
// be a copy that drifts.
|
|
// From the envelope, not from the mailable's `from` property: that
|
|
// property stays empty until the mail is built, so reading it here
|
|
// showed nothing at all.
|
|
'senders' => collect($previews)
|
|
->keys()
|
|
->mapWithKeys(fn (string $key) => [
|
|
$key => app(MailPreviews::class)->make($key)?->envelope()->from?->address,
|
|
])
|
|
->all(),
|
|
'me' => Auth::guard('operator')->user()?->email ?? '',
|
|
// A preview that goes nowhere is worse than none: with a
|
|
// non-delivering mailer the browser view still works and the send
|
|
// silently writes to a file.
|
|
'delivers' => ! in_array(config('mail.default'), ['log', 'array', null], true),
|
|
]);
|
|
}
|
|
}
|