From a36fec32048940dbc0dfcc225ab0393dda6d6574 Mon Sep 17 00:00:00 2001 From: Clara van Miert Date: Thu, 20 Aug 2020 18:03:09 +0200 Subject: [PATCH] [Mailer] Support Amazon SES ConfigurationSetName In Amazon SES a Configuration Set can be used to monitor email sending events (delivery, bounces, complaints etc.). In order to use this feature the ConfigurationSetName needs to be sent along with the email. Setting the `X-SES-CONFIGURATION-SET` header should accomplish this for all SES Transports now. Ref: https://docs.aws.amazon.com/ses/latest/DeveloperGuide/using-configuration-sets-in-email.html --- .../Transport/SesApiAsyncAwsTransportTest.php | 3 ++ .../Tests/Transport/SesApiTransportTest.php | 48 +++++++++++++++++++ .../SesHttpAsyncAwsTransportTest.php | 3 ++ .../Tests/Transport/SesHttpTransportTest.php | 4 ++ .../Transport/SesApiAsyncAwsTransport.php | 3 ++ .../Amazon/Transport/SesApiTransport.php | 11 ++++- .../Transport/SesHttpAsyncAwsTransport.php | 12 ++++- .../Amazon/Transport/SesHttpTransport.php | 12 ++++- 8 files changed, 91 insertions(+), 5 deletions(-) diff --git a/src/Symfony/Component/Mailer/Bridge/Amazon/Tests/Transport/SesApiAsyncAwsTransportTest.php b/src/Symfony/Component/Mailer/Bridge/Amazon/Tests/Transport/SesApiAsyncAwsTransportTest.php index 88e1bd98cc..2cd87cf96b 100644 --- a/src/Symfony/Component/Mailer/Bridge/Amazon/Tests/Transport/SesApiAsyncAwsTransportTest.php +++ b/src/Symfony/Component/Mailer/Bridge/Amazon/Tests/Transport/SesApiAsyncAwsTransportTest.php @@ -69,6 +69,7 @@ class SesApiAsyncAwsTransportTest extends TestCase $this->assertSame('Hello There!', $content['Content']['Simple']['Body']['Text']['Data']); $this->assertSame('Hello There!', $content['Content']['Simple']['Body']['Html']['Data']); $this->assertSame(['replyto-1@example.com', 'replyto-2@example.com'], $content['ReplyToAddresses']); + $this->assertSame('aws-configuration-set-name', $content['ConfigurationSetName']); $json = '{"MessageId": "foobar"}'; @@ -87,6 +88,8 @@ class SesApiAsyncAwsTransportTest extends TestCase ->html('Hello There!') ->replyTo(new Address('replyto-1@example.com'), new Address('replyto-2@example.com')); + $mail->getHeaders()->addTextHeader('X-SES-CONFIGURATION-SET', 'aws-configuration-set-name'); + $message = $transport->send($mail); $this->assertSame('foobar', $message->getMessageId()); diff --git a/src/Symfony/Component/Mailer/Bridge/Amazon/Tests/Transport/SesApiTransportTest.php b/src/Symfony/Component/Mailer/Bridge/Amazon/Tests/Transport/SesApiTransportTest.php index 2a4adfa418..b4dfa191ae 100644 --- a/src/Symfony/Component/Mailer/Bridge/Amazon/Tests/Transport/SesApiTransportTest.php +++ b/src/Symfony/Component/Mailer/Bridge/Amazon/Tests/Transport/SesApiTransportTest.php @@ -68,6 +68,7 @@ class SesApiTransportTest extends TestCase $this->assertSame('Saif Eddin ', $content['Destination_ToAddresses_member'][0]); $this->assertSame('Fabien ', $content['Source']); $this->assertSame('Hello There!', $content['Message_Body_Text_Data']); + $this->assertSame('aws-configuration-set-name', $content['ConfigurationSetName']); $xml = ' @@ -88,6 +89,53 @@ class SesApiTransportTest extends TestCase ->from(new Address('fabpot@symfony.com', 'Fabien')) ->text('Hello There!'); + $mail->getHeaders()->addTextHeader('X-SES-CONFIGURATION-SET', 'aws-configuration-set-name'); + + $message = $transport->send($mail); + + $this->assertSame('foobar', $message->getMessageId()); + } + + public function testSendWithAttachments() + { + $client = new MockHttpClient(function (string $method, string $url, array $options): ResponseInterface { + $this->assertSame('POST', $method); + $this->assertSame('https://email.eu-west-1.amazonaws.com:8984/', $url); + $this->assertStringContainsStringIgnoringCase('X-Amzn-Authorization: AWS3-HTTPS AWSAccessKeyId=ACCESS_KEY,Algorithm=HmacSHA256,Signature=', $options['headers'][0] ?? $options['request_headers'][0]); + + parse_str($options['body'], $body); + $content = base64_decode($body['RawMessage_Data']); + + $this->assertStringContainsString('Hello!', $content); + $this->assertStringContainsString('Saif Eddin ', $content); + $this->assertStringContainsString('Fabien ', $content); + $this->assertStringContainsString('Hello There!', $content); + $this->assertStringContainsString(base64_encode('attached data'), $content); + + $this->assertSame('aws-configuration-set-name', $body['ConfigurationSetName']); + + $xml = ' + + foobar + +'; + + return new MockResponse($xml, [ + 'http_code' => 200, + ]); + }); + $transport = new SesApiTransport('ACCESS_KEY', 'SECRET_KEY', null, $client); + $transport->setPort(8984); + + $mail = new Email(); + $mail->subject('Hello!') + ->to(new Address('saif.gmati@symfony.com', 'Saif Eddin')) + ->from(new Address('fabpot@symfony.com', 'Fabien')) + ->text('Hello There!') + ->attach('attached data'); + + $mail->getHeaders()->addTextHeader('X-SES-CONFIGURATION-SET', 'aws-configuration-set-name'); + $message = $transport->send($mail); $this->assertSame('foobar', $message->getMessageId()); diff --git a/src/Symfony/Component/Mailer/Bridge/Amazon/Tests/Transport/SesHttpAsyncAwsTransportTest.php b/src/Symfony/Component/Mailer/Bridge/Amazon/Tests/Transport/SesHttpAsyncAwsTransportTest.php index ff3a6e23ad..5b79491fbc 100644 --- a/src/Symfony/Component/Mailer/Bridge/Amazon/Tests/Transport/SesHttpAsyncAwsTransportTest.php +++ b/src/Symfony/Component/Mailer/Bridge/Amazon/Tests/Transport/SesHttpAsyncAwsTransportTest.php @@ -68,6 +68,7 @@ class SesHttpAsyncAwsTransportTest extends TestCase $this->assertStringContainsString('Saif Eddin ', $content); $this->assertStringContainsString('Fabien ', $content); $this->assertStringContainsString('Hello There!', $content); + $this->assertSame('aws-configuration-set-name', $body['ConfigurationSetName']); $json = '{"MessageId": "foobar"}'; @@ -84,6 +85,8 @@ class SesHttpAsyncAwsTransportTest extends TestCase ->from(new Address('fabpot@symfony.com', 'Fabien')) ->text('Hello There!'); + $mail->getHeaders()->addTextHeader('X-SES-CONFIGURATION-SET', 'aws-configuration-set-name'); + $message = $transport->send($mail); $this->assertSame('foobar', $message->getMessageId()); diff --git a/src/Symfony/Component/Mailer/Bridge/Amazon/Tests/Transport/SesHttpTransportTest.php b/src/Symfony/Component/Mailer/Bridge/Amazon/Tests/Transport/SesHttpTransportTest.php index 994990443d..e1f28be824 100644 --- a/src/Symfony/Component/Mailer/Bridge/Amazon/Tests/Transport/SesHttpTransportTest.php +++ b/src/Symfony/Component/Mailer/Bridge/Amazon/Tests/Transport/SesHttpTransportTest.php @@ -70,6 +70,8 @@ class SesHttpTransportTest extends TestCase $this->assertStringContainsString('Fabien ', $content); $this->assertStringContainsString('Hello There!', $content); + $this->assertSame('aws-configuration-set-name', $body['ConfigurationSetName']); + $xml = ' foobar @@ -89,6 +91,8 @@ class SesHttpTransportTest extends TestCase ->from(new Address('fabpot@symfony.com', 'Fabien')) ->text('Hello There!'); + $mail->getHeaders()->addTextHeader('X-SES-CONFIGURATION-SET', 'aws-configuration-set-name'); + $message = $transport->send($mail); $this->assertSame('foobar', $message->getMessageId()); diff --git a/src/Symfony/Component/Mailer/Bridge/Amazon/Transport/SesApiAsyncAwsTransport.php b/src/Symfony/Component/Mailer/Bridge/Amazon/Transport/SesApiAsyncAwsTransport.php index e7878ccc8b..9c03fe3744 100644 --- a/src/Symfony/Component/Mailer/Bridge/Amazon/Transport/SesApiAsyncAwsTransport.php +++ b/src/Symfony/Component/Mailer/Bridge/Amazon/Transport/SesApiAsyncAwsTransport.php @@ -89,6 +89,9 @@ class SesApiAsyncAwsTransport extends SesHttpAsyncAwsTransport if ($emails = $email->getReplyTo()) { $request['ReplyToAddresses'] = $this->stringifyAddresses($emails); } + if ($header = $email->getHeaders()->get('X-SES-CONFIGURATION-SET')) { + $request['ConfigurationSetName'] = $header->getBodyAsString(); + } return new SendEmailRequest($request); } diff --git a/src/Symfony/Component/Mailer/Bridge/Amazon/Transport/SesApiTransport.php b/src/Symfony/Component/Mailer/Bridge/Amazon/Transport/SesApiTransport.php index 45ccd65cdf..b872be52c6 100644 --- a/src/Symfony/Component/Mailer/Bridge/Amazon/Transport/SesApiTransport.php +++ b/src/Symfony/Component/Mailer/Bridge/Amazon/Transport/SesApiTransport.php @@ -90,10 +90,16 @@ class SesApiTransport extends AbstractApiTransport private function getPayload(Email $email, Envelope $envelope): array { if ($email->getAttachments()) { - return [ + $payload = [ 'Action' => 'SendRawEmail', 'RawMessage.Data' => base64_encode($email->toString()), ]; + + if ($header = $email->getHeaders()->get('X-SES-CONFIGURATION-SET')) { + $payload['ConfigurationSetName'] = $header->getBodyAsString(); + } + + return $payload; } $payload = [ @@ -118,6 +124,9 @@ class SesApiTransport extends AbstractApiTransport if ($email->getReplyTo()) { $payload['ReplyToAddresses.member'] = $this->stringifyAddresses($email->getReplyTo()); } + if ($header = $email->getHeaders()->get('X-SES-CONFIGURATION-SET')) { + $payload['ConfigurationSetName'] = $header->getBodyAsString(); + } return $payload; } diff --git a/src/Symfony/Component/Mailer/Bridge/Amazon/Transport/SesHttpAsyncAwsTransport.php b/src/Symfony/Component/Mailer/Bridge/Amazon/Transport/SesHttpAsyncAwsTransport.php index 284e56b331..58ae25e792 100644 --- a/src/Symfony/Component/Mailer/Bridge/Amazon/Transport/SesHttpAsyncAwsTransport.php +++ b/src/Symfony/Component/Mailer/Bridge/Amazon/Transport/SesHttpAsyncAwsTransport.php @@ -19,6 +19,7 @@ use Psr\Log\LoggerInterface; use Symfony\Component\Mailer\Exception\HttpTransportException; use Symfony\Component\Mailer\SentMessage; use Symfony\Component\Mailer\Transport\AbstractTransport; +use Symfony\Component\Mime\Message; use Symfony\Contracts\EventDispatcher\EventDispatcherInterface; /** @@ -67,7 +68,7 @@ class SesHttpAsyncAwsTransport extends AbstractTransport protected function getRequest(SentMessage $message): SendEmailRequest { - return new SendEmailRequest([ + $request = [ 'Destination' => new Destination([ 'ToAddresses' => $this->stringifyAddresses($message->getEnvelope()->getRecipients()), ]), @@ -76,6 +77,13 @@ class SesHttpAsyncAwsTransport extends AbstractTransport 'Data' => $message->toString(), ], ], - ]); + ]; + + if (($message->getOriginalMessage() instanceof Message) + && $configurationSetHeader = $message->getOriginalMessage()->getHeaders()->get('X-SES-CONFIGURATION-SET')) { + $request['ConfigurationSetName'] = $configurationSetHeader->getBodyAsString(); + } + + return new SendEmailRequest($request); } } diff --git a/src/Symfony/Component/Mailer/Bridge/Amazon/Transport/SesHttpTransport.php b/src/Symfony/Component/Mailer/Bridge/Amazon/Transport/SesHttpTransport.php index e3fefd4583..20af6c519a 100644 --- a/src/Symfony/Component/Mailer/Bridge/Amazon/Transport/SesHttpTransport.php +++ b/src/Symfony/Component/Mailer/Bridge/Amazon/Transport/SesHttpTransport.php @@ -15,6 +15,7 @@ use Psr\Log\LoggerInterface; use Symfony\Component\Mailer\Exception\HttpTransportException; use Symfony\Component\Mailer\SentMessage; use Symfony\Component\Mailer\Transport\AbstractHttpTransport; +use Symfony\Component\Mime\Message; use Symfony\Contracts\EventDispatcher\EventDispatcherInterface; use Symfony\Contracts\HttpClient\HttpClientInterface; use Symfony\Contracts\HttpClient\ResponseInterface; @@ -54,7 +55,7 @@ class SesHttpTransport extends AbstractHttpTransport $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)); - $response = $this->client->request('POST', 'https://'.$this->getEndpoint(), [ + $request = [ 'headers' => [ 'X-Amzn-Authorization' => $auth, 'Date' => $date, @@ -63,7 +64,14 @@ class SesHttpTransport extends AbstractHttpTransport 'Action' => 'SendRawEmail', 'RawMessage.Data' => base64_encode($message->toString()), ], - ]); + ]; + + if (($message->getOriginalMessage() instanceof Message) + && $configurationSetHeader = $message->getOriginalMessage()->getHeaders()->get('X-SES-CONFIGURATION-SET')) { + $request['body']['ConfigurationSetName'] = $configurationSetHeader->getBodyAsString(); + } + + $response = $this->client->request('POST', 'https://'.$this->getEndpoint(), $request); $result = new \SimpleXMLElement($response->getContent(false)); if (200 !== $response->getStatusCode()) {