Skip to content

Commit 805449d

Browse files
committed
feature #33967 [Mailer] Add Message-Id to SentMessage when sending an email (fabpot)
This PR was squashed before being merged into the 4.4 branch (closes #33967). Discussion ---------- [Mailer] Add Message-Id to SentMessage when sending an email | Q | A | ------------- | --- | Branch? | 4.4 | Bug fix? | yes-ish | New feature? | yes-ish as well | Deprecations? | no <!-- please update UPGRADE-*.md and src/**/CHANGELOG.md files --> | Tickets | fixes #33681 | License | MIT | Doc PR | - This adds `SentMessage::getMessageId()` to retrieve the message id as generated internally OR by the provider sending the email. Commits ------- d97d1f9 [Mailer] Fix Message ID for Postmark SMTP b42c269 Add Message-Id to SentMessage when sending an email
2 parents 004ee73 + d97d1f9 commit 805449d

File tree

13 files changed

+94
-33
lines changed

13 files changed

+94
-33
lines changed

src/Symfony/Component/Mailer/Bridge/Amazon/Transport/SesApiTransport.php

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
use Psr\Log\LoggerInterface;
1515
use Symfony\Component\Mailer\Envelope;
1616
use Symfony\Component\Mailer\Exception\HttpTransportException;
17+
use Symfony\Component\Mailer\SentMessage;
1718
use Symfony\Component\Mailer\Transport\AbstractApiTransport;
1819
use Symfony\Component\Mime\Email;
1920
use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;
@@ -48,7 +49,7 @@ public function __toString(): string
4849
return sprintf('ses+api://%s@%s', $this->accessKey, $this->getEndpoint());
4950
}
5051

51-
protected function doSendApi(Email $email, Envelope $envelope): ResponseInterface
52+
protected function doSendApi(SentMessage $sentMessage, Email $email, Envelope $envelope): ResponseInterface
5253
{
5354
$date = gmdate('D, d M Y H:i:s e');
5455
$auth = sprintf('AWS3-HTTPS AWSAccessKeyId=%s,Algorithm=HmacSHA256,Signature=%s', $this->accessKey, $this->getSignature($date));
@@ -62,12 +63,13 @@ protected function doSendApi(Email $email, Envelope $envelope): ResponseInterfac
6263
'body' => $this->getPayload($email, $envelope),
6364
]);
6465

66+
$result = new \SimpleXMLElement($response->getContent(false));
6567
if (200 !== $response->getStatusCode()) {
66-
$error = new \SimpleXMLElement($response->getContent(false));
67-
68-
throw new HttpTransportException(sprintf('Unable to send an email: %s (code %s).', $error->Error->Message, $error->Error->Code), $response);
68+
throw new HttpTransportException(sprintf('Unable to send an email: %s (code %s).', $result->Error->Message, $result->Error->Code), $response);
6969
}
7070

71+
$sentMessage->setMessageId($result->SendEmailResult->MessageId);
72+
7173
return $response;
7274
}
7375

src/Symfony/Component/Mailer/Bridge/Amazon/Transport/SesHttpTransport.php

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -63,12 +63,13 @@ protected function doSendHttp(SentMessage $message): ResponseInterface
6363
],
6464
]);
6565

66+
$result = new \SimpleXMLElement($response->getContent(false));
6667
if (200 !== $response->getStatusCode()) {
67-
$error = new \SimpleXMLElement($response->getContent(false));
68-
69-
throw new HttpTransportException(sprintf('Unable to send an email: %s (code %s).', $error->Error->Message, $error->Error->Code), $response);
68+
throw new HttpTransportException(sprintf('Unable to send an email: %s (code %s).', $result->Error->Message, $result->Error->Code), $response);
7069
}
7170

71+
$message->setMessageId($result->SendEmailResult->MessageId);
72+
7273
return $response;
7374
}
7475

src/Symfony/Component/Mailer/Bridge/Mailchimp/Transport/MandrillApiTransport.php

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
use Psr\Log\LoggerInterface;
1515
use Symfony\Component\Mailer\Envelope;
1616
use Symfony\Component\Mailer\Exception\HttpTransportException;
17+
use Symfony\Component\Mailer\SentMessage;
1718
use Symfony\Component\Mailer\Transport\AbstractApiTransport;
1819
use Symfony\Component\Mime\Email;
1920
use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;
@@ -41,21 +42,23 @@ public function __toString(): string
4142
return sprintf('mandrill+api://%s', $this->getEndpoint());
4243
}
4344

44-
protected function doSendApi(Email $email, Envelope $envelope): ResponseInterface
45+
protected function doSendApi(SentMessage $sentMessage, Email $email, Envelope $envelope): ResponseInterface
4546
{
4647
$response = $this->client->request('POST', 'https://'.$this->getEndpoint().'/api/1.0/messages/send.json', [
4748
'json' => $this->getPayload($email, $envelope),
4849
]);
4950

51+
$result = $response->toArray(false);
5052
if (200 !== $response->getStatusCode()) {
51-
$result = $response->toArray(false);
5253
if ('error' === ($result['status'] ?? false)) {
5354
throw new HttpTransportException(sprintf('Unable to send an email: %s (code %s).', $result['message'], $result['code']), $response);
5455
}
5556

5657
throw new HttpTransportException(sprintf('Unable to send an email (code %s).', $result['code']), $response);
5758
}
5859

60+
$sentMessage->setMessageId($result['_id']);
61+
5962
return $response;
6063
}
6164

src/Symfony/Component/Mailer/Bridge/Mailchimp/Transport/MandrillHttpTransport.php

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,15 +51,17 @@ protected function doSendHttp(SentMessage $message): ResponseInterface
5151
],
5252
]);
5353

54+
$result = $response->toArray(false);
5455
if (200 !== $response->getStatusCode()) {
55-
$result = $response->toArray(false);
5656
if ('error' === ($result['status'] ?? false)) {
5757
throw new HttpTransportException(sprintf('Unable to send an email: %s (code %s).', $result['message'], $result['code']), $response);
5858
}
5959

6060
throw new HttpTransportException(sprintf('Unable to send an email (code %s).', $result['code']), $response);
6161
}
6262

63+
$message->setMessageId($result['_id']);
64+
6365
return $response;
6466
}
6567

src/Symfony/Component/Mailer/Bridge/Mailgun/Transport/MailgunApiTransport.php

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
use Psr\Log\LoggerInterface;
1515
use Symfony\Component\Mailer\Envelope;
1616
use Symfony\Component\Mailer\Exception\HttpTransportException;
17+
use Symfony\Component\Mailer\SentMessage;
1718
use Symfony\Component\Mailer\Transport\AbstractApiTransport;
1819
use Symfony\Component\Mime\Email;
1920
use Symfony\Component\Mime\Part\Multipart\FormDataPart;
@@ -46,7 +47,7 @@ public function __toString(): string
4647
return sprintf('mailgun+api://%s?domain=%s', $this->getEndpoint(), $this->domain);
4748
}
4849

49-
protected function doSendApi(Email $email, Envelope $envelope): ResponseInterface
50+
protected function doSendApi(SentMessage $sentMessage, Email $email, Envelope $envelope): ResponseInterface
5051
{
5152
$body = new FormDataPart($this->getPayload($email, $envelope));
5253
$headers = [];
@@ -61,14 +62,17 @@ protected function doSendApi(Email $email, Envelope $envelope): ResponseInterfac
6162
'body' => $body->bodyToIterable(),
6263
]);
6364

65+
$result = $response->toArray(false);
6466
if (200 !== $response->getStatusCode()) {
6567
if ('application/json' === $response->getHeaders(false)['content-type'][0]) {
66-
throw new HttpTransportException(sprintf('Unable to send an email: %s (code %s).', $response->toArray(false)['message'], $response->getStatusCode()), $response);
68+
throw new HttpTransportException(sprintf('Unable to send an email: %s (code %s).', $result['message'], $response->getStatusCode()), $response);
6769
}
6870

6971
throw new HttpTransportException(sprintf('Unable to send an email: %s (code %s).', $response->getContent(false), $response->getStatusCode()), $response);
7072
}
7173

74+
$sentMessage->setMessageId($result['id']);
75+
7276
return $response;
7377
}
7478

src/Symfony/Component/Mailer/Bridge/Mailgun/Transport/MailgunHttpTransport.php

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,14 +64,17 @@ protected function doSendHttp(SentMessage $message): ResponseInterface
6464
'body' => $body->bodyToIterable(),
6565
]);
6666

67+
$result = $response->toArray(false);
6768
if (200 !== $response->getStatusCode()) {
6869
if ('application/json' === $response->getHeaders(false)['content-type'][0]) {
69-
throw new HttpTransportException(sprintf('Unable to send an email: %s (code %s).', $response->toArray(false)['message'], $response->getStatusCode()), $response);
70+
throw new HttpTransportException(sprintf('Unable to send an email: %s (code %s).', $result['message'], $response->getStatusCode()), $response);
7071
}
7172

7273
throw new HttpTransportException(sprintf('Unable to send an email: %s (code %s).', $response->getContent(false), $response->getStatusCode()), $response);
7374
}
7475

76+
$message->setMessageId($result['id']);
77+
7578
return $response;
7679
}
7780

src/Symfony/Component/Mailer/Bridge/Postmark/Transport/PostmarkApiTransport.php

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
use Psr\Log\LoggerInterface;
1515
use Symfony\Component\Mailer\Envelope;
1616
use Symfony\Component\Mailer\Exception\HttpTransportException;
17+
use Symfony\Component\Mailer\SentMessage;
1718
use Symfony\Component\Mailer\Transport\AbstractApiTransport;
1819
use Symfony\Component\Mime\Email;
1920
use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;
@@ -41,7 +42,7 @@ public function __toString(): string
4142
return sprintf('postmark+api://%s', $this->getEndpoint());
4243
}
4344

44-
protected function doSendApi(Email $email, Envelope $envelope): ResponseInterface
45+
protected function doSendApi(SentMessage $sentMessage, Email $email, Envelope $envelope): ResponseInterface
4546
{
4647
$response = $this->client->request('POST', 'https://'.$this->getEndpoint().'/email', [
4748
'headers' => [
@@ -51,12 +52,13 @@ protected function doSendApi(Email $email, Envelope $envelope): ResponseInterfac
5152
'json' => $this->getPayload($email, $envelope),
5253
]);
5354

55+
$result = $response->toArray(false);
5456
if (200 !== $response->getStatusCode()) {
55-
$error = $response->toArray(false);
56-
57-
throw new HttpTransportException(sprintf('Unable to send an email: %s (code %s).', $error['Message'], $error['ErrorCode']), $response);
57+
throw new HttpTransportException(sprintf('Unable to send an email: %s (code %s).', $result['Message'], $result['ErrorCode']), $response);
5858
}
5959

60+
$sentMessage->setMessageId($result['MessageID']);
61+
6062
return $response;
6163
}
6264

src/Symfony/Component/Mailer/Bridge/Postmark/Transport/PostmarkSmtpTransport.php

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,11 @@
1212
namespace Symfony\Component\Mailer\Bridge\Postmark\Transport;
1313

1414
use Psr\Log\LoggerInterface;
15+
use Symfony\Component\Mailer\Envelope;
16+
use Symfony\Component\Mailer\SentMessage;
1517
use Symfony\Component\Mailer\Transport\Smtp\EsmtpTransport;
18+
use Symfony\Component\Mime\Message;
19+
use Symfony\Component\Mime\RawMessage;
1620
use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;
1721

1822
/**
@@ -27,4 +31,13 @@ public function __construct(string $id, EventDispatcherInterface $dispatcher = n
2731
$this->setUsername($id);
2832
$this->setPassword($id);
2933
}
34+
35+
public function send(RawMessage $message, Envelope $envelope = null): ?SentMessage
36+
{
37+
if ($message instanceof Message) {
38+
$message->getHeaders()->addTextHeader('X-PM-KeepID', 'true');
39+
}
40+
41+
return parent::send($message, $envelope);
42+
}
3043
}

src/Symfony/Component/Mailer/Bridge/Sendgrid/Tests/Transport/SendgridApiTransportTest.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,10 @@ public function testSend()
5959
->expects($this->once())
6060
->method('getStatusCode')
6161
->willReturn(202);
62+
$response
63+
->expects($this->once())
64+
->method('getHeaders')
65+
->willReturn(['x-message-id' => '1']);
6266

6367
$httpClient = $this->createMock(HttpClientInterface::class);
6468

src/Symfony/Component/Mailer/Bridge/Sendgrid/Transport/SendgridApiTransport.php

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
use Psr\Log\LoggerInterface;
1515
use Symfony\Component\Mailer\Envelope;
1616
use Symfony\Component\Mailer\Exception\HttpTransportException;
17+
use Symfony\Component\Mailer\SentMessage;
1718
use Symfony\Component\Mailer\Transport\AbstractApiTransport;
1819
use Symfony\Component\Mime\Address;
1920
use Symfony\Component\Mime\Email;
@@ -42,7 +43,7 @@ public function __toString(): string
4243
return sprintf('sendgrid+api://%s', $this->getEndpoint());
4344
}
4445

45-
protected function doSendApi(Email $email, Envelope $envelope): ResponseInterface
46+
protected function doSendApi(SentMessage $sentMessage, Email $email, Envelope $envelope): ResponseInterface
4647
{
4748
$response = $this->client->request('POST', 'https://'.$this->getEndpoint().'/v3/mail/send', [
4849
'json' => $this->getPayload($email, $envelope),
@@ -55,6 +56,8 @@ protected function doSendApi(Email $email, Envelope $envelope): ResponseInterfac
5556
throw new HttpTransportException(sprintf('Unable to send an email: %s (code %s).', implode('; ', array_column($errors['errors'], 'message')), $response->getStatusCode()), $response);
5657
}
5758

59+
$sentMessage->setMessageId($response->getHeaders(false)['x-message-id'][0]);
60+
5861
return $response;
5962
}
6063

src/Symfony/Component/Mailer/SentMessage.php

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ class SentMessage
2222
private $original;
2323
private $raw;
2424
private $envelope;
25+
private $messageId;
2526
private $debug = '';
2627

2728
/**
@@ -31,9 +32,20 @@ public function __construct(RawMessage $message, Envelope $envelope)
3132
{
3233
$message->ensureValidity();
3334

34-
$this->raw = $message instanceof Message ? new RawMessage($message->toIterable()) : $message;
3535
$this->original = $message;
3636
$this->envelope = $envelope;
37+
38+
if ($message instanceof Message) {
39+
$message = clone $message;
40+
$headers = $message->getHeaders();
41+
if (!$headers->has('Message-ID')) {
42+
$headers->addIdHeader('Message-ID', $message->generateMessageId());
43+
}
44+
$this->messageId = $headers->get('Message-ID')->getId();
45+
$this->raw = new RawMessage($message->toIterable());
46+
} else {
47+
$this->raw = $message;
48+
}
3749
}
3850

3951
public function getMessage(): RawMessage
@@ -51,6 +63,16 @@ public function getEnvelope(): Envelope
5163
return $this->envelope;
5264
}
5365

66+
public function setMessageId(string $id): void
67+
{
68+
$this->messageId = $id;
69+
}
70+
71+
public function getMessageId(): string
72+
{
73+
return $this->messageId;
74+
}
75+
5476
public function getDebug(): string
5577
{
5678
return $this->debug;

src/Symfony/Component/Mailer/Transport/AbstractApiTransport.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424
*/
2525
abstract class AbstractApiTransport extends AbstractHttpTransport
2626
{
27-
abstract protected function doSendApi(Email $email, Envelope $envelope): ResponseInterface;
27+
abstract protected function doSendApi(SentMessage $sentMessage, Email $email, Envelope $envelope): ResponseInterface;
2828

2929
protected function doSendHttp(SentMessage $message): ResponseInterface
3030
{
@@ -34,7 +34,7 @@ protected function doSendHttp(SentMessage $message): ResponseInterface
3434
throw new RuntimeException(sprintf('Unable to send message with the "%s" transport: %s', __CLASS__, $e->getMessage()), 0, $e);
3535
}
3636

37-
return $this->doSendApi($email, $message->getEnvelope());
37+
return $this->doSendApi($message, $email, $message->getEnvelope());
3838
}
3939

4040
protected function getRecipients(Email $email, Envelope $envelope): array

src/Symfony/Component/Mime/Message.php

Lines changed: 14 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -32,9 +32,7 @@ public function __construct(Headers $headers = null, AbstractPart $body = null)
3232

3333
public function __clone()
3434
{
35-
if (null !== $this->headers) {
36-
$this->headers = clone $this->headers;
37-
}
35+
$this->headers = clone $this->headers;
3836

3937
if (null !== $this->body) {
4038
$this->body = clone $this->body;
@@ -86,16 +84,12 @@ public function getPreparedHeaders(): Headers
8684
}
8785

8886
// determine the "real" sender
89-
$senders = $headers->get('From')->getAddresses();
90-
$sender = $senders[0];
91-
if ($headers->has('Sender')) {
92-
$sender = $headers->get('Sender')->getAddress();
93-
} elseif (\count($senders) > 1) {
94-
$headers->addMailboxHeader('Sender', $sender);
87+
if (!$headers->has('Sender') && \count($froms = $headers->get('From')->getAddresses()) > 1) {
88+
$headers->addMailboxHeader('Sender', $froms[0]);
9589
}
9690

9791
if (!$headers->has('Message-ID')) {
98-
$headers->addIdHeader('Message-ID', $this->generateMessageId($sender->getAddress()));
92+
$headers->addIdHeader('Message-ID', $this->generateMessageId());
9993
}
10094

10195
// remove the Bcc field which should NOT be part of the sent message
@@ -132,9 +126,17 @@ public function ensureValidity()
132126
parent::ensureValidity();
133127
}
134128

135-
private function generateMessageId(string $email): string
129+
public function generateMessageId(): string
136130
{
137-
return bin2hex(random_bytes(16)).strstr($email, '@');
131+
if ($this->headers->has('Sender')) {
132+
$sender = $this->headers->get('Sender')->getAddress();
133+
} elseif ($this->headers->has('From')) {
134+
$sender = $this->headers->get('From')->getAddresses()[0];
135+
} else {
136+
throw new LogicException('An email must have a "From" or a "Sender" header to compute a Messsage ID.');
137+
}
138+
139+
return bin2hex(random_bytes(16)).strstr($sender->getAddress(), '@');
138140
}
139141

140142
public function __serialize(): array

0 commit comments

Comments
 (0)