8000 [Mailer] Use AsyncAws to handle SES requests · symfony/symfony@2124387 · GitHub
[go: up one dir, main page]

Skip to content

Commit 2124387

Browse files
jderussefabpot
authored andcommitted
[Mailer] Use AsyncAws to handle SES requests
1 parent c6cf433 commit 2124387

15 files changed

+502
-24
lines changed

UPGRADE-5.1.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,8 @@ Mailer
7272
------
7373

7474
* Deprecated passing Mailgun headers without their "h:" prefix.
75+
* Deprecated the `SesApiTransport` class. It has been replaced by SesApiAsyncAwsTransport Run `composer require async-aws/ses` to use the new classes.
76+
* Deprecated the `SesHttpTransport` class. It has been replaced by SesHttpAsyncAwsTransport Run `composer require async-aws/ses` to use the new classes.
7577

7678
Messenger
7779
---------

UPGRADE-6.0.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,13 @@ HttpKernel
6464
* Made `WarmableInterface::warmUp()` return a list of classes or files to preload on PHP 7.4+
6565
* Removed support for `service:action` syntax to reference controllers. Use `serviceOrFqcn::method` instead.
6666

67+
68+
Mailer
69+
------
70+
71+
* Removed the `SesApiTransport` class. Use `SesApiAsyncAwsTransport` instead.
72+
* Removed the `SesHttpTransport` class. Use `SesHttpAsyncAwsTransport` instead.
73+
6774
Messenger
6875
---------
6976

composer.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,7 @@
104104
"require-dev": {
105105
"amphp/http-client": "^4.2",
106106
"amphp/http-tunnel": "^1.0",
107+
"async-aws/ses": "^1.0",
107108
"cache/integration-tests": "dev-master",
108109
"doctrine/annotations": "~1.0",
109110
"doctrine/cache": "~1.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: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
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\Core\Credentials\NullProvider;
16+
use AsyncAws\Ses\SesClient;
17+
use PHPUnit\Framework\TestCase;
18+
use Symfony\Component\HttpClient\MockHttpClient;
19+
use Symfony\Component\HttpClient\Response\MockResponse;
20+
use Symfony\Component\Mailer\Bridge\Amazon\Transport\SesApiAsyncAwsTransport;
21+
use Symfony\Component\Mailer\Exception\HttpTransportException;
22+
use Symfony\Component\Mime\Address;
23+
use Symfony\Component\Mime\Email;
24+
use Symfony\Contracts\HttpClient\ResponseInterface;
25+
26+
class SesApiAsyncAwsTransportTest extends TestCase
27+
{
28+
/**
29+
* @dataProvider getTransportData
30+
*/
31+
public function testToString(SesApiAsyncAwsTransport $transport, string $expected)
32+
{
33+
$this->assertSame($expected, (string) $transport);
34+
}
35+
36+
public function getTransportData()
37+
{
38+
return [
39+
[
40+
new SesApiAsyncAwsTransport(new SesClient(Configuration::create(['accessKeyId' => 'ACCESS_KEY', 'accessKeySecret' => 'SECRET_KEY']))),
41+
'ses+api://ACCESS_KEY@us-east-1',
42+
],
43+
[
44+
new SesApiAsyncAwsTransport(new SesClient(Configuration::create(['accessKeyId' => 'ACCESS_KEY', 'accessKeySecret' => 'SECRET_KEY', 'region' => 'us-west-1']))),
45+
'ses+api://ACCESS_KEY@us-west-1',
46+
],
47+
[
48+
new SesApiAsyncAwsTransport(new SesClient(Configuration::create(['accessKeyId' => 'ACCESS_KEY', 'accessKeySecret' => 'SECRET_KEY', 'endpoint' => 'https://example.com']))),
49+
'ses+api://ACCESS_KEY@example.com',
50+
],
51+
[
52+
new SesApiAsyncAwsTransport(new SesClient(Configuration::create(['accessKeyId' => 'ACCESS_KEY', 'accessKeySecret' => 'SECRET_KEY', 'endpoint' => 'https://example.com:99']))),
53+
'ses+api://ACCESS_KEY@example.com:99',
54+
],
55+
];
56+
}
57+
58+
public function testSend()
59+
{
60+
$client = new MockHttpClient(function (string $method, string $url, array $options): ResponseInterface {
61+
$this->assertSame('POST', $method);
62+
$this->assertSame('https://email.us-east-1.amazonaws.com/v2/email/outbound-emails', $url);
63+
64+
$content = json_decode($options['body'], true);
65+
66+
$this->assertSame('Hello!', $content['Content']['Simple']['Subject']['Data']);
67+
$this->assertSame('Saif Eddin <saif.gmati@symfony.com>', $content['Destination']['ToAddresses'][0]);
68+
$this->assertSame('Fabien <fabpot@symfony.com>', $content['FromEmailAddress']);
69+
$this->assertSame('Hello There!', $content['Content']['Simple']['Body']['Text']['Data']);
70+
$this->assertSame('<b>Hello There!</b>', $content['Content']['Simple']['Body']['Html']['Data']);
71+
72+
$json = '{"MessageId": "foobar"}';
73+
74+
return new MockResponse($json, [
75+
'http_code' => 200,
76+
]);
77+
});
78+
79+
$transport = new SesApiAsyncAwsTransport(new SesClient(Configuration::create([]), new NullProvider(), $client));
80+
81+
$mail = new Email();
82+
$mail->subject('Hello!')
83+
->to(new Address('saif.gmati@symfony.com', 'Saif Eddin'))
84+
->from(new Address('fabpot@symfony.com', 'Fabien'))
85+
->text('Hello There!')
86+
->html('<b>Hello There!</b>');
87+
88+
$message = $transport->send($mail);
89+
90+
$this->assertSame('foobar', $message->getMessageId());
91+
}
92+
93+
public function testSendThrowsForErrorResponse()
94+
{
95+
$client = new MockHttpClient(function (string $method, string $url, array $options): ResponseInterface {
96+
$xml = "<SendEmailResponse xmlns=\"https://email.amazonaws.com/doc/2010-03-31/\">
97+
<Error>
98+
<Message>i'm a teapot</Message>
99+
<Code>418</Code>
100+
</Error>
101+
</SendEmailResponse>";
102+
103+
return new MockResponse($xml, [
104+
'http_code' => 418,
105+
]);
106+
});
107+
108+
$transport = new SesApiAsyncAwsTransport(new SesClient(Configuration::create([]), new NullProvider(), $client));
109+
110+
$mail = new Email();
111+
$mail->subject('Hello!')
112+
->to(new Address('saif.gmati@symfony.com', 'Saif Eddin'))
113+
->from(new Address('fabpot@symfony.com', 'Fabien'))
114+
->text('Hello There!');
115+
116+
$this->expectException(HttpTransportException::class);
117+
$this->expectExceptionMessage('Unable to send an email: i\'m a teapot (code 418).');
118+
$transport->send($mail);
119+
}
120+
}

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,9 @@
2020
use Symfony\Component\Mime\Email;
2121
use Symfony\Contracts\HttpClient\ResponseInterface;
2222

23+
/**
24+
* @group legacy
25+
*/
2326
class SesApiTransportTest extends TestCase
2427
{
2528
/**
Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
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\Core\Credentials\NullProvider;
16+
use AsyncAws\Ses\SesClient;
17+
use PHPUnit\Framework\TestCase;
18+
use Symfony\Component\HttpClient\MockHttpClient;
19+
use Symfony\Component\HttpClient\Response\MockResponse;
20+
use Symfony\Component\Mailer\Bridge\Amazon\Transport\SesHttpAsyncAwsTransport;
21+
use Symfony\Component\Mailer\Exception\HttpTransportException;
22+
use Symfony\Component\Mime\Address;
23+
use Symfony\Component\Mime\Email;
24+
use Symfony\Contracts\HttpClient\ResponseInterface;
25+
26+
class SesHttpAsyncAwsTransportTest extends TestCase
27+
{
28+
/**
29+
* @dataProvider getTransportData
30+
*/
31+
public function testToString(SesHttpAsyncAwsTransport $transport, string $expected)
32+
{
33+
$this->assertSame($expected, (string) $transport);
34+
}
35+
36+
public function getTransportData()
37+
{
38+
return [
39+
[
40+
new SesHttpAsyncAwsTransport(new SesClient(Configuration::create(['accessKeyId' => 'ACCESS_KEY', 'accessKeySecret' => 'SECRET_KEY']))),
41+
'ses+https://ACCESS_KEY@us-east-1',
42+
],
43+
[
44+
new SesHttpAsyncAwsTransport(new SesClient(Configuration::create(['accessKeyId' => 'ACCESS_KEY', 'accessKeySecret' => 'SECRET_KEY', 'region' => 'us-west-1']))),
45+
'ses+https://ACCESS_KEY@us-west-1',
46+
],
47+
[
48+
new SesHttpAsyncAwsTransport(new SesClient(Configuration::create(['accessKeyId' => 'ACCESS_KEY', 'accessKeySecret' => 'SECRET_KEY', 'endpoint' => 'https://example.com']))),
49+
'ses+https://ACCESS_KEY@example.com',
50+
],
51+
[
52+
new SesHttpAsyncAwsTransport(new SesClient(Configuration::create(['accessKeyId' => 'ACCESS_KEY', 'accessKeySecret' => 'SECRET_KEY', 'endpoint' => 'https://example.com:99']))),
53+
'ses+https://ACCESS_KEY@example.com:99',
54+
],
55+
];
56+
}
57+
58+
public function testSend()
59+
{
60+
$client = new MockHttpClient(function (string $method, string $url, array $options): ResponseInterface {
61+
$this->assertSame('POST', $method);
62+
$this->assertSame('https://email.us-east-1.amazonaws.com/v2/email/outbound-emails', $url);
63+
64+
$body = json_decode($options['body'], true);
65+
$content = base64_decode($body['Content']['Raw']['Data']);
66+
67+
$this->assertStringContainsString('Hello!', $content);
68+
$this->assertStringContainsString('Saif Eddin <saif.gmati@symfony.com>', $content);
69+
$this->assertStringContainsString('Fabien <fabpot@symfony.com>', $content);
70+
$this->assertStringContainsString('Hello There!', $content);
71+
72+
$json = '{"MessageId": "foobar"}';
73+
74+
return new MockResponse($json, [
75+
'http_code' => 200,
76+
]);
77+
});
78+
79+
$transport = new SesHttpAsyncAwsTransport(new SesClient(Configuration::create([]), new NullProvider(), $client));
80+
81+
$mail = new Email();
82+
$mail->subject('Hello!')
83+
->to(new Address('saif.gmati@symfony.com', 'Saif Eddin'))
84+
->from(new Address('fabpot@symfony.com', 'Fabien'))
85+
->text('Hello There!');
86+
87+
$message = $transport->send($mail);
88 BD94 +
89+
$this->assertSame('foobar', $message->getMessageId());
90+
}
91+
92+
public function testSendThrowsForErrorResponse()
93+
{
94+
$client = new MockHttpClient(function (string $method, string $url, array $options): ResponseInterface {
95+
$xml = "<SendEmailResponse xmlns=\"https://email.amazonaws.com/doc/2010-03-31/\">
96+
<Error>
97+
<Message>i'm a teapot</Message>
98+
<Code>418</Code>
99+
</Error>
100+
</SendEmailResponse>";
101+
102+
return new MockResponse($xml, [
103+
'http_code' => 418,
104+
]);
105+
});
106+
107+
$transport = new SesHttpAsyncAwsTransport(new SesClient(Configuration::create([]), new NullProvider(), $client));
108+
109+
$mail = new Email();
110+
$mail->subject('Hello!')
111+
->to(new Address('saif.gmati@symfony.com', 'Saif Eddin'))
112+
->from(new Address('fabpot@symfony.com', 'Fabien'))
113+
->text('Hello There!');
114+
115+
$this->expectException(HttpTransportException::class);
116+
$this->expectExceptionMessage('Unable to send an email: i\'m a teapot (code 418).');
117+
$transport->send($mail);
118+
}
119+
}

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,9 @@
2020
use Symfony\Component\Mime\Email;
2121
use Symfony\Contracts\HttpClient\ResponseInterface;
2222

23+
/**
24+
* @group legacy
25+
*/
2326
class SesHttpTransportTest extends TestCase
2427
{
2528
/**

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

Lines changed: 13 additions & 13 deletions
< 93A5 /tr>
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)
0