Add Message-Id to SentMessage when sending an email

This commit is contained in:
Fabien Potencier 2019-10-12 09:08:57 +02:00
parent a84ec3a636
commit b42c269760
12 changed files with 83 additions and 35 deletions

View File

@ -14,6 +14,7 @@ namespace Symfony\Component\Mailer\Bridge\Amazon\Transport;
use Psr\Log\LoggerInterface;
use Symfony\Component\Mailer\Envelope;
use Symfony\Component\Mailer\Exception\HttpTransportException;
use Symfony\Component\Mailer\SentMessage;
use Symfony\Component\Mailer\Transport\AbstractApiTransport;
use Symfony\Component\Mime\Email;
use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;
@ -48,7 +49,7 @@ class SesApiTransport extends AbstractApiTransport
return sprintf('ses+api://%s@%s', $this->accessKey, $this->getEndpoint());
}
protected function doSendApi(Email $email, Envelope $envelope): ResponseInterface
protected function doSendApi(SentMessage $sentMessage, Email $email, Envelope $envelope): ResponseInterface
{
$date = gmdate('D, d M Y H:i:s e');
$auth = sprintf('AWS3-HTTPS AWSAccessKeyId=%s,Algorithm=HmacSHA256,Signature=%s', $this->accessKey, $this->getSignature($date));
@ -62,12 +63,13 @@ class SesApiTransport extends AbstractApiTransport
'body' => $this->getPayload($email, $envelope),
]);
$result = new \SimpleXMLElement($response->getContent(false));
if (200 !== $response->getStatusCode()) {
$error = new \SimpleXMLElement($response->getContent(false));
throw new HttpTransportException(sprintf('Unable to send an email: %s (code %s).', $error->Error->Message, $error->Error->Code), $response);
throw new HttpTransportException(sprintf('Unable to send an email: %s (code %s).', $result->Error->Message, $result->Error->Code), $response);
}
$sentMessage->setMessageId($result->SendEmailResult->MessageId);
return $response;
}

View File

@ -63,12 +63,13 @@ class SesHttpTransport extends AbstractHttpTransport
],
]);
$result = new \SimpleXMLElement($response->getContent(false));
if (200 !== $response->getStatusCode()) {
$error = new \SimpleXMLElement($response->getContent(false));
throw new HttpTransportException(sprintf('Unable to send an email: %s (code %s).', $error->Error->Message, $error->Error->Code), $response);
throw new HttpTransportException(sprintf('Unable to send an email: %s (code %s).', $result->Error->Message, $result->Error->Code), $response);
}
$message->setMessageId($result->SendEmailResult->MessageId);
return $response;
}

View File

@ -14,6 +14,7 @@ namespace Symfony\Component\Mailer\Bridge\Mailchimp\Transport;
use Psr\Log\LoggerInterface;
use Symfony\Component\Mailer\Envelope;
use Symfony\Component\Mailer\Exception\HttpTransportException;
use Symfony\Component\Mailer\SentMessage;
use Symfony\Component\Mailer\Transport\AbstractApiTransport;
use Symfony\Component\Mime\Email;
use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;
@ -41,14 +42,14 @@ class MandrillApiTransport extends AbstractApiTransport
return sprintf('mandrill+api://%s', $this->getEndpoint());
}
protected function doSendApi(Email $email, Envelope $envelope): ResponseInterface
protected function doSendApi(SentMessage $sentMessage, Email $email, Envelope $envelope): ResponseInterface
{
$response = $this->client->request('POST', 'https://'.$this->getEndpoint().'/api/1.0/messages/send.json', [
'json' => $this->getPayload($email, $envelope),
]);
$result = $response->toArray(false);
if (200 !== $response->getStatusCode()) {
$result = $response->toArray(false);
if ('error' === ($result['status'] ?? false)) {
throw new HttpTransportException(sprintf('Unable to send an email: %s (code %s).', $result['message'], $result['code']), $response);
}
@ -56,6 +57,8 @@ class MandrillApiTransport extends AbstractApiTransport
throw new HttpTransportException(sprintf('Unable to send an email (code %s).', $result['code']), $response);
}
$sentMessage->setMessageId($result['_id']);
return $response;
}

View File

@ -51,8 +51,8 @@ class MandrillHttpTransport extends AbstractHttpTransport
],
]);
$result = $response->toArray(false);
if (200 !== $response->getStatusCode()) {
$result = $response->toArray(false);
if ('error' === ($result['status'] ?? false)) {
throw new HttpTransportException(sprintf('Unable to send an email: %s (code %s).', $result['message'], $result['code']), $response);
}
@ -60,6 +60,8 @@ class MandrillHttpTransport extends AbstractHttpTransport
throw new HttpTransportException(sprintf('Unable to send an email (code %s).', $result['code']), $response);
}
$message->setMessageId($result['_id']);
return $response;
}

View File

@ -14,6 +14,7 @@ namespace Symfony\Component\Mailer\Bridge\Mailgun\Transport;
use Psr\Log\LoggerInterface;
use Symfony\Component\Mailer\Envelope;
use Symfony\Component\Mailer\Exception\HttpTransportException;
use Symfony\Component\Mailer\SentMessage;
use Symfony\Component\Mailer\Transport\AbstractApiTransport;
use Symfony\Component\Mime\Email;
use Symfony\Component\Mime\Part\Multipart\FormDataPart;
@ -46,7 +47,7 @@ class MailgunApiTransport extends AbstractApiTransport
return sprintf('mailgun+api://%s?domain=%s', $this->getEndpoint(), $this->domain);
}
protected function doSendApi(Email $email, Envelope $envelope): ResponseInterface
protected function doSendApi(SentMessage $sentMessage, Email $email, Envelope $envelope): ResponseInterface
{
$body = new FormDataPart($this->getPayload($email, $envelope));
$headers = [];
@ -61,14 +62,17 @@ class MailgunApiTransport extends AbstractApiTransport
'body' => $body->bodyToIterable(),
]);
$result = $response->toArray(false);
if (200 !== $response->getStatusCode()) {
if ('application/json' === $response->getHeaders(false)['content-type'][0]) {
throw new HttpTransportException(sprintf('Unable to send an email: %s (code %s).', $response->toArray(false)['message'], $response->getStatusCode()), $response);
throw new HttpTransportException(sprintf('Unable to send an email: %s (code %s).', $result['message'], $response->getStatusCode()), $response);
}
throw new HttpTransportException(sprintf('Unable to send an email: %s (code %s).', $response->getContent(false), $response->getStatusCode()), $response);
throw new HttpTransportException(sprintf('Unable to send an email: %s (code %s).', $result, $response->getStatusCode()), $response);
}
$sentMessage->setMessageId($result['id']);
return $response;
}

View File

@ -64,14 +64,17 @@ class MailgunHttpTransport extends AbstractHttpTransport
'body' => $body->bodyToIterable(),
]);
$result = $response->toArray(false);
if (200 !== $response->getStatusCode()) {
if ('application/json' === $response->getHeaders(false)['content-type'][0]) {
throw new HttpTransportException(sprintf('Unable to send an email: %s (code %s).', $response->toArray(false)['message'], $response->getStatusCode()), $response);
throw new HttpTransportException(sprintf('Unable to send an email: %s (code %s).', $result['message'], $response->getStatusCode()), $response);
}
throw new HttpTransportException(sprintf('Unable to send an email: %s (code %s).', $response->getContent(false), $response->getStatusCode()), $response);
throw new HttpTransportException(sprintf('Unable to send an email: %s (code %s).', $result, $response->getStatusCode()), $response);
}
$message->setMessageId($result['id']);
return $response;
}

View File

@ -14,6 +14,7 @@ namespace Symfony\Component\Mailer\Bridge\Postmark\Transport;
use Psr\Log\LoggerInterface;
use Symfony\Component\Mailer\Envelope;
use Symfony\Component\Mailer\Exception\HttpTransportException;
use Symfony\Component\Mailer\SentMessage;
use Symfony\Component\Mailer\Transport\AbstractApiTransport;
use Symfony\Component\Mime\Email;
use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;
@ -41,7 +42,7 @@ class PostmarkApiTransport extends AbstractApiTransport
return sprintf('postmark+api://%s', $this->getEndpoint());
}
protected function doSendApi(Email $email, Envelope $envelope): ResponseInterface
protected function doSendApi(SentMessage $sentMessage, Email $email, Envelope $envelope): ResponseInterface
{
$response = $this->client->request('POST', 'https://'.$this->getEndpoint().'/email', [
'headers' => [
@ -51,12 +52,13 @@ class PostmarkApiTransport extends AbstractApiTransport
'json' => $this->getPayload($email, $envelope),
]);
$result = $response->toArray(false);
if (200 !== $response->getStatusCode()) {
$error = $response->toArray(false);
throw new HttpTransportException(sprintf('Unable to send an email: %s (code %s).', $error['Message'], $error['ErrorCode']), $response);
throw new HttpTransportException(sprintf('Unable to send an email: %s (code %s).', $result['Message'], $result['ErrorCode']), $response);
}
$sentMessage->setMessageId($result['MessageID']);
return $response;
}

View File

@ -59,6 +59,10 @@ class SendgridApiTransportTest extends TestCase
->expects($this->once())
->method('getStatusCode')
->willReturn(202);
$response
->expects($this->once())
->method('getHeaders')
->willReturn(['x-message-id' => '1']);
$httpClient = $this->createMock(HttpClientInterface::class);

View File

@ -14,6 +14,7 @@ namespace Symfony\Component\Mailer\Bridge\Sendgrid\Transport;
use Psr\Log\LoggerInterface;
use Symfony\Component\Mailer\Envelope;
use Symfony\Component\Mailer\Exception\HttpTransportException;
use Symfony\Component\Mailer\SentMessage;
use Symfony\Component\Mailer\Transport\AbstractApiTransport;
use Symfony\Component\Mime\Address;
use Symfony\Component\Mime\Email;
@ -42,7 +43,7 @@ class SendgridApiTransport extends AbstractApiTransport
return sprintf('sendgrid+api://%s', $this->getEndpoint());
}
protected function doSendApi(Email $email, Envelope $envelope): ResponseInterface
protected function doSendApi(SentMessage $sentMessage, Email $email, Envelope $envelope): ResponseInterface
{
$response = $this->client->request('POST', 'https://'.$this->getEndpoint().'/v3/mail/send', [
'json' => $this->getPayload($email, $envelope),
@ -55,6 +56,8 @@ class SendgridApiTransport extends AbstractApiTransport
throw new HttpTransportException(sprintf('Unable to send an email: %s (code %s).', implode('; ', array_column($errors['errors'], 'message')), $response->getStatusCode()), $response);
}
$sentMessage->setMessageId($response->getHeaders(false)['x-message-id'][0]);
return $response;
}

View File

@ -22,6 +22,7 @@ class SentMessage
private $original;
private $raw;
private $envelope;
private $messageId;
private $debug = '';
/**
@ -31,9 +32,20 @@ class SentMessage
{
$message->ensureValidity();
$this->raw = $message instanceof Message ? new RawMessage($message->toIterable()) : $message;
$this->original = $message;
$this->envelope = $envelope;
if ($message instanceof Message) {
$message = clone $message;
$headers = $message->getHeaders();
if (!$headers->has('Message-ID')) {
$headers->addIdHeader('Message-ID', $message->generateMessageId());
}
$this->messageId = $headers->get('Message-ID')->getId();
$this->raw = new RawMessage($message->toIterable());
} else {
$this->raw = $message;
}
}
public function getMessage(): RawMessage
@ -51,6 +63,16 @@ class SentMessage
return $this->envelope;
}
public function setMessageId(string $id): void
{
$this->messageId = $id;
}
public function getMessageId(): string
{
return $this->messageId;
}
public function getDebug(): string
{
return $this->debug;

View File

@ -24,7 +24,7 @@ use Symfony\Contracts\HttpClient\ResponseInterface;
*/
abstract class AbstractApiTransport extends AbstractHttpTransport
{
abstract protected function doSendApi(Email $email, Envelope $envelope): ResponseInterface;
abstract protected function doSendApi(SentMessage $sentMessage, Email $email, Envelope $envelope): ResponseInterface;
protected function doSendHttp(SentMessage $message): ResponseInterface
{
@ -34,7 +34,7 @@ abstract class AbstractApiTransport extends AbstractHttpTransport
throw new RuntimeException(sprintf('Unable to send message with the "%s" transport: %s', __CLASS__, $e->getMessage()), 0, $e);
}
return $this->doSendApi($email, $message->getEnvelope());
return $this->doSendApi($message, $email, $message->getEnvelope());
}
protected function getRecipients(Email $email, Envelope $envelope): array

View File

@ -32,9 +32,7 @@ class Message extends RawMessage
public function __clone()
{
if (null !== $this->headers) {
$this->headers = clone $this->headers;
}
$this->headers = clone $this->headers;
if (null !== $this->body) {
$this->body = clone $this->body;
@ -86,16 +84,12 @@ class Message extends RawMessage
}
// determine the "real" sender
$senders = $headers->get('From')->getAddresses();
$sender = $senders[0];
if ($headers->has('Sender')) {
$sender = $headers->get('Sender')->getAddress();
} elseif (\count($senders) > 1) {
$headers->addMailboxHeader('Sender', $sender);
if (!$headers->has('Sender') && \count($froms = $headers->get('From')->getAddresses()) > 1) {
$headers->addMailboxHeader('Sender', $froms[0]);
}
if (!$headers->has('Message-ID')) {
$headers->addIdHeader('Message-ID', $this->generateMessageId($sender->getAddress()));
$headers->addIdHeader('Message-ID', $this->generateMessageId());
}
// remove the Bcc field which should NOT be part of the sent message
@ -132,9 +126,17 @@ class Message extends RawMessage
parent::ensureValidity();
}
private function generateMessageId(string $email): string
public function generateMessageId(): string
{
return bin2hex(random_bytes(16)).strstr($email, '@');
if ($this->headers->has('Sender')) {
$sender = $this->headers->get('Sender')->getAddress();
} elseif ($this->headers->has('From')) {
$sender = $this->headers->get('From')->getAddresses()[0];
} else {
throw new LogicException('An email must have a "From" or a "Sender" header to compute a Messsage ID.');
}
return bin2hex(random_bytes(16)).strstr($sender->getAddress(), '@');
}
public function __serialize(): array