diff --git a/src/Mail/SmtpMailer.php b/src/Mail/SmtpMailer.php index f9a1dc99..d2cfec7c 100644 --- a/src/Mail/SmtpMailer.php +++ b/src/Mail/SmtpMailer.php @@ -73,6 +73,10 @@ public function send(Message $mail): void { $tmp = clone $mail; $tmp->setHeader('Bcc', null); + if (!$tmp->getHeader('To') && !$tmp->getHeader('Cc')) { + // missing recipient headers make some mailers (e.g., sendmail) nervous -> set 'To' like many MTAs do + $tmp->setHeader('To', 'undisclosed-recipients: ;'); + } $data = $this->signer ? $this->signer->generateSignedMessage($tmp) diff --git a/tests/Mail/Mail.SmtpMailer.bcc.phpt b/tests/Mail/Mail.SmtpMailer.bcc.phpt new file mode 100644 index 00000000..ec1b6aa3 --- /dev/null +++ b/tests/Mail/Mail.SmtpMailer.bcc.phpt @@ -0,0 +1,38 @@ +setFrom('Tester '); +$mail->addBcc('hidden1@example.com'); +$mail->addBcc('hidden2@example.com'); + +$mailer = new SmtpMailerTestWrapper; +$mailer->send($mail); + +// check the mail was sent to all Bcc mails +list($from, $to1, $to2, $data, $body) = $mailer->getWrittenLines(); +Assert::equal("MAIL FROM:", $from); +Assert::equal("RCPT TO:", $to1); +Assert::equal("RCPT TO:", $to2); +Assert::equal("DATA", $data); + +// make sure no Bcc is in the body and 'To; was set to 'undisclosed-recipients' +$body = explode("\r\n", $body); +$recipientHeaders = array_values(array_filter($body, function ($line) { + return preg_match('/^(To|Cc|Bcc):/i', $line); +})); +Assert::count(1, $recipientHeaders); +Assert::equal("To: undisclosed-recipients: ;", $recipientHeaders[0]); diff --git a/tests/Mail/SmtpMailerTestWrapper.php b/tests/Mail/SmtpMailerTestWrapper.php new file mode 100644 index 00000000..87ede3f5 --- /dev/null +++ b/tests/Mail/SmtpMailerTestWrapper.php @@ -0,0 +1,75 @@ +connected) { + throw new Exception("The connect() function called, but the connection was already established."); + } + $this->connected = true; + } + + + /** + * Terminates mocking connection. + */ + protected function disconnect(): void + { + if (!$this->connected) { + throw new Exception("The disconnect() function called, but no connection was currently established."); + } + $this->connected = false; + } + + + /** + * Overrides writing function so we can collect, what was actually written by the sender. + */ + protected function write(string $line, int|array|null $expectedCode = null, ?string $message = null): void + { + $this->written[] = $line; + } + + + /** + * Overrides reading function to mock inputs for the mailer. + */ + protected function read(): string + { + return ''; // not needed yet, may be implemented in the future + } + + /** + * Return lines collected in write calls. + * @return string[] + */ + public function getWrittenLines(): array + { + return $this->written; + } +}