8000 feature #38277 [Mailer] Added Sendinblue bridge (drixs6o9) · symfony/symfony@5c48235 · GitHub
[go: up one dir, main page]

Skip to content

Commit 5c48235

Browse files
committed
feature #38277 [Mailer] Added Sendinblue bridge (drixs6o9)
This PR was squashed before being merged into the 5.2-dev branch. Discussion ---------- [Mailer] Added Sendinblue bridge | Q | A | ------------- | --- | Branch? | master | Bug fix? | no | New feature? | yes | Deprecations? | no | License | MIT | Doc PR | [https://github.com/symfony/symfony-docs/pull/14272](https://github.com/symfony/symfony-docs/pull/14272) | Recipe | [https://github.com/symfony/recipes/pull/822](https://github.com/symfony/recipes/pull/822) <!-- Replace this notice by a short README for your feature/bugfix. This will help people understand your PR and can be used as a start for the documentation. Additionally (see https://symfony.com/releases): - Always add tests and ensure they pass. - Never break backward compatibility (see https://symfony.com/bc). - Bug fixes must be submitted against the lowest maintained branch where they apply (lowest branches are regularly merged to upper ones so they get the fixes too.) - Features and deprecations must be submitted against branch master. --> N.B. I had a little help from [Pierre TONDEREAU](https://github.com/ptondereau) for the API. Commits ------- 836a203 [Mailer] Added Sendinblue bridge
2 parents 6e9949b + 836a203 commit 5c48235

File tree

16 files changed

+645
-0
lines changed

16 files changed

+645
-0
lines changed

src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@
8383
use Symfony\Component\Mailer\Bridge\Mailgun\Transport\MailgunTransportFactory;
8484
use Symfony\Component\Mailer\Bridge\Postmark\Transport\PostmarkTransportFactory;
8585
use Symfony\Component\Mailer\Bridge\Sendgrid\Transport\SendgridTransportFactory;
86+
use Symfony\Component\Mailer\Bridge\Sendinblue\Transport\SendinblueTransportFactory;
8687
use Symfony\Component\Mailer\Mailer;
8788
use Symfony\Component\Messenger\Bridge\AmazonSqs\Transport\AmazonSqsTransportFactory;
8889
use Symfony\Component\Messenger\Bridge\Amqp\Transport\AmqpTransportFactory;
@@ -2128,6 +2129,7 @@ private function registerMailerConfiguration(array $config, ContainerBuilder $co
21282129
MailgunTransportFactory::class => 'mailer.transport_factory.mailgun',
21292130
PostmarkTransportFactory::class => 'mailer.transport_factory.postmark',
21302131
SendgridTransportFactory::class => 'mailer.transport_factory.sendgrid',
2132+
SendinblueTransportFactory::class => 'mailer.transport_factory.sendinblue',
21312133
];
21322134

21332135
foreach ($classToServices as $class => $service) {

src/Symfony/Bundle/FrameworkBundle/Resources/config/mailer_transports.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
use Symfony\Component\Mailer\Bridge\Mailgun\Transport\MailgunTransportFactory;
1818
use Symfony\Component\Mailer\Bridge\Postmark\Transport\PostmarkTransportFactory;
1919
use Symfony\Component\Mailer\Bridge\Sendgrid\Transport\SendgridTransportFactory;
20+
use Symfony\Component\Mailer\Bridge\Sendinblue\Transport\SendinblueTransportFactory;
2021
use Symfony\Component\Mailer\Transport\AbstractTransportFactory;
2122
use Symfony\Component\Mailer\Transport\NativeTransportFactory;
2223
use Symfony\Component\Mailer\Transport\NullTransportFactory;
@@ -65,6 +66,10 @@
6566
->parent('mailer.transport_factory.abstract')
6667
->tag('mailer.transport_factory')
6768

69+
->set('mailer.transport_factory.sendinblue', SendinblueTransportFactory::class)
70+
->parent('mailer.transport_factory.abstract')
71+
->tag('mailer.transport_factory')
72+
6873
->set('mailer.transport_factory.smtp', EsmtpTransportFactory::class)
6974
->parent('mailer.transport_factory.abstract')
7075
->tag('mailer.transport_factory', ['priority' => -100])
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
/Tests export-ignore
2+
/phpunit.xml.dist export-ignore
3+
/.gitattributes export-ignore
4+
/.gitignore export-ignore
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
vendor/
2+
composer.lock
3+
phpunit.xml
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
CHANGELOG
2+
=========
3+
4+
5.2.0
5+
-----
6+
7+
* Added the bridge
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
Copyright (c) 2019-2020 Fabien Potencier
2+
3+
Permission is hereby granted, free of charge, to any person obtaining a copy
4+
of this software and associated documentation files (the "Software"), to deal
5+
in the Software without restriction, including without limitation the rights
6+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7+
copies of the Software, and to permit persons to whom the Software is furnished
8+
to do so, subject to the following conditions:
9+
10+
The above copyright notice and this permission notice shall be included in all
11+
copies or substantial portions of the Software.
12+
13+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19+
THE SOFTWARE.
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
Sendinblue Bridge
2+
=================
3+
4+
Provides Sendinblue integration for Symfony Mailer.
5+
6+
7+
Configuration example:
8+
9+
```env
10+
# API
11+
MAILER_DSN=sendinblue+api://$SENDINBLUE_API_KEY@default
12+
13+
# SMTP
14+
MAILER_DSN=sendinblue+smtp://$SENDINBLUE_USERNAME:$SENDINBLUE_PASSWORD@default
15+
```
16+
17+
With API, you can use custom headers.
18+
19+
```php
20+
$params = ['param1' => 'foo', 'param2' => 'bar'];
21+
$json = json_encode(['"custom_header_1' => 'custom_value_1']);
22+
23+
$email = new Email();
24+
$email
25+
->getHeaders()
26+
->add(new MetadataHeader('custom', $json))
27+
->add(new TagHeader('TagInHeaders1'))
28+
->add(new TagHeader('TagInHeaders2'))
29+
->addTextHeader('sender.ip', '1.2.3.4')
30+
->addTextHeader('templateId', 1)
31+
->addParameterizedHeader('params', 'params', $params)
32+
->addTextHeader('foo', 'bar')
33+
;
34+
```
35+
36+
This example allow you to set :
37+
38+
* templateId
39+
* params
40+
* tags
41+
* headers
42+
* sender.ip
43+
* X-Mailin-Custom
44+
45+
For more informations, you can refer to [Sendinblue API documentation](https://developers.sendinblue.com/reference#sendtransacemail).
46+
47+
Resources
48+
---------
49+
50+
* [Contributing](https://symfony.com/doc/current/contributing/index.html)
51+
* [Report issues](https://github.com/symfony/symfony/issues) and
52+
[send Pull Requests](https://github.com/symfony/symfony/pulls)
53+
in the [main Symfony repository](https://github.com/symfony/symfony)
Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
<?php
2+
3+
namespace Symfony\Component\Mailer\Bridge\Sendinblue\Tests\Transport;
4+
5+
use PHPUnit\Framework\TestCase;
6+
use Symfony\Component\HttpClient\MockHttpClient;
7+
use Symfony\Component\HttpClient\Response\MockResponse;
8+
use Symfony\Component\Mailer\Bridge\Sendinblue\Transport\SendinblueApiTransport;
9+
use Symfony\Component\Mailer\Envelope;
10+
use Symfony\Component\Mailer\Exception\HttpTransportException;
11+
use Symfony\Component\Mailer\Header\MetadataHeader;
12+
use Symfony\Component\Mailer\Header\TagHeader;
13+
use Symfony\Component\Mime\Address;
14+
use Symfony\Component\Mime\Email;
15+
use Symfony\Component\Mime\Part\DataPart;
16+
use Symfony\Contracts\HttpClient\ResponseInterface;
17+
18+
class SendinblueApiTransportTest extends TestCase
19+
{
20+
/**
21+
* @dataProvider getTransportData
22+
*/
23+
public function testToString(SendinblueApiTransport $transport, string $expected)
24+
{
25+
$this->assertSame($expected, (string) $transport);
26+
}
27+
28+
public function getTransportData()
29+
{
30+
yield [
31+
new SendinblueApiTransport('ACCESS_KEY'),
32+
'sendinblue+api://api.sendinblue.com',
33+
];
34+
35+
yield [
36+
(new SendinblueApiTransport('ACCESS_KEY'))->setHost('example.com'),
37+
'sendinblue+api://example.com',
38+
];
39+
40+
yield [
41+
(new SendinblueApiTransport('ACCESS_KEY'))->setHost('example.com')->setPort(99),
42+
'sendinblue+api://example.com:99',
43+
];
44+
}
45+
46+
public function testCustomHeader()
47+
{
48+
$params = ['param1' => 'foo', 'param2' => 'bar'];
49+
$json = json_encode(['"custom_header_1' => 'custom_value_1']);
50+
51+
$email = new Email();
52+
$email->getHeaders()
53+
->add(new MetadataHeader('custom', $json))
54+
->add(new TagHeader('TagInHeaders'))
55+
->addTextHeader('templateId', 1)
56+
->addParameterizedHeader('params', 'params', $params)
57+
->addTextHeader('foo', 'bar')
58+
;
59+
$envelope = new Envelope(new Address('alice@system.com', 'Alice'), [new Address('bob@system.com', 'Bob')]);
60+
61+
$transport = new SendinblueApiTransport('ACCESS_KEY');
62+
$method = new \ReflectionMethod(SendinblueApiTransport::class, 'getPayload');
63+
$method->setAccessible(true);
64+
$payload = $method->invoke($transport, $email, $envelope);
65+
66+
$this->assertArrayHasKey('X-Mailin-Custom', $payload['headers']);
67+
$this->assertEquals($json, $payload['headers']['X-Mailin-Custom']);
68+
69+
$this->assertArrayHasKey('tags', $payload);
70+
$this->assertEquals('TagInHeaders', current($payload['tags']));
71+
$this->assertArrayHasKey('templateId', $payload);
72+
$this->assertEquals(1, $payload['templateId']);
73+
$this->assertArrayHasKey('params', $payload);
74+
$this->assertEquals('foo', $payload['params']['param1']);
75+
$this->assertEquals('bar', $payload['params']['param2']);
76+
$this->assertArrayHasKey('foo', $payload['headers']);
77+
$this->assertEquals('bar', $payload['headers']['foo']);
78+
}
79+
80+
public function testSendThrowsForErrorResponse()
81+
{
82+
$client = new MockHttpClient(function (string $method, string $url, array $options): ResponseInterface {
83+
$this->assertSame('POST', $method);
84+
$this->assertSame('https://api.sendinblue.com:8984/v3/smtp/email', $url);
85+
$this->assertStringContainsString('Accept: */*', $options['headers'][2] ?? $options['request_headers'][1]);
86+
87+
return new MockResponse(json_encode(['message' => 'i\'m a teapot']), [
88+
'http_code' => 418,
89+
'response_headers' => [
90+
'content-type' => 'application/json',
91+
],
92+
]);
93+
});
94+
95+
$transport = new SendinblueApiTransport('ACCESS_KEY', $client);
96+
$transport->setPort(8984);
97+
98+
$mail = new Email();
99+
$mail->subject('Hello!')
100+
->to(new Address('saif.gmati@symfony.com', 'Saif Eddin'))
101+
->from(new Address('fabpot@symfony.com', 'Fabien'))
102+
->text('Hello There!')
103+
;
104+
105+
$this->expectException(HttpTransportException::class);
106+
$this->expectExceptionMessage('Unable to send an email: i\'m a teapot (code 418).');
107+
$transport->send($mail);
108+
}
109+
110+
public function testSend()
111+
{
112+
$client = new MockHttpClient(function (string $method, string $url, array $options): ResponseInterface {
113+
$this->assertSame('POST', $method);
114+
$this->assertSame('https://api.sendinblue.com:8984/v3/smtp/email', $url);
115+
$this->assertStringContainsString('Accept: */*', $options['headers'][2] ?? $options['request_headers'][1]);
116+
117+
return new MockResponse(json_encode(['messageId' => 'foobar']), [
118+
'http_code' => 201,
119+
]);
120+
});
121+
122+
$transport = new SendinblueApiTransport('ACCESS_KEY', $client);
123+
$transport->setPort(8984);
124+
125+
$dataPart = new DataPart('body');
126+
$mail = new Email();
127+
$mail->subject('Hello!')
128+
->to(new Address('saif.gmati@symfony.com', 'Saif Eddin'))
129+
->from(new Address('fabpot@symfony.com', 'Fabien'))
130+
->text('Hello here!')
131+
->html('Hello there!')
132+
->addCc('foo@bar.fr')
133+
->addBcc('foo@bar.fr')
134+
->addReplyTo('foo@bar.fr')
135+
->attachPart($dataPart)
136+
;
137+
138+
$message = $transport->send($mail);
139+
140+
$this->assertSame('foobar', $message->getMessageId());
141+
}
142+
}
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
<?php
2+
3+
namespace Symfony\Component\Mailer\Bridge\Sendinblue\Tests\Transport;
4+
5+
use Symfony\Component\Mailer\Bridge\Sendinblue\Transport\SendinblueApiTransport;
6+
use Symfony\Component\Mailer\Bridge\Sendinblue\Transport\SendinblueSmtpTransport;
7+
use Symfony\Component\Mailer\Bridge\Sendinblue\Transport\SendinblueTransportFactory;
8+
use Symfony\Component\Mailer\Test\TransportFactoryTestCase;
9+
use Symfony\Component\Mailer\Transport\Dsn;
10+
use Symfony\Component\Mailer\Transport\TransportFactoryInterface;
11+
12+
class SendinblueTransportFactoryTest extends TransportFactoryTestCase
13+
{
14+
public function getFactory(): TransportFactoryInterface
15+
{
16+
return new SendinblueTransportFactory($this->getDispatcher(), $this->getClient(), $this->getLogger());
17+
}
18+
19+
public function supportsProvider(): iterable
20+
{
21+
yield [
22+
new Dsn('sendinblue', 'default'),
23+
true,
24+
];
25+
26+
yield [
27+
new Dsn('sendinblue+smtp', 'default'),
28+
true,
29+
];
30+
31+
yield [
32+
new Dsn('sendinblue+smtp', 'example.com'),
33+
true,
34+
];
35+
36+
yield [
37+
new Dsn('sendinblue+api', 'default'),
38+
true,
39+
];
40+
}
41+
42+
public function createProvider(): iterable
43+
{
44+
yield [
45+
new Dsn('sendinblue', 'default', self::USER, self::PASSWORD),
46+
new SendinblueSmtpTransport(self::USER, self::PASSWORD, $this->getDispatcher(), $this->getLogger()),
47+
];
48+
49+
yield [
50+
new Dsn('sendinblue+smtp', 'default', self::USER, self::PASSWORD),
51+
new SendinblueSmtpTransport(self::USER, self::PASSWORD, $this->getDispatcher(), $this->getLogger()),
52+
];
53+
54+
yield [
55+
new Dsn('sendinblue+smtp', 'default', self::USER, self::PASSWORD, 465),
56+
new SendinblueSmtpTransport(self::USER, self::PASSWORD, $this->getDispatcher(), $this->getLogger()),
57+
];
58+
59+
yield [
60+
new Dsn('sendinblue+api', 'default', self::USER),
61+
new SendinblueApiTransport(self::USER, $this->getClient(), $this->getDispatcher(), $this->getLogger()),
62+
];
63+
}
64+
65+
public function unsupportedSchemeProvider(): iterable
66+
{
67+
yield [
68+
new Dsn('sendinblue+foo', 'default', self::USER, self::PASSWORD),
69+
'The "sendinblue+foo" scheme is not supported; supported schemes for mailer "sendinblue" are: "sendinblue", "sendinblue+smtp", "sendinblue+api".',
70+
];
71+
}
72+
73+
public function incompleteDsnProvider(): iterable
74+
{
75+
yield [new Dsn('sendinblue+smtp', 'default', self::USER)];
76+
77+
yield [new Dsn('sendinblue+smtp', 'default', null, self::PASSWORD)];
78+
79+
yield [new Dsn('sendinblue+api', 'default')];
80+
}
81+
}

0 commit comments

Comments
 (0)
0