feature #37897 [Mailer] Support Amazon SES ConfigurationSetName (cvmiert)

This PR was merged into the 5.2-dev branch.

Discussion
----------

[Mailer] Support Amazon SES ConfigurationSetName

| Q             | A
| ------------- | ---
| Branch?       | master
| Bug fix?      | no
| New feature?  | yes
| Deprecations? | no
| Tickets       | N/A
| License       | MIT
| Doc PR        | N/A

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

Commits
-------

a36fec3204 [Mailer] Support Amazon SES ConfigurationSetName
This commit is contained in:
Fabien Potencier 2020-08-22 08:28:39 +02:00
commit f1d1514793
8 changed files with 91 additions and 5 deletions

View File

@ -69,6 +69,7 @@ class SesApiAsyncAwsTransportTest extends TestCase
$this->assertSame('Hello There!', $content['Content']['Simple']['Body']['Text']['Data']);
$this->assertSame('<b>Hello There!</b>', $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('<b>Hello There!</b>')
->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());

View File

@ -68,6 +68,7 @@ class SesApiTransportTest extends TestCase
$this->assertSame('Saif Eddin <saif.gmati@symfony.com>', $content['Destination_ToAddresses_member'][0]);
$this->assertSame('Fabien <fabpot@symfony.com>', $content['Source']);
$this->assertSame('Hello There!', $content['Message_Body_Text_Data']);
$this->assertSame('aws-configuration-set-name', $content['ConfigurationSetName']);
$xml = '<SendEmailResponse xmlns="https://email.amazonaws.com/doc/2010-03-31/">
<SendEmailResult>
@ -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 <saif.gmati@symfony.com>', $content);
$this->assertStringContainsString('Fabien <fabpot@symfony.com>', $content);
$this->assertStringContainsString('Hello There!', $content);
$this->assertStringContainsString(base64_encode('attached data'), $content);
$this->assertSame('aws-configuration-set-name', $body['ConfigurationSetName']);
$xml = '<SendEmailResponse xmlns="https://email.amazonaws.com/doc/2010-03-31/">
<SendRawEmailResult>
<MessageId>foobar</MessageId>
</SendRawEmailResult>
</SendEmailResponse>';
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());

View File

@ -68,6 +68,7 @@ class SesHttpAsyncAwsTransportTest extends TestCase
$this->assertStringContainsString('Saif Eddin <saif.gmati@symfony.com>', $content);
$this->assertStringContainsString('Fabien <fabpot@symfony.com>', $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());

View File

@ -70,6 +70,8 @@ class SesHttpTransportTest extends TestCase
$this->assertStringContainsString('Fabien <fabpot@symfony.com>', $content);
$this->assertStringContainsString('Hello There!', $content);
$this->assertSame('aws-configuration-set-name', $body['ConfigurationSetName']);
$xml = '<SendEmailResponse xmlns="https://email.amazonaws.com/doc/2010-03-31/">
<SendRawEmailResult>
<MessageId>foobar</MessageId>
@ -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());

View File

@ -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);
}

View File

@ -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;
}

View File

@ -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);
}
}

View File

@ -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()) {