Skip to content

Commit 7e34c92

Browse files
committed
When a mail is sent via SMTP mailer with only Bcc recipients, add 'To: undisclosed-recipients' header.
1 parent fe6ce27 commit 7e34c92

3 files changed

Lines changed: 117 additions & 0 deletions

File tree

src/Mail/SmtpMailer.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,10 @@ public function send(Message $mail): void
7373
{
7474
$tmp = clone $mail;
7575
$tmp->setHeader('Bcc', null);
76+
if (!$tmp->getHeader('To') && !$tmp->getHeader('Cc')) {
77+
// missing recipient headers make some mailers (e.g., sendmail) nervous -> set 'To' like many MTAs do
78+
$tmp->setHeader('To', 'undisclosed-recipients: ;');
79+
}
7680

7781
$data = $this->signer
7882
? $this->signer->generateSignedMessage($tmp)
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
<?php
2+
3+
/**
4+
* Test: Nette\Mail\SmtpMailer correctly handles Bcc-only message
5+
*/
6+
7+
declare(strict_types=1);
8+
9+
use Nette\Mail\Message;
10+
use Tester\Assert;
11+
12+
13+
require __DIR__ . '/../bootstrap.php';
14+
require __DIR__ . '/SmtpMailerTestWrapper.php';
15+
16+
17+
$mail = new Message;
18+
$mail->setFrom('Tester <tester@example.com>');
19+
$mail->addBcc('hidden1@example.com');
20+
$mail->addBcc('hidden2@example.com');
21+
22+
$mailer = new SmtpMailerTestWrapper;
23+
$mailer->send($mail);
24+
25+
// check the mail was sent to all Bcc mails
26+
list($from, $to1, $to2, $data, $body) = $mailer->getWrittenLines();
27+
Assert::equal("MAIL FROM:<tester@example.com>", $from);
28+
Assert::equal("RCPT TO:<hidden1@example.com>", $to1);
29+
Assert::equal("RCPT TO:<hidden2@example.com>", $to2);
30+
Assert::equal("DATA", $data);
31+
32+
// make sure no Bcc is in the body and 'To; was set to 'undisclosed-recipients'
33+
$body = explode("\r\n", $body);
34+
$recipientHeaders = array_values(array_filter($body, function ($line) {
35+
return preg_match('/^(To|Cc|Bcc):/i', $line);
36+
}));
37+
Assert::count(1, $recipientHeaders);
38+
Assert::equal("To: undisclosed-recipients: ;", $recipientHeaders[0]);
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
<?php
2+
3+
/**
4+
* Common code for Mail test cases.
5+
*/
6+
7+
declare(strict_types=1);
8+
9+
use Nette\Mail\SmtpMailer;
10+
use Nette\Mail\Message;
11+
12+
/**
13+
* A wrapper for SmtpMailer (derived class) that helps with tesing.
14+
* It overrides internal connection functions so we can mock TCP communication.
15+
* Note: this is the first implementation -- it only collects the write operations.
16+
*/
17+
class SmtpMailerTestWrapper extends SmtpMailer
18+
{
19+
private $connected = false;
20+
private $written = [];
21+
22+
public function __construct() {
23+
parent::__construct('localhost', '', '');
24+
}
25+
26+
/**
27+
* Overrides connection to mock TCP interaction.
28+
*/
29+
protected function connect(): void
30+
{
31+
if ($this->connected) {
32+
throw new Exception("The connect() function called, but the connection was already established.");
33+
}
34+
$this->connected = true;
35+
}
36+
37+
38+
/**
39+
* Terminates mocking connection.
40+
*/
41+
protected function disconnect(): void
42+
{
43+
if (!$this->connected) {
44+
throw new Exception("The disconnect() function called, but no connection was currently established.");
45+
}
46+
$this->connected = false;
47+
}
48+
49+
50+
/**
51+
* Overrides writing function so we can collect, what was actually written by the sender.
52+
*/
53+
protected function write(string $line, int|array|null $expectedCode = null, ?string $message = null): void
54+
{
55+
$this->written[] = $line;
56+
}
57+
58+
59+
/**
60+
* Overrides reading function to mock inputs for the mailer.
61+
*/
62+
protected function read(): string
63+
{
64+
return ''; // not needed yet, may be implemented in the future
65+
}
66+
67+
/**
68+
* Return lines collected in write calls.
69+
* @return stirng[]
70+
*/
71+
public function getWrittenLines(): array
72+
{
73+
return $this->written;
74+
}
75+
}

0 commit comments

Comments
 (0)