CluPilotCloud/app/Mail/Concerns/SendsFromMailbox.php

59 lines
2.1 KiB
PHP

<?php
namespace App\Mail\Concerns;
use App\Services\Mail\MailboxResolver;
use Illuminate\Mail\Mailables\Address;
use Illuminate\Mail\Mailables\Envelope;
/**
* From and Reply-To, taken from the mailbox behind a purpose.
*
* Reply-To is the whole reason sending alone is enough: a customer answering a
* support reply lands in the support mailbox, read in an ordinary mail client,
* so no IMAP is needed to close the loop.
*
* Except on a no-reply mailbox. A "no-reply" address you can reply to is a lie
* in the sender, and the one place the promise is made is the address itself.
*/
trait SendsFromMailbox
{
protected function mailboxEnvelope(string $purpose, string $subject): Envelope
{
[$from, $replyTo] = $this->mailboxAddresses($purpose);
return new Envelope(
from: $from,
replyTo: $replyTo ? [$replyTo] : [],
subject: $subject,
);
}
/**
* The From and Reply-To addresses for $purpose's mailbox — the ONE place
* that decides "no configured mailbox means no sender fields at all" and
* "no_reply means no Reply-To". Shared by every consumer of a purpose
* mailbox regardless of the shape it builds — an Envelope here (a
* Mailable), a MailMessage in CloudReady (a Notification, which cannot
* use mailboxEnvelope() directly since it is not building an Envelope).
* Two independent copies of this same decision is exactly the shape of
* gap Task 4 found and fixed in MailboxTransport::resolution().
*
* @return array{0: ?Address, 1: ?Address} [from, replyTo]
*/
protected function mailboxAddresses(string $purpose): array
{
$box = app(MailboxResolver::class)->for($purpose);
if ($box === null) {
// No mailbox configured yet — both null, so a caller falls back
// to its own framework default rather than dereferencing null.
return [null, null];
}
$from = new Address($box->address, $box->display_name ?: null);
return [$from, $box->no_reply ? null : $from];
}
}