Skip to content

Commit b9a1428

Browse files
committed
[AmazonMailer] Add adapter for AsyncAws and deprecate HttpClient impl
1 parent 7c90c8b commit b9a1428

13 files changed

+500
-25
lines changed

UPGRADE-5.1.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,12 @@ HttpFoundation
3838
`__construct()` instead)
3939
* Made the Mime component an optional dependency
4040

41+
Mailer
42+
------
43+
44+
* Deprecated the `SesApiTransport` class. It has been replaced by SesApiAsyncAwsTransport Run `composer require async-aws/ses` to use the new classes.
45+
* Deprecated the `SesHttpTransport` class. It has been replaced by SesHttpAsyncAwsTransport Run `composer require async-aws/ses` to use the new classes.
46+
4147
Messenger
4248
---------
4349

UPGRADE-6.0.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,12 @@ HttpFoundation
3535
`RedirectResponse::create()`, and `StreamedResponse::create()` methods (use
3636
`__construct()` instead)
3737

38+
Mailer
39+
------
40+
41+
* Removed the `SesApiTransport` class. Run `composer require async-aws/ses` to keep the transport in your application.
42+
* Removed the `SesHttpTransport` class. Run `composer require async-aws/ses` to keep the transport in your application.
43+
3844
Messenger
3945
---------
4046

composer.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,8 @@
126126
"phpdocumentor/reflection-docblock": "^3.0|^4.0",
127127
"twig/cssinliner-extra": "^2.12",
128128
"twig/inky-extra": "^2.12",
129-
"twig/markdown-extra": "^2.12"
129+
"twig/markdown-extra": "^2.12",
130+
"async-aws/ses": "^0.3"
130131
},
131132
"conflict": {
132133
"masterminds/html5": "<2.6",

src/Symfony/Component/Mailer/Bridge/Amazon/CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
CHANGELOG
22
=========
33

4+
5.1.0
5+
-----
6+
7+
* Added `async-aws/ses` to communicate with AWS API.
8+
49
4.4.0
510
-----
611

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\Mailer\Bridge\Amazon\Tests\Transport;
13+
14+
use AsyncAws\Core\Configuration;
15+
use AsyncAws\Ses\SesClient;
16+
use PHPUnit\Framework\TestCase;
17+
use Symfony\Component\HttpClient\MockHttpClient;
18+
use Symfony\Component\HttpClient\Response\MockResponse;
19+
use Symfony\Component\Mailer\Bridge\Amazon\Transport\SesApiAsyncAwsTransport;
20+
use Symfony\Component\Mailer\Exception\HttpTransportException;
21+
use Symfony\Component\Mime\Address;
22+
use Symfony\Component\Mime\Email;
23+
use Symfony\Contracts\HttpClient\ResponseInterface;
24+
25+
class SesApiAsyncAwsTransportTest extends TestCase
26+
{
27+
/**
28+
* @dataProvider getTransportData
29+
*/
30+
public function testToString(SesApiAsyncAwsTransport $transport, string $expected)
31+
{
32+
$this->assertSame($expected, (string) $transport);
33+
}
34+
35+
public function getTransportData()
36+
{
37+
return [
38+
[
39+
new SesApiAsyncAwsTransport(new SesClient(Configuration::create(['accessKeyId' => 'ACCESS_KEY', 'accessKeySecret' => 'SECRET_KEY']))),
40+
'ses+api://ACCESS_KEY@us-east-1',
41+
],
42+
[
43+
new SesApiAsyncAwsTransport(new SesClient(Configuration::create(['accessKeyId' => 'ACCESS_KEY', 'accessKeySecret' => 'SECRET_KEY', 'region' => 'us-west-1']))),
44+
'ses+api://ACCESS_KEY@us-west-1',
45+
],
46+
[
47+
new SesApiAsyncAwsTransport(new SesClient(Configuration::create(['accessKeyId' => 'ACCESS_KEY', 'accessKeySecret' => 'SECRET_KEY', 'endpoint' => 'https://example.com']))),
48+
'ses+api://ACCESS_KEY@example.com',
49+
],
50+
[
51+
new SesApiAsyncAwsTransport(new SesClient(Configuration::create(['accessKeyId' => 'ACCESS_KEY', 'accessKeySecret' => 'SECRET_KEY', 'endpoint' => 'https://example.com:99']))),
52+
'ses+api://ACCESS_KEY@example.com:99',
53+
],
54+
];
55+
}
56+
57+
public function testSend()
58+
{
59+
$client = new MockHttpClient(function (string $method, string $url, array $options): ResponseInterface {
60+
$this->assertSame('POST', $method);
61+
$this->assertSame('https://email.us-east-1.amazonaws.com/v2/email/outbound-emails', $url);
62+
63+
$content = json_decode($options['body'](), true);
64+
65+
$this->assertSame('Hello!', $content['Content']['Simple']['Subject']['Data']);
66+
$this->assertSame('Saif Eddin <saif.gmati@symfony.com>', $content['Destination']['ToAddresses'][0]);
67+
$this->assertSame('Fabien <fabpot@symfony.com>', $content['FromEmailAddress']);
68+
$this->assertSame('Hello There!', $content['Content']['Simple']['Body']['Text']['Data']);
69+
70+
$json = '{"MessageId": "foobar"}';
71+
72+
return new MockResponse($json, [
73+
'http_code' => 200,
74+
]);
75+
});
76+
77+
$transport = new SesApiAsyncAwsTransport(new SesClient(Configuration::create([]), null, $client));
78+
79+
$mail = new Email();
80+
$mail->subject('Hello!')
81+
->to(new Address('saif.gmati@symfony.com', 'Saif Eddin'))
82+
->from(new Address('fabpot@symfony.com', 'Fabien'))
83+
->text('Hello There!');
84+
85+
$message = $transport->send($mail);
86+
87+
$this->assertSame('foobar', $message->getMessageId());
88+
}
89+
90+
public function testSendThrowsForErrorResponse()
91+
{
92+
$client = new MockHttpClient(function (string $method, string $url, array $options): ResponseInterface {
93+
$xml = "<SendEmailResponse xmlns=\"https://email.amazonaws.com/doc/2010-03-31/\">
94+
<Error>
95+
<Message>i'm a teapot</Message>
96+
<Code>418</Code>
97+
</Error>
98+
</SendEmailResponse>";
99+
100+
return new MockResponse($xml, [
101+
'http_code' => 418,
102+
]);
103+
});
104+
105+
$transport = new SesApiAsyncAwsTransport(new SesClient(Configuration::create([]), null, $client));
106+
107+
$mail = new Email();
108+
$mail->subject('Hello!')
109+
->to(new Address('saif.gmati@symfony.com', 'Saif Eddin'))
110+
->from(new Address('fabpot@symfony.com', 'Fabien'))
111+
->text('Hello There!');
112+
113+
$this->expectException(HttpTransportException::class);
114+
$this->expectExceptionMessage('Unable to send an email: i\'m a teapot (code 418).');
115+
$transport->send($mail);
116+
}
117+
}
Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\Mailer\Bridge\Amazon\Tests\Transport;
13+
14+
use AsyncAws\Core\Configuration;
15+
use AsyncAws\Ses\SesClient;
16+
use PHPUnit\Framework\TestCase;
17+
use Symfony\Component\HttpClient\MockHttpClient;
18+
use Symfony\Component\HttpClient\Response\MockResponse;
19+
use Symfony\Component\Mailer\Bridge\Amazon\Transport\SesHttpAsyncAwsTransport;
20+
use Symfony\Component\Mailer\Exception\HttpTransportException;
21+
use Symfony\Component\Mime\Address;
22+
use Symfony\Component\Mime\Email;
23+
use Symfony\Contracts\HttpClient\ResponseInterface;
24+
25+
class SesHttpAsyncAwsTransportTest extends TestCase
26+
{
27+
/**
28+
* @dataProvider getTransportData
29+
*/
30+
public function testToString(SesHttpAsyncAwsTransport $transport, string $expected)
31+
{
32+
$this->assertSame($expected, (string) $transport);
33+
}
34+
35+
public function getTransportData()
36+
{
37+
return [
38+
[
39+
new SesHttpAsyncAwsTransport(new SesClient(Configuration::create(['accessKeyId' => 'ACCESS_KEY', 'accessKeySecret' => 'SECRET_KEY']))),
40+
'ses+https://ACCESS_KEY@us-east-1',
41+
],
42+
[
43+
new SesHttpAsyncAwsTransport(new SesClient(Configuration::create(['accessKeyId' => 'ACCESS_KEY', 'accessKeySecret' => 'SECRET_KEY', 'region' => 'us-west-1']))),
44+
'ses+https://ACCESS_KEY@us-west-1',
45+
],
46+
[
47+
new SesHttpAsyncAwsTransport(new SesClient(Configuration::create(['accessKeyId' => 'ACCESS_KEY', 'accessKeySecret' => 'SECRET_KEY', 'endpoint' => 'https://example.com']))),
48+
'ses+https://ACCESS_KEY@example.com',
49+
],
50+
[
51+
new SesHttpAsyncAwsTransport(new SesClient(Configuration::create(['accessKeyId' => 'ACCESS_KEY', 'accessKeySecret' => 'SECRET_KEY', 'endpoint' => 'https://example.com:99']))),
52+
'ses+https://ACCESS_KEY@example.com:99',
53+
],
54+
];
55+
}
56+
57+
public function testSend()
58+
{
59+
$client = new MockHttpClient(function (string $method, string $url, array $options): ResponseInterface {
60+
$this->assertSame('POST', $method);
61+
$this->assertSame('https://email.us-east-1.amazonaws.com/v2/email/outbound-emails', $url);
62+
63+
$body = json_decode($options['body'](), true);
64+
$content = base64_decode($body['Content']['Raw']['Data']);
65+
66+
$this->assertStringContainsString('Hello!', $content);
67+
$this->assertStringContainsString('Saif Eddin <saif.gmati@symfony.com>', $content);
68+
$this->assertStringContainsString('Fabien <fabpot@symfony.com>', $content);
69+
$this->assertStringContainsString('Hello There!', $content);
70+
71+
$json = '{"MessageId": "foobar"}';
72+
73+
return new MockResponse($json, [
74+
'http_code' => 200,
75+
]);
76+
});
77+
78+
$transport = new SesHttpAsyncAwsTransport(new SesClient(Configuration::create([]), null, $client));
79+
80+
$mail = new Email();
81+
$mail->subject('Hello!')
82+
->to(new Address('saif.gmati@symfony.com', 'Saif Eddin'))
83+
->from(new Address('fabpot@symfony.com', 'Fabien'))
84+
->text('Hello There!');
85+
86+
$message = $transport->send($mail);
87+
88+
$this->assertSame('foobar', $message->getMessageId());
89+
}
90+
91+
public function testSendThrowsForErrorResponse()
92+
{
93+
$client = new MockHttpClient(function (string $method, string $url, array $options): ResponseInterface {
94+
$xml = "<SendEmailResponse xmlns=\"https://email.amazonaws.com/doc/2010-03-31/\">
95+
<Error>
96+
<Message>i'm a teapot</Message>
97+
<Code>418</Code>
98+
</Error>
99+
</SendEmailResponse>";
100+
101+
return new MockResponse($xml, [
102+
'http_code' => 418,
103+
]);
104+
});
105+
106+
$transport = new SesHttpAsyncAwsTransport(new SesClient(Configuration::create([]), null, $client));
107+
108+
$mail = new Email();
109+
$mail->subject('Hello!')
110+
->to(new Address('saif.gmati@symfony.com', 'Saif Eddin'))
111+
->from(new Address('fabpot@symfony.com', 'Fabien'))
112+
->text('Hello There!');
113+
114+
$this->expectException(HttpTransportException::class);
115+
$this->expectExceptionMessage('Unable to send an email: i\'m a teapot (code 418).');
116+
$transport->send($mail);
117+
}
118+
}

src/Symfony/Component/Mailer/Bridge/Amazon/Tests/Transport/SesTransportFactoryTest.php

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,10 @@
1111

1212
namespace Symfony\Component\Mailer\Bridge\Amazon\Tests\Transport;
1313

14-
use Symfony\Component\Mailer\Bridge\Amazon\Transport\SesApiTransport;
15-
use Symfony\Component\Mailer\Bridge\Amazon\Transport\SesHttpTransport;
14+
use AsyncAws\Core\Configuration;
15+
use AsyncAws\Ses\SesClient;
16+
use Symfony\Component\Mailer\Bridge\Amazon\Transport\SesApiAsyncAwsTransport;
17+
use Symfony\Component\Mailer\Bridge\Amazon\Transport\SesHttpAsyncAwsTransport;
1618
use Symfony\Component\Mailer\Bridge\Amazon\Transport\SesSmtpTransport;
1719
use Symfony\Component\Mailer\Bridge\Amazon\Transport\SesTransportFactory;
1820
use Symfony\Component\Mailer\Test\TransportFactoryTestCase;
@@ -67,37 +69,37 @@ public function createProvider(): iterable
6769

6870
yield [
6971
new Dsn('ses+api', 'default', self::USER, self::PASSWORD),
70-
new SesApiTransport(self::USER, self::PASSWORD, null, $client, $dispatcher, $logger),
72+
new SesApiAsyncAwsTransport(new SesClient(Configuration::create(['accessKeyId' => self::USER, 'accessKeySecret' => self::PASSWORD, 'region' => 'eu-west-1']), null, $client, $logger), $dispatcher, $logger),
7173
];
7274

7375
yield [
74-
new Dsn('ses+api', 'default', self::USER, self::PASSWORD, null, ['region' => 'eu-west-1']),
75-
new SesApiTransport(self::USER, self::PASSWORD, 'eu-west-1', $client, $dispatcher, $logger),
76+
new Dsn('ses+api', 'default', self::USER, self::PASSWORD, null, ['region' => 'eu-west-2']),
77+
new SesApiAsyncAwsTransport(new SesClient(Configuration::create(['accessKeyId' => self::USER, 'accessKeySecret' => self::PASSWORD, 'region' => 'eu-west-2']), null, $client, $logger), $dispatcher, $logger),
7678
];
7779

7880
yield [
7981
new Dsn('ses+api', 'example.com', self::USER, self::PASSWORD, 8080),
80-
(new SesApiTransport(self::USER, self::PASSWORD, null, $client, $dispatcher, $logger))->setHost('example.com')->setPort(8080),
82+
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),
8183
];
8284

8385
yield [
8486
new Dsn('ses+https', 'default', self::USER, self::PASSWORD),
85-
new SesHttpTransport(self::USER, self::PASSWORD, null, $client, $dispatcher, $logger),
87+
new SesHttpAsyncAwsTransport(new SesClient(Configuration::create(['accessKeyId' => self::USER, 'accessKeySecret' => self::PASSWORD, 'region' => 'eu-west-1']), null, $client, $logger), $dispatcher, $logger),
8688
];
8789

8890
yield [
8991
new Dsn('ses', 'default', self::USER, self::PASSWORD),
90-
new SesHttpTransport(self::USER, self::PASSWORD, null, $client, $dispatcher, $logger),
92+
new SesHttpAsyncAwsTransport(new SesClient(Configuration::create(['accessKeyId' => self::USER, 'accessKeySecret' => self::PASSWORD, 'region' => 'eu-west-1']), null, $client, $logger), $dispatcher, $logger),
9193
];
9294

9395
yield [
9496
new Dsn('ses+https', 'example.com', self::USER, self::PASSWORD, 8080),
95-
(new SesHttpTransport(self::USER, self::PASSWORD, null, $client, $dispatcher, $logger))->setHost('example.com')->setPort(8080),
97+
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),
9698
];
9799

98100
yield [
99-
new Dsn('ses+https', 'default', self::USER, self::PASSWORD, null, ['region' => 'eu-west-1']),
100-
new SesHttpTransport(self::USER, self::PASSWORD, 'eu-west-1', $client, $dispatcher, $logger),
101+
new Dsn('ses+https', 'default', self::USER, self::PASSWORD, null, ['region' => 'eu-west-2']),
102+
new SesHttpAsyncAwsTransport(new SesClient(Configuration::create(['accessKeyId' => self::USER, 'accessKeySecret' => self::PASSWORD, 'region' => 'eu-west-2']), null, $client, $logger), $dispatcher, $logger),
101103
];
102104

103105
yield [
@@ -127,7 +129,5 @@ public function unsupportedSchemeProvider(): iterable
127129
public function incompleteDsnProvider(): iterable
128130
{
129131
yield [new Dsn('ses+smtp', 'default', self::USER)];
130-
131-
yield [new Dsn('ses+smtp', 'default', null, self::PASSWORD)];
132132
}
133133
}

0 commit comments

Comments
 (0)