From 8b44e299fab4bf6e0c2803ee7d189fff6ea80de4 Mon Sep 17 00:00:00 2001 From: Dries Vints Date: Fri, 28 Jan 2022 16:48:59 +0100 Subject: [PATCH 1/6] Re-implement original SesTransport --- src/Illuminate/Mail/MailManager.php | 30 +++-- .../Mail/Transport/ArrayTransport.php | 2 - .../Mail/Transport/LogTransport.php | 2 - .../Mail/Transport/SesTransport.php | 112 ++++++++++++++++++ tests/Mail/MailSesTransportTest.php | 86 +++++++++++++- 5 files changed, 215 insertions(+), 17 deletions(-) create mode 100644 src/Illuminate/Mail/Transport/SesTransport.php diff --git a/src/Illuminate/Mail/MailManager.php b/src/Illuminate/Mail/MailManager.php index b7b0fc7908b4..f0958c12512c 100644 --- a/src/Illuminate/Mail/MailManager.php +++ b/src/Illuminate/Mail/MailManager.php @@ -2,16 +2,17 @@ namespace Illuminate\Mail; +use Aws\Ses\SesClient; use Closure; use Illuminate\Contracts\Mail\Factory as FactoryContract; use Illuminate\Log\LogManager; use Illuminate\Mail\Transport\ArrayTransport; use Illuminate\Mail\Transport\LogTransport; +use Illuminate\Mail\Transport\SesTransport; use Illuminate\Support\Arr; use Illuminate\Support\Str; use InvalidArgumentException; use Psr\Log\LoggerInterface; -use Symfony\Component\Mailer\Bridge\Amazon\Transport\SesTransportFactory; use Symfony\Component\Mailer\Bridge\Mailgun\Transport\MailgunTransportFactory; use Symfony\Component\Mailer\Bridge\Postmark\Transport\PostmarkTransportFactory; use Symfony\Component\Mailer\Transport\Dsn; @@ -234,20 +235,25 @@ protected function createSesTransport(array $config) $config = Arr::except($config, ['transport']); - $factory = new SesTransportFactory(); + return new SesTransport( + new SesClient($this->addSesCredentials($config)), + $config['options'] ?? [] + ); + } - if (! isset($config['session_token']) && isset($config['token'])) { - $config['session_token'] = $config['token']; + /** + * Add the SES credentials to the configuration array. + * + * @param array $config + * @return array + */ + protected function addSesCredentials(array $config) + { + if (! empty($config['key']) && ! empty($config['secret'])) { + $config['credentials'] = Arr::only($config, ['key', 'secret', 'token']); } - return $factory->create(new Dsn( - 'ses+api', - 'default', - $config['key'] ?? null, - $config['secret'] ?? null, - $config['port'] ?? null, - $config - )); + return $config; } /** diff --git a/src/Illuminate/Mail/Transport/ArrayTransport.php b/src/Illuminate/Mail/Transport/ArrayTransport.php index 1403c1acd9a6..dc26ed69d90b 100644 --- a/src/Illuminate/Mail/Transport/ArrayTransport.php +++ b/src/Illuminate/Mail/Transport/ArrayTransport.php @@ -29,8 +29,6 @@ public function __construct() /** * {@inheritdoc} - * - * @return int */ public function send(RawMessage $message, Envelope $envelope = null): ?SentMessage { diff --git a/src/Illuminate/Mail/Transport/LogTransport.php b/src/Illuminate/Mail/Transport/LogTransport.php index 82be1708be02..d9ec8ac09d7e 100644 --- a/src/Illuminate/Mail/Transport/LogTransport.php +++ b/src/Illuminate/Mail/Transport/LogTransport.php @@ -30,8 +30,6 @@ public function __construct(LoggerInterface $logger) /** * {@inheritdoc} - * - * @return int */ public function send(RawMessage $message, Envelope $envelope = null): ?SentMessage { diff --git a/src/Illuminate/Mail/Transport/SesTransport.php b/src/Illuminate/Mail/Transport/SesTransport.php new file mode 100644 index 000000000000..b1317046e378 --- /dev/null +++ b/src/Illuminate/Mail/Transport/SesTransport.php @@ -0,0 +1,112 @@ +ses = $ses; + $this->options = $options; + } + + /** + * {@inheritDoc} + */ + protected function doSend(SentMessage $message): void + { + try { + $this->ses->sendRawEmail( + array_merge( + $this->options, [ + 'Source' => $message->getEnvelope()->getSender()->toString(), + 'RawMessage' => [ + 'Data' => $message->toString(), + ], + ] + ) + ); + } catch (AwsException $e) { + throw new Exception('Request to AWS SES API failed.', $e->getCode(), $e); + } + } + + /** + * Get the string representation of the transport. + * + * @return string + */ + public function __toString(): string + { + $configuration = $this->ses->getConfig(); + + 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); + } + + /** + * Get the Amazon SES client for the SesTransport instance. + * + * @return \Aws\Ses\SesClient + */ + public function ses() + { + return $this->ses; + } + + /** + * Get the transmission options being used by the transport. + * + * @return array + */ + public function getOptions() + { + return $this->options; + } + + /** + * Set the transmission options being used by the transport. + * + * @param array $options + * @return array + */ + public function setOptions(array $options) + { + return $this->options = $options; + } +} diff --git a/tests/Mail/MailSesTransportTest.php b/tests/Mail/MailSesTransportTest.php index 306038cc4514..76e726e39633 100755 --- a/tests/Mail/MailSesTransportTest.php +++ b/tests/Mail/MailSesTransportTest.php @@ -2,13 +2,25 @@ namespace Illuminate\Tests\Mail; +use Aws\Ses\SesClient; use Illuminate\Config\Repository; use Illuminate\Container\Container; use Illuminate\Mail\MailManager; +use Illuminate\Mail\Transport\SesTransport; +use Illuminate\View\Factory; use PHPUnit\Framework\TestCase; +use Symfony\Component\Mime\Email; +use Mockery as m; class MailSesTransportTest extends TestCase { + protected function tearDown(): void + { + m::close(); + + parent::tearDown(); + } + public function testGetTransport() { $container = new Container; @@ -16,6 +28,8 @@ public function testGetTransport() $container->singleton('config', function () { return new Repository([ 'services.ses' => [ + 'key' => 'foo', + 'secret' => 'bar', 'region' => 'us-east-1', ], ]); @@ -23,8 +37,78 @@ public function testGetTransport() $manager = new MailManager($container); + /** @var \Illuminate\Mail\Transport\SesTransport $transport */ $transport = $manager->createSymfonyTransport(['transport' => 'ses']); - $this->assertSame('ses+api://random_key@us-east-1', $transport->__toString()); + $ses = $transport->ses(); + + $this->assertSame('us-east-1', $ses->getRegion()); + } + + public function testSend() + { + $message = new Email(); + $message->subject('Foo subject'); + $message->text('Bar body'); + $message->sender('myself@example.com'); + $message->to('me@example.com'); + $message->bcc('you@example.com'); + + $client = m::mock(SesClient::class); + $client->shouldReceive('sendRawEmail')->once(); + + (new SesTransport($client))->send($message); + } + + public function testSesLocalConfiguration() + { + $container = new Container; + + $container->singleton('config', function () { + return new Repository([ + 'mail' => [ + 'mailers' => [ + 'ses' => [ + 'transport' => 'ses', + 'region' => 'eu-west-1', + 'options' => [ + 'ConfigurationSetName' => 'Laravel', + 'Tags' => [ + ['Name' => 'Laravel', 'Value' => 'Framework'], + ], + ], + ], + ], + ], + 'services' => [ + 'ses' => [ + 'region' => 'us-east-1', + ], + ], + ]); + }); + + $container->instance('view', $this->createMock(Factory::class)); + + $container->bind('events', function () { + return null; + }); + + $manager = new MailManager($container); + + /** @var \Illuminate\Mail\Mailer $mailer */ + $mailer = $manager->mailer('ses'); + + /** @var \Illuminate\Mail\Transport\SesTransport $transport */ + $transport = $mailer->getSymfonyTransport(); + + $this->assertSame('eu-west-1', $transport->ses()->getRegion()); + + $this->assertSame([ + 'ConfigurationSetName' => 'Laravel', + 'Tags' => [ + ['Name' => 'Laravel', 'Value' => 'Framework'], + ], + ], $transport->getOptions()); } } From 8ffceeca07104b04a596dee12def476e656c8477 Mon Sep 17 00:00:00 2001 From: StyleCI Bot Date: Fri, 28 Jan 2022 15:49:47 +0000 Subject: [PATCH 2/6] Apply fixes from StyleCI --- src/Illuminate/Mail/Transport/SesTransport.php | 1 - tests/Mail/MailSesTransportTest.php | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Illuminate/Mail/Transport/SesTransport.php b/src/Illuminate/Mail/Transport/SesTransport.php index b1317046e378..06e8708a0809 100644 --- a/src/Illuminate/Mail/Transport/SesTransport.php +++ b/src/Illuminate/Mail/Transport/SesTransport.php @@ -8,7 +8,6 @@ use Symfony\Component\Mailer\SentMessage; use Symfony\Component\Mailer\Transport\AbstractTransport; use Symfony\Component\Mailer\Transport\TransportInterface; -use Symfony\Component\Mime\Email; class SesTransport extends AbstractTransport implements TransportInterface { diff --git a/tests/Mail/MailSesTransportTest.php b/tests/Mail/MailSesTransportTest.php index 76e726e39633..6b5d52f8a889 100755 --- a/tests/Mail/MailSesTransportTest.php +++ b/tests/Mail/MailSesTransportTest.php @@ -8,9 +8,9 @@ use Illuminate\Mail\MailManager; use Illuminate\Mail\Transport\SesTransport; use Illuminate\View\Factory; +use Mockery as m; use PHPUnit\Framework\TestCase; use Symfony\Component\Mime\Email; -use Mockery as m; class MailSesTransportTest extends TestCase { From 3fe2f6dc6941d234988dea671a20536ca727e872 Mon Sep 17 00:00:00 2001 From: Dries Vints Date: Fri, 28 Jan 2022 17:18:14 +0100 Subject: [PATCH 3/6] wip --- src/Illuminate/Mail/Transport/SesTransport.php | 11 +---------- tests/Mail/MailSesTransportTest.php | 2 ++ 2 files changed, 3 insertions(+), 10 deletions(-) diff --git a/src/Illuminate/Mail/Transport/SesTransport.php b/src/Illuminate/Mail/Transport/SesTransport.php index 06e8708a0809..6f071ffff029 100644 --- a/src/Illuminate/Mail/Transport/SesTransport.php +++ b/src/Illuminate/Mail/Transport/SesTransport.php @@ -66,16 +66,7 @@ protected function doSend(SentMessage $message): void */ public function __toString(): string { - $configuration = $this->ses->getConfig(); - - 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); + return 'ses'; } /** diff --git a/tests/Mail/MailSesTransportTest.php b/tests/Mail/MailSesTransportTest.php index 6b5d52f8a889..9e5fe65d7597 100755 --- a/tests/Mail/MailSesTransportTest.php +++ b/tests/Mail/MailSesTransportTest.php @@ -43,6 +43,8 @@ public function testGetTransport() $ses = $transport->ses(); $this->assertSame('us-east-1', $ses->getRegion()); + + $this->assertSame('ses', (string) $transport); } public function testSend() From b826d91943ea941c215fdcbfba3ba54c9b0059ef Mon Sep 17 00:00:00 2001 From: Dries Vints Date: Fri, 28 Jan 2022 17:21:04 +0100 Subject: [PATCH 4/6] wip --- composer.json | 3 +-- src/Illuminate/Mail/composer.json | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/composer.json b/composer.json index bd44721a7f1b..1213cd7334e4 100644 --- a/composer.json +++ b/composer.json @@ -140,7 +140,7 @@ "ext-posix": "Required to use all features of the queue worker.", "ext-redis": "Required to use the Redis cache and queue drivers (^4.0|^5.0).", "ably/ably-php": "Required to use the Ably broadcast driver (^1.0).", - "aws/aws-sdk-php": "Required to use the SQS queue driver and DynamoDb failed job storage (^3.198.1).", + "aws/aws-sdk-php": "Required to use the SQS queue driver, DynamoDb failed job storage and SES mail driver (^3.198.1).", "brianium/paratest": "Required to run tests in parallel (^6.0).", "doctrine/dbal": "Required to rename columns and drop SQLite columns (^2.13.3|^3.1.4).", "filp/whoops": "Required for friendly error pages in development (^2.14.3).", @@ -157,7 +157,6 @@ "predis/predis": "Required to use the predis connector (^1.1.9).", "psr/http-message": "Required to allow Storage::put to accept a StreamInterface (^1.0).", "pusher/pusher-php-server": "Required to use the Pusher broadcast driver (^6.0|^7.0).", - "symfony/amazon-mailer": "Required to enable support for the SES mail transport (^6.0).", "symfony/cache": "Required to PSR-6 cache bridge (^6.0).", "symfony/filesystem": "Required to enable support for relative symbolic links (^6.0).", "symfony/http-client": "Required to enable support for the Symfony API mail transports (^6.0).", diff --git a/src/Illuminate/Mail/composer.json b/src/Illuminate/Mail/composer.json index c8a0a6fc1238..56e008e18256 100755 --- a/src/Illuminate/Mail/composer.json +++ b/src/Illuminate/Mail/composer.json @@ -37,7 +37,7 @@ } }, "suggest": { - "symfony/amazon-mailer": "Required to enable support for the SES mail transport (^6.0).", + "aws/aws-sdk-php": "Required to use the SES mail driver (^3.198.1).", "symfony/http-client": "Required to use the Symfony API mail transports (^6.0).", "symfony/mailgun-mailer": "Required to enable support for the Mailgun mail transport (^6.0).", "symfony/postmark-mailer": "Required to enable support for the Postmark mail transport (^6.0)." From c1de87a0864f9e30af64fe6ca8d0f8c163adc9b3 Mon Sep 17 00:00:00 2001 From: Dries Vints Date: Fri, 28 Jan 2022 17:24:20 +0100 Subject: [PATCH 5/6] wip --- src/Illuminate/Mail/Transport/SesTransport.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Illuminate/Mail/Transport/SesTransport.php b/src/Illuminate/Mail/Transport/SesTransport.php index 6f071ffff029..4f2fe090a4ea 100644 --- a/src/Illuminate/Mail/Transport/SesTransport.php +++ b/src/Illuminate/Mail/Transport/SesTransport.php @@ -36,6 +36,8 @@ public function __construct(SesClient $ses, $options = []) { $this->ses = $ses; $this->options = $options; + + parent::__construct(); } /** From d4cfdba6455c8f924ff346b1729549e3edf16193 Mon Sep 17 00:00:00 2001 From: Taylor Otwell Date: Mon, 31 Jan 2022 09:50:48 -0600 Subject: [PATCH 6/6] Update composer.json --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 1213cd7334e4..a2c76c9f4ae1 100644 --- a/composer.json +++ b/composer.json @@ -140,7 +140,7 @@ "ext-posix": "Required to use all features of the queue worker.", "ext-redis": "Required to use the Redis cache and queue drivers (^4.0|^5.0).", "ably/ably-php": "Required to use the Ably broadcast driver (^1.0).", - "aws/aws-sdk-php": "Required to use the SQS queue driver, DynamoDb failed job storage and SES mail driver (^3.198.1).", + "aws/aws-sdk-php": "Required to use the SQS queue driver, DynamoDb failed job storage, and SES mail driver (^3.198.1).", "brianium/paratest": "Required to run tests in parallel (^6.0).", "doctrine/dbal": "Required to rename columns and drop SQLite columns (^2.13.3|^3.1.4).", "filp/whoops": "Required for friendly error pages in development (^2.14.3).",