84 lines
3.2 KiB
PHP
84 lines
3.2 KiB
PHP
<?php
|
|
|
|
namespace App\Services\Mail;
|
|
|
|
use App\Models\Mailbox;
|
|
use App\Support\Settings;
|
|
use InvalidArgumentException;
|
|
|
|
/**
|
|
* Which mailbox a given kind of mail goes out from.
|
|
*
|
|
* Read on every resolve rather than cached in a property: a queue worker lives
|
|
* for hours, and a mapping cached at construction would keep sending from the
|
|
* address that was configured when the worker started.
|
|
*/
|
|
final class MailboxResolver
|
|
{
|
|
public function for(string $purpose): ?Mailbox
|
|
{
|
|
if (! in_array($purpose, MailPurpose::ALL, true)) {
|
|
throw new InvalidArgumentException("Unknown mail purpose [{$purpose}].");
|
|
}
|
|
|
|
$box = $this->active($this->named(Settings::get(MailPurpose::settingKey($purpose))));
|
|
|
|
if ($box !== null) {
|
|
return $box;
|
|
}
|
|
|
|
// Fall back rather than fail: a mail that goes out from the wrong
|
|
// address is recoverable, one that is never sent may not be noticed.
|
|
return $purpose === MailPurpose::SYSTEM
|
|
? null
|
|
: $this->active($this->named(Settings::get(MailPurpose::settingKey(MailPurpose::SYSTEM))));
|
|
}
|
|
|
|
/**
|
|
* Das Postfach, das diese Art Mail TATSAECHLICH tragen wuerde — oder null.
|
|
*
|
|
* `for()` beantwortet „welches Postfach ist zugeordnet", das hier
|
|
* beantwortet „welches kann senden": zugeordnet, aktiv, und mit
|
|
* Zugangsdaten, falls es sich anmeldet. Das sind nicht dieselben Fragen.
|
|
*
|
|
* Entstanden aus einem gemeldeten Fehler (31.7.2026): die
|
|
* „Passwort vergessen"-Mail kam nicht an, obwohl die Zustellung
|
|
* eingeschaltet war. Die Bereitschaftsseite fragte eine DRITTE Frage — ob
|
|
* ueberhaupt irgendein Postfach angelegt ist — und stand deshalb auf gruen,
|
|
* waehrend jede Systemmail in der Warteschlange an
|
|
* MailboxTransport::delegate() starb. Der Kunde las „sehen Sie in Ihr
|
|
* Postfach", und im Postausgang stand nichts, weil RecordSentMail auf
|
|
* MessageSent hoert: was nie gesendet wurde, hinterlaesst keine Zeile.
|
|
*
|
|
* `Mailbox::isConfigured()` bleibt die eine Stelle, die weiss, was
|
|
* „kann senden" heisst; hier und im Transport wird sie nur gefragt.
|
|
*/
|
|
public function usableFor(string $purpose): ?Mailbox
|
|
{
|
|
$box = $this->for($purpose);
|
|
|
|
return $box !== null && $box->isConfigured() ? $box : null;
|
|
}
|
|
|
|
private function named(mixed $key): ?Mailbox
|
|
{
|
|
return is_string($key) && $key !== '' ? Mailbox::findByKey($key) : null;
|
|
}
|
|
|
|
/**
|
|
* Deactivating a mailbox is an explicit operator action meaning "do not
|
|
* use this" — it must fall back exactly like a mapping that points
|
|
* nowhere or a row that is gone, so it is filtered here, at BOTH lookup
|
|
* sites, rather than left for a caller to notice.
|
|
*
|
|
* A mailbox with no password is deliberately NOT filtered here: silently
|
|
* sending support mail from billing@ because support@ has no password
|
|
* yet is worse than a loud failure naming the mailbox that needs one —
|
|
* that check stays a MailboxTransport concern, not this one.
|
|
*/
|
|
private function active(?Mailbox $box): ?Mailbox
|
|
{
|
|
return $box?->active === true ? $box : null;
|
|
}
|
|
}
|