` moves * ONE mail type to another mailbox — and it moved only the envelope: the * mailable still named `cp_`, so `security-block` on info@ went out * with `From: info@` over no-reply@'s session. The pairing this file guards is * therefore no longer "From purpose == mailer purpose" but "From key == mailer * key", with MailCatalogue as the one place that maps a key to its purpose. */ it('gives every mail that takes a mailbox From the matching mailer', function () { $offenders = []; foreach (File::allFiles(app_path('Mail')) as $file) { $code = $file->getContents(); // Only the ones that take their sender from a mailbox. A mailable with // no mailbox From has nothing to mismatch. if (! str_contains($code, 'mailboxEnvelope(') && ! str_contains($code, 'mailboxAddresses(')) { continue; } preg_match('/mailbox(?:Envelope|Addresses)\(\s*MailPurpose::([A-Z_]+)/', $code, $from); preg_match("/->mailer\(MailCatalogue::mailer\(('[a-z0-9-]+')/", $code, $keyed); preg_match("/->mailer\('cp_'\.MailPurpose::([A-Z_]+)\)/", $code, $byPurpose); $fromPurpose = $from[1] ?? null; if ($fromPurpose === null) { continue; } // The mailer names a mail type: the catalogue must agree that this type // belongs to the purpose the envelope asks for. Otherwise the fallback // when no route is set would move the sender without moving the login. // // A literal that is not itself a key is a PREFIX — DunningNoticeMail // builds 'dunning-'.$level, one class for four catalogued types. Then // every type behind that prefix has to agree, and there has to be at // least one: a prefix nothing matches is a mailer that does not exist. if (isset($keyed[1])) { $literal = trim($keyed[1], "'"); $expected = constant('App\Services\Mail\MailPurpose::'.$fromPurpose); $matching = array_filter( MailCatalogue::all(), fn (string $key) => $key === $literal || str_starts_with($key, $literal), ARRAY_FILTER_USE_KEY, ); if ($matching === []) { $offenders[] = $file->getFilename().": mail key [{$literal}] matches nothing in MailCatalogue"; continue; } foreach ($matching as $key => $entry) { if ($entry['purpose'] !== $expected) { $offenders[] = $file->getFilename().": From {$fromPurpose}, mail key {$key} is catalogued as {$entry['purpose']}"; } } continue; } // No mail key at all (ContactRequestMail): the purpose mailer is still // the right answer, and it still has to match the From. if (($byPurpose[1] ?? null) !== $fromPurpose) { $offenders[] = $file->getFilename().': From '.$fromPurpose.', mailer '.($byPurpose[1] ?? 'DEFAULT'); } } expect($offenders)->toBe([]); }); it('sends each purpose over its own mailer, not over whatever mail.default is', function () { // The purpose mailers exist so that each mailbox authenticates as itself. // A mailable naming no mailer silently uses mail.default, which on this // installation is the plain smtp mailer with ONE account behind it. foreach (App\Services\Mail\MailPurpose::ALL as $purpose) { expect(config('mail.mailers.cp_'.$purpose)) ->toBe(['transport' => 'mailbox', 'purpose' => $purpose]); } }); it('gives every catalogued mail type a mailer of its own', function () { // Without the mailer, MailRoute would move the envelope and leave the SMTP // session where it was — the exact failure the route was built to allow. foreach (MailCatalogue::all() as $key => $entry) { expect(config('mail.mailers.'.MailCatalogue::mailer($key))) ->toBe(['transport' => 'mailbox', 'purpose' => $entry['purpose'], 'mail_key' => $key]); } });