[Mailer] Use AsyncAws to handle SES requests
This commit is contained in:
parent
c6cf433589
commit
21243874bc
|
@ -72,6 +72,8 @@ Mailer
|
|||
------
|
||||
|
||||
* Deprecated passing Mailgun headers without their "h:" prefix.
|
||||
* Deprecated the `SesApiTransport` class. It has been replaced by SesApiAsyncAwsTransport Run `composer require async-aws/ses` to use the new classes.
|
||||
* Deprecated the `SesHttpTransport` class. It has been replaced by SesHttpAsyncAwsTransport Run `composer require async-aws/ses` to use the new classes.
|
||||
|
||||
Messenger
|
||||
---------
|
||||
|
|
|
@ -64,6 +64,13 @@ HttpKernel
|
|||
* Made `WarmableInterface::warmUp()` return a list of classes or files to preload on PHP 7.4+
|
||||
* Removed support for `service:action` syntax to reference controllers. Use `serviceOrFqcn::method` instead.
|
||||
|
||||
|
||||
Mailer
|
||||
------
|
||||
|
||||
* Removed the `SesApiTransport` class. Use `SesApiAsyncAwsTransport` instead.
|
||||
* Removed the `SesHttpTransport` class. Use `SesHttpAsyncAwsTransport` instead.
|
||||
|
||||
Messenger
|
||||
---------
|
||||
|
||||
|
|
|
@ -104,6 +104,7 @@
|
|||
"require-dev": {
|
||||
"amphp/http-client": "^4.2",
|
||||
"amphp/http-tunnel": "^1.0",
|
||||
"async-aws/ses": "^1.0",
|
||||
"cache/integration-tests": "dev-master",
|
||||
"doctrine/annotations": "~1.0",
|
||||
"doctrine/cache": "~1.6",
|
||||
|
|
|
@ -1,6 +1,11 @@
|
|||
CHANGELOG
|
||||
=========
|
||||
|
||||
5.1.0
|
||||
-----
|
||||
|
||||
* Added `async-aws/ses` to communicate with AWS API.
|
||||
|
||||
4.4.0
|
||||
-----
|
||||
|
||||
|
|
|
@ -0,0 +1,120 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\Mailer\Bridge\Amazon\Tests\Transport;
|
||||
|
||||
use AsyncAws\Core\Configuration;
|
||||
use AsyncAws\Core\Credentials\NullProvider;
|
||||
use AsyncAws\Ses\SesClient;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Symfony\Component\HttpClient\MockHttpClient;
|
||||
use Symfony\Component\HttpClient\Response\MockResponse;
|
||||
use Symfony\Component\Mailer\Bridge\Amazon\Transport\SesApiAsyncAwsTransport;
|
||||
use Symfony\Component\Mailer\Exception\HttpTransportException;
|
||||
use Symfony\Component\Mime\Address;
|
||||
use Symfony\Component\Mime\Email;
|
||||
use Symfony\Contracts\HttpClient\ResponseInterface;
|
||||
|
||||
class SesApiAsyncAwsTransportTest extends TestCase
|
||||
{
|
||||
/**
|
||||
* @dataProvider getTransportData
|
||||
*/
|
||||
public function testToString(SesApiAsyncAwsTransport $transport, string $expected)
|
||||
{
|
||||
$this->assertSame($expected, (string) $transport);
|
||||
}
|
||||
|
||||
public function getTransportData()
|
||||
{
|
||||
return [
|
||||
[
|
||||
new SesApiAsyncAwsTransport(new SesClient(Configuration::create(['accessKeyId' => 'ACCESS_KEY', 'accessKeySecret' => 'SECRET_KEY']))),
|
||||
'ses+api://ACCESS_KEY@us-east-1',
|
||||
],
|
||||
[
|
||||
new SesApiAsyncAwsTransport(new SesClient(Configuration::create(['accessKeyId' => 'ACCESS_KEY', 'accessKeySecret' => 'SECRET_KEY', 'region' => 'us-west-1']))),
|
||||
'ses+api://ACCESS_KEY@us-west-1',
|
||||
],
|
||||
[
|
||||
new SesApiAsyncAwsTransport(new SesClient(Configuration::create(['accessKeyId' => 'ACCESS_KEY', 'accessKeySecret' => 'SECRET_KEY', 'endpoint' => 'https://example.com']))),
|
||||
'ses+api://ACCESS_KEY@example.com',
|
||||
],
|
||||
[
|
||||
new SesApiAsyncAwsTransport(new SesClient(Configuration::create(['accessKeyId' => 'ACCESS_KEY', 'accessKeySecret' => 'SECRET_KEY', 'endpoint' => 'https://example.com:99']))),
|
||||
'ses+api://ACCESS_KEY@example.com:99',
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
public function testSend()
|
||||
{
|
||||
$client = new MockHttpClient(function (string $method, string $url, array $options): ResponseInterface {
|
||||
$this->assertSame('POST', $method);
|
||||
$this->assertSame('https://email.us-east-1.amazonaws.com/v2/email/outbound-emails', $url);
|
||||
|
||||
$content = json_decode($options['body'], true);
|
||||
|
||||
$this->assertSame('Hello!', $content['Content']['Simple']['Subject']['Data']);
|
||||
$this->assertSame('Saif Eddin <saif.gmati@symfony.com>', $content['Destination']['ToAddresses'][0]);
|
||||
$this->assertSame('Fabien <fabpot@symfony.com>', $content['FromEmailAddress']);
|
||||
$this->assertSame('Hello There!', $content['Content']['Simple']['Body']['Text']['Data']);
|
||||
$this->assertSame('<b>Hello There!</b>', $content['Content']['Simple']['Body']['Html']['Data']);
|
||||
|
||||
$json = '{"MessageId": "foobar"}';
|
||||
|
||||
return new MockResponse($json, [
|
||||
'http_code' => 200,
|
||||
]);
|
||||
});
|
||||
|
||||
$transport = new SesApiAsyncAwsTransport(new SesClient(Configuration::create([]), new NullProvider(), $client));
|
||||
|
||||
$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!')
|
||||
->html('<b>Hello There!</b>');
|
||||
|
||||
$message = $transport->send($mail);
|
||||
|
||||
$this->assertSame('foobar', $message->getMessageId());
|
||||
}
|
||||
|
||||
public function testSendThrowsForErrorResponse()
|
||||
{
|
||||
$client = new MockHttpClient(function (string $method, string $url, array $options): ResponseInterface {
|
||||
$xml = "<SendEmailResponse xmlns=\"https://email.amazonaws.com/doc/2010-03-31/\">
|
||||
<Error>
|
||||
<Message>i'm a teapot</Message>
|
||||
<Code>418</Code>
|
||||
</Error>
|
||||
</SendEmailResponse>";
|
||||
|
||||
return new MockResponse($xml, [
|
||||
'http_code' => 418,
|
||||
]);
|
||||
});
|
||||
|
||||
$transport = new SesApiAsyncAwsTransport(new SesClient(Configuration::create([]), new NullProvider(), $client));
|
||||
|
||||
$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!');
|
||||
|
||||
$this->expectException(HttpTransportException::class);
|
||||
$this->expectExceptionMessage('Unable to send an email: i\'m a teapot (code 418).');
|
||||
$transport->send($mail);
|
||||
}
|
||||
}
|
|
@ -20,6 +20,9 @@ use Symfony\Component\Mime\Address;
|
|||
use Symfony\Component\Mime\Email;
|
||||
use Symfony\Contracts\HttpClient\ResponseInterface;
|
||||
|
||||
/**
|
||||
* @group legacy
|
||||
*/
|
||||
class SesApiTransportTest extends TestCase
|
||||
{
|
||||
/**
|
||||
|
|
|
@ -0,0 +1,119 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\Mailer\Bridge\Amazon\Tests\Transport;
|
||||
|
||||
use AsyncAws\Core\Configuration;
|
||||
use AsyncAws\Core\Credentials\NullProvider;
|
||||
use AsyncAws\Ses\SesClient;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Symfony\Component\HttpClient\MockHttpClient;
|
||||
use Symfony\Component\HttpClient\Response\MockResponse;
|
||||
use Symfony\Component\Mailer\Bridge\Amazon\Transport\SesHttpAsyncAwsTransport;
|
||||
use Symfony\Component\Mailer\Exception\HttpTransportException;
|
||||
use Symfony\Component\Mime\Address;
|
||||
use Symfony\Component\Mime\Email;
|
||||
use Symfony\Contracts\HttpClient\ResponseInterface;
|
||||
|
||||
class SesHttpAsyncAwsTransportTest extends TestCase
|
||||
{
|
||||
/**
|
||||
* @dataProvider getTransportData
|
||||
*/
|
||||
public function testToString(SesHttpAsyncAwsTransport $transport, string $expected)
|
||||
{
|
||||
$this->assertSame($expected, (string) $transport);
|
||||
}
|
||||
|
||||
public function getTransportData()
|
||||
{
|
||||
return [
|
||||
[
|
||||
new SesHttpAsyncAwsTransport(new SesClient(Configuration::create(['accessKeyId' => 'ACCESS_KEY', 'accessKeySecret' => 'SECRET_KEY']))),
|
||||
'ses+https://ACCESS_KEY@us-east-1',
|
||||
],
|
||||
[
|
||||
new SesHttpAsyncAwsTransport(new SesClient(Configuration::create(['accessKeyId' => 'ACCESS_KEY', 'accessKeySecret' => 'SECRET_KEY', 'region' => 'us-west-1']))),
|
||||
'ses+https://ACCESS_KEY@us-west-1',
|
||||
],
|
||||
[
|
||||
new SesHttpAsyncAwsTransport(new SesClient(Configuration::create(['accessKeyId' => 'ACCESS_KEY', 'accessKeySecret' => 'SECRET_KEY', 'endpoint' => 'https://example.com']))),
|
||||
'ses+https://ACCESS_KEY@example.com',
|
||||
],
|
||||
[
|
||||
new SesHttpAsyncAwsTransport(new SesClient(Configuration::create(['accessKeyId' => 'ACCESS_KEY', 'accessKeySecret' => 'SECRET_KEY', 'endpoint' => 'https://example.com:99']))),
|
||||
'ses+https://ACCESS_KEY@example.com:99',
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
public function testSend()
|
||||
{
|
||||
$client = new MockHttpClient(function (string $method, string $url, array $options): ResponseInterface {
|
||||
$this->assertSame('POST', $method);
|
||||
$this->assertSame('https://email.us-east-1.amazonaws.com/v2/email/outbound-emails', $url);
|
||||
|
||||
$body = json_decode($options['body'], true);
|
||||
$content = base64_decode($body['Content']['Raw']['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);
|
||||
|
||||
$json = '{"MessageId": "foobar"}';
|
||||
|
||||
return new MockResponse($json, [
|
||||
'http_code' => 200,
|
||||
]);
|
||||
});
|
||||
|
||||
$transport = new SesHttpAsyncAwsTransport(new SesClient(Configuration::create([]), new NullProvider(), $client));
|
||||
|
||||
$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!');
|
||||
|
||||
$message = $transport->send($mail);
|
||||
|
||||
$this->assertSame('foobar', $message->getMessageId());
|
||||
}
|
||||
|
||||
public function testSendThrowsForErrorResponse()
|
||||
{
|
||||
$client = new MockHttpClient(function (string $method, string $url, array $options): ResponseInterface {
|
||||
$xml = "<SendEmailResponse xmlns=\"https://email.amazonaws.com/doc/2010-03-31/\">
|
||||
<Error>
|
||||
<Message>i'm a teapot</Message>
|
||||
<Code>418</Code>
|
||||
</Error>
|
||||
</SendEmailResponse>";
|
||||
|
||||
return new MockResponse($xml, [
|
||||
'http_code' => 418,
|
||||
]);
|
||||
});
|
||||
|
||||
$transport = new SesHttpAsyncAwsTransport(new SesClient(Configuration::create([]), new NullProvider(), $client));
|
||||
|
||||
$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!');
|
||||
|
||||
$this->expectException(HttpTransportException::class);
|
||||
$this->expectExceptionMessage('Unable to send an email: i\'m a teapot (code 418).');
|
||||
$transport->send($mail);
|
||||
}
|
||||
}
|
|
@ -20,6 +20,9 @@ use Symfony\Component\Mime\Address;
|
|||
use Symfony\Component\Mime\Email;
|
||||
use Symfony\Contracts\HttpClient\ResponseInterface;
|
||||
|
||||
/**
|
||||
* @group legacy
|
||||
*/
|
||||
class SesHttpTransportTest extends TestCase
|
||||
{
|
||||
/**
|
||||
|
|
|
@ -11,8 +11,10 @@
|
|||
|
||||
namespace Symfony\Component\Mailer\Bridge\Amazon\Tests\Transport;
|
||||
|
||||
use Symfony\Component\Mailer\Bridge\Amazon\Transport\SesApiTransport;
|
||||
use Symfony\Component\Mailer\Bridge\Amazon\Transport\SesHttpTransport;
|
||||
use AsyncAws\Core\Configuration;
|
||||
use AsyncAws\Ses\SesClient;
|
||||
use Symfony\Component\Mailer\Bridge\Amazon\Transport\SesApiAsyncAwsTransport;
|
||||
use Symfony\Component\Mailer\Bridge\Amazon\Transport\SesHttpAsyncAwsTransport;
|
||||
use Symfony\Component\Mailer\Bridge\Amazon\Transport\SesSmtpTransport;
|
||||
use Symfony\Component\Mailer\Bridge\Amazon\Transport\SesTransportFactory;
|
||||
use Symfony\Component\Mailer\Test\TransportFactoryTestCase;
|
||||
|
@ -67,37 +69,37 @@ class SesTransportFactoryTest extends TransportFactoryTestCase
|
|||
|
||||
yield [
|
||||
new Dsn('ses+api', 'default', self::USER, self::PASSWORD),
|
||||
new SesApiTransport(self::USER, self::PASSWORD, null, $client, $dispatcher, $logger),
|
||||
new SesApiAsyncAwsTransport(new SesClient(Configuration::create(['accessKeyId' => self::USER, 'accessKeySecret' => self::PASSWORD, 'region' => 'eu-west-1']), null, $client, $logger), $dispatcher, $logger),
|
||||
];
|
||||
|
||||
yield [
|
||||
new Dsn('ses+api', 'default', self::USER, self::PASSWORD, null, ['region' => 'eu-west-1']),
|
||||
new SesApiTransport(self::USER, self::PASSWORD, 'eu-west-1', $client, $dispatcher, $logger),
|
||||
new Dsn('ses+api', 'default', self::USER, self::PASSWORD, null, ['region' => 'eu-west-2']),
|
||||
new SesApiAsyncAwsTransport(new SesClient(Configuration::create(['accessKeyId' => self::USER, 'accessKeySecret' => self::PASSWORD, 'region' => 'eu-west-2']), null, $client, $logger), $dispatcher, $logger),
|
||||
];
|
||||
|
||||
yield [
|
||||
new Dsn('ses+api', 'example.com', self::USER, self::PASSWORD, 8080),
|
||||
(new SesApiTransport(self::USER, self::PASSWORD, null, $client, $dispatcher, $logger))->setHost('example.com')->setPort(8080),
|
||||
new SesApiAsyncAwsTransport(new SesClient(Configuration::create(['accessKeyId' => self::USER, 'accessKeySecret' => self::PASSWORD, 'region' => 'eu-west-1', 'endpoint' => 'https://example.com:8080']), null, $client, $logger), $dispatcher, $logger),
|
||||
];
|
||||
|
||||
yield [
|
||||
new Dsn('ses+https', 'default', self::USER, self::PASSWORD),
|
||||
new SesHttpTransport(self::USER, self::PASSWORD, null, $client, $dispatcher, $logger),
|
||||
new SesHttpAsyncAwsTransport(new SesClient(Configuration::create(['accessKeyId' => self::USER, 'accessKeySecret' => self::PASSWORD, 'region' => 'eu-west-1']), null, $client, $logger), $dispatcher, $logger),
|
||||
];
|
||||
|
||||
yield [
|
||||
new Dsn('ses', 'default', self::USER, self::PASSWORD),
|
||||
new SesHttpTransport(self::USER, self::PASSWORD, null, $client, $dispatcher, $logger),
|
||||
new SesHttpAsyncAwsTransport(new SesClient(Configuration::create(['accessKeyId' => self::USER, 'accessKeySecret' => self::PASSWORD, 'region' => 'eu-west-1']), null, $client, $logger), $dispatcher, $logger),
|
||||
];
|
||||
|
||||
yield [
|
||||
new Dsn('ses+https', 'example.com', self::USER, self::PASSWORD, 8080),
|
||||
(new SesHttpTransport(self::USER, self::PASSWORD, null, $client, $dispatcher, $logger))->setHost('example.com')->setPort(8080),
|
||||
new SesHttpAsyncAwsTransport(new SesClient(Configuration::create(['accessKeyId' => self::USER, 'accessKeySecret' => self::PASSWORD, 'region' => 'eu-west-1', 'endpoint' => 'https://example.com:8080']), null, $client, $logger), $dispatcher, $logger),
|
||||
];
|
||||
|
||||
yield [
|
||||
new Dsn('ses+https', 'default', self::USER, self::PASSWORD, null, ['region' => 'eu-west-1']),
|
||||
new SesHttpTransport(self::USER, self::PASSWORD, 'eu-west-1', $client, $dispatcher, $logger),
|
||||
new Dsn('ses+https', 'default', self::USER, self::PASSWORD, null, ['region' => 'eu-west-2']),
|
||||
new SesHttpAsyncAwsTransport(new SesClient(Configuration::create(['accessKeyId' => self::USER, 'accessKeySecret' => self::PASSWORD, 'region' => 'eu-west-2']), null, $client, $logger), $dispatcher, $logger),
|
||||
];
|
||||
|
||||
yield [
|
||||
|
@ -127,7 +129,5 @@ class SesTransportFactoryTest extends TransportFactoryTestCase
|
|||
public function incompleteDsnProvider(): iterable
|
||||
{
|
||||
yield [new Dsn('ses+smtp', 'default', self::USER)];
|
||||
|
||||
yield [new Dsn('ses+smtp', 'default', null, self::PASSWORD)];
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,101 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\Mailer\Bridge\Amazon\Transport;
|
||||
|
||||
use AsyncAws\Ses\Input\SendEmailRequest;
|
||||
use AsyncAws\Ses\ValueObject\Content;
|
||||
use Symfony\Component\Mailer\Envelope;
|
||||
use Symfony\Component\Mailer\Exception\RuntimeException;
|
||||
use Symfony\Component\Mailer\SentMessage;
|
||||
use Symfony\Component\Mime\Address;
|
||||
use Symfony\Component\Mime\Email;
|
||||
use Symfony\Component\Mime\MessageConverter;
|
||||
|
||||
/**
|
||||
* @author Jérémy Derussé <jeremy@derusse.com>
|
||||
*/
|
||||
class SesApiAsyncAwsTransport extends SesHttpAsyncAwsTransport
|
||||
{
|
||||
public function __toString(): string
|
||||
{
|
||||
$configuration = $this->sesClient->getConfiguration();
|
||||
if (!$configuration->isDefault('endpoint')) {
|
||||
$endpoint = parse_url($configuration->get('endpoint'));
|
||||
$host = $endpoint['host'].($endpoint['port'] ?? null ? ':'.$endpoint['port'] : '');
|
||||
} else {
|
||||
$host = $configuration->get('region');
|
||||
}
|
||||
|
||||
return sprintf('ses+api://%s@%s', $configuration->get('accessKeyId'), $host);
|
||||
}
|
||||
|
||||
protected function getRequest(SentMessage $message): SendEmailRequest
|
||||
{
|
||||
try {
|
||||
$email = MessageConverter::toEmail($message->getOriginalMessage());
|
||||
} catch (\Exception $e) {
|
||||
throw new RuntimeException(sprintf('Unable to send message with the "%s" transport: "%s".', __CLASS__, $e->getMessage()), 0, $e);
|
||||
}
|
||||
|
||||
if ($email->getAttachments()) {
|
||||
return parent::getRequest($message);
|
||||
}
|
||||
|
||||
$envelope = $message->getEnvelope();
|
||||
|
||||
$request = [
|
||||
'FromEmailAddress' => $envelope->getSender()->toString(),
|
||||
'Destination' => [
|
||||
'ToAddresses' => $this->stringifyAddresses($this->getRecipients($email, $envelope)),
|
||||
],
|
||||
'Content' => [
|
||||
'Simple' => [
|
||||
'Subject' => [
|
||||
'Data' => $email->getSubject(),
|
||||
'Charset' => 'utf-8',
|
||||
],
|
||||
'Body' => [],
|
||||
],
|
||||
],
|
||||
];
|
||||
|
||||
if ($emails = $email->getCc()) {
|
||||
$request['Destination']['CcAddresses'] = $this->stringifyAddresses($emails);
|
||||
}
|
||||
if ($emails = $email->getBcc()) {
|
||||
$request['Destination']['BccAddresses'] = $this->stringifyAddresses($emails);
|
||||
}
|
||||
if ($email->getTextBody()) {
|
||||
$request['Content']['Simple']['Body']['Text'] = new Content([
|
||||
'Data' => $email->getTextBody(),
|
||||
'Charset' => $email->getTextCharset(),
|
||||
]);
|
||||
}
|
||||
if ($email->getHtmlBody()) {
|
||||
$request['Content']['Simple']['Body']['Html'] = new Content([
|
||||
'Data' => $email->getHtmlBody(),
|
||||
'Charset' => $email->getHtmlCharset(),
|
||||
]);
|
||||
}
|
||||
|
||||
return new SendEmailRequest($request);
|
||||
}
|
||||
|
||||
private function getRecipients(Email $email, Envelope $envelope): array
|
||||
{
|
||||
$emailRecipients = array_merge($email->getCc(), $email->getBcc());
|
||||
|
||||
return array_filter($envelope->getRecipients(), function (Address $address) use ($emailRecipients) {
|
||||
return !\in_array($address, $emailRecipients, true);
|
||||
});
|
||||
}
|
||||
}
|
|
@ -21,6 +21,8 @@ use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;
|
|||
use Symfony\Contracts\HttpClient\HttpClientInterface;
|
||||
use Symfony\Contracts\HttpClient\ResponseInterface;
|
||||
|
||||
trigger_deprecation('symfony/amazon-mailer', '5.1', 'The "%s" class is deprecated, use "%s" instead. The Amazon transport now requires "AsyncAws". Run "composer require async-aws/ses".', SesApiTransport::class, SesApiAsyncAwsTransport::class);
|
||||
|
||||
/**
|
||||
* @author Kevin Verschaeve
|
||||
*/
|
||||
|
|
|
@ -0,0 +1,81 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\Mailer\Bridge\Amazon\Transport;
|
||||
|
||||
use AsyncAws\Core\Exception\Http\HttpException;
|
||||
use AsyncAws\Ses\Input\SendEmailRequest;
|
||||
use AsyncAws\Ses\SesClient;
|
||||
use AsyncAws\Ses\ValueObject\Destination;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Symfony\Component\Mailer\Exception\HttpTransportException;
|
||||
use Symfony\Component\Mailer\SentMessage;
|
||||
use Symfony\Component\Mailer\Transport\AbstractTransport;
|
||||
use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;
|
||||
|
||||
/**
|
||||
* @author Jérémy Derussé <jeremy@derusse.com>
|
||||
*/
|
||||
class SesHttpAsyncAwsTransport extends AbstractTransport
|
||||
{
|
||||
/** @var SesClient */
|
||||
protected $sesClient;
|
||||
|
||||
public function __construct(SesClient $sesClient, EventDispatcherInterface $dispatcher = null, LoggerInterface $logger = null)
|
||||
{
|
||||
$this->sesClient = $sesClient;
|
||||
|
||||
parent::__construct($dispatcher, $logger);
|
||||
}
|
||||
|
||||
public function __toString(): string
|
||||
{
|
||||
$configuration = $this->sesClient->getConfiguration();
|
||||
if (!$configuration->isDefault('endpoint')) {
|
||||
$endpoint = parse_url($configuration->get('endpoint'));
|
||||
$host = $endpoint['host'].($endpoint['port'] ?? null ? ':'.$endpoint['port'] : '');
|
||||
} else {
|
||||
$host = $configuration->get('region');
|
||||
}
|
||||
|
||||
return sprintf('ses+https://%s@%s', $configuration->get('accessKeyId'), $host);
|
||||
}
|
||||
|
||||
protected function doSend(SentMessage $message): void
|
||||
{
|
||||
$result = $this->sesClient->sendEmail($this->getRequest($message));
|
||||
$response = $result->info()['response'];
|
||||
|
||||
try {
|
||||
$message->setMessageId($result->getMessageId());
|
||||
$message->appendDebug($response->getInfo('debug') ?? '');
|
||||
} catch (HttpException $e) {
|
||||
$exception = new HttpTransportException(sprintf('Unable to send an email: %s (code %s).', $e->getAwsMessage() ?: $e->getMessage(), $e->getAwsCode() ?: $e->getCode()), $e->getResponse(), $e->getCode(), $e);
|
||||
$exception->appendDebug($e->getResponse()->getInfo('debug') ?? '');
|
||||
|
||||
throw $exception;
|
||||
}
|
||||
}
|
||||
|
||||
protected function getRequest(SentMessage $message): SendEmailRequest
|
||||
{
|
||||
return new SendEmailRequest([
|
||||
'Destination' => $destination = new Destination([
|
||||
'ToAddresses' => $this->stringifyAddresses($message->getEnvelope()->getRecipients()),
|
||||
]),
|
||||
'Content' => [
|
||||
'Raw' => [
|
||||
'Data' => $message->toString(),
|
||||
],
|
||||
],
|
||||
]);
|
||||
}
|
||||
}
|
|
@ -19,6 +19,8 @@ use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;
|
|||
use Symfony\Contracts\HttpClient\HttpClientInterface;
|
||||
use Symfony\Contracts\HttpClient\ResponseInterface;
|
||||
|
||||
trigger_deprecation('symfony/amazon-mailer', '5.1', 'The "%s" class is deprecated, use "%s" instead. The Amazon transport now requires "AsyncAws". Run "composer require async-aws/ses".', SesHttpTransport::class, SesHttpAsyncAwsTransport::class);
|
||||
|
||||
/**
|
||||
* @author Kevin Verschaeve
|
||||
*/
|
||||
|
|
|
@ -11,6 +11,9 @@
|
|||
|
||||
namespace Symfony\Component\Mailer\Bridge\Amazon\Transport;
|
||||
|
||||
use AsyncAws\Core\Configuration;
|
||||
use AsyncAws\Ses\SesClient;
|
||||
use Symfony\Component\HttpClient\HttpClient;
|
||||
use Symfony\Component\Mailer\Exception\UnsupportedSchemeException;
|
||||
use Symfony\Component\Mailer\Transport\AbstractTransportFactory;
|
||||
use Symfony\Component\Mailer\Transport\Dsn;
|
||||
|
@ -18,28 +21,55 @@ use Symfony\Component\Mailer\Transport\TransportInterface;
|
|||
|
||||
/**
|
||||
* @author Konstantin Myakshin <molodchick@gmail.com>
|
||||
* @author Jérémy Derussé <jeremy@derusse.com>
|
||||
*/
|
||||
final class SesTransportFactory extends AbstractTransportFactory
|
||||
{
|
||||
public function create(Dsn $dsn): TransportInterface
|
||||
{
|
||||
$scheme = $dsn->getScheme();
|
||||
$user = $this->getUser($dsn);
|
||||
$password = $this->getPassword($dsn);
|
||||
$region = $dsn->getOption('region');
|
||||
$host = 'default' === $dsn->getHost() ? null : $dsn->getHost();
|
||||
$port = $dsn->getPort();
|
||||
|
||||
if ('ses+api' === $scheme) {
|
||||
return (new SesApiTransport($user, $password, $region, $this->client, $this->dispatcher, $this->logger))->setHost($host)->setPort($port);
|
||||
}
|
||||
|
||||
if ('ses+https' === $scheme || 'ses' === $scheme) {
|
||||
return (new SesHttpTransport($user, $password, $region, $this->client, $this->dispatcher, $this->logger))->setHost($host)->setPort($port);
|
||||
}
|
||||
|
||||
if ('ses+smtp' === $scheme || 'ses+smtps' === $scheme) {
|
||||
return new SesSmtpTransport($user, $password, $region, $this->dispatcher, $this->logger);
|
||||
return new SesSmtpTransport($this->getUser($dsn), $this->getPassword($dsn), $region, $this->dispatcher, $this->logger);
|
||||
}
|
||||
|
||||
if (!class_exists(SesClient::class)) {
|
||||
if (!class_exists(HttpClient::class)) {
|
||||
throw new \LogicException(sprintf('You cannot use "%s" as the HttpClient component or AsyncAws package is not installed. Try running "composer require async-aws/ses".', __CLASS__));
|
||||
}
|
||||
|
||||
trigger_deprecation('symfony/amazon-mailer', '5.1', 'Using the "%s" transport without AsyncAws is deprecated. Try running "composer require async-aws/ses".', $scheme, \get_called_class());
|
||||
|
||||
$user = $this->getUser($dsn);
|
||||
$password = $this->getPassword($dsn);
|
||||
$host = 'default' === $dsn->getHost() ? null : $dsn->getHost();
|
||||
$port = $dsn->getPort();
|
||||
|
||||
if ('ses+api' === $scheme) {
|
||||
return (new SesApiTransport($user, $password, $region, $this->client, $this->dispatcher, $this->logger))->setHost($host)->setPort($port);
|
||||
}
|
||||
if ('ses+https' === $scheme || 'ses' === $scheme) {
|
||||
return (new SesHttpTransport($user, $password, $region, $this->client, $this->dispatcher, $this->logger))->setHost($host)->setPort($port);
|
||||
}
|
||||
} else {
|
||||
switch ($scheme) {
|
||||
case 'ses+api':
|
||||
$class = SesApiAsyncAwsTransport::class;
|
||||
// no break
|
||||
case 'ses':
|
||||
case 'ses+https':
|
||||
$class = $class ?? SesHttpAsyncAwsTransport::class;
|
||||
$options = [
|
||||
'region' => $dsn->getOption('region') ?: 'eu-west-1',
|
||||
'accessKeyId' => $dsn->getUser(),
|
||||
'accessKeySecret' => $dsn->getPassword(),
|
||||
] + (
|
||||
'default' === $dsn->getHost() ? [] : ['endpoint' => 'https://'.$dsn->getHost().($dsn->getPort() ? ':'.$dsn->getPort() : '')]
|
||||
);
|
||||
|
||||
return new $class(new SesClient(Configuration::create($options), null, $this->client, $this->logger), $this->dispatcher, $this->logger);
|
||||
}
|
||||
}
|
||||
|
||||
throw new UnsupportedSchemeException($dsn, 'ses', $this->getSupportedSchemes());
|
||||
|
|
|
@ -17,9 +17,11 @@
|
|||
],
|
||||
"require": {
|
||||
"php": "^7.2.5",
|
||||
"symfony/deprecation-contracts": "^2.1",
|
||||
"symfony/mailer": "^4.4|^5.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"async-aws/ses": "^1.0",
|
||||
"symfony/http-client": "^4.4|^5.0"
|
||||
},
|
||||
"autoload": {
|
||||
|
|
Reference in New Issue