Skip to content

Commit ab00efd

Browse files
committed
Add Sweego Mailer Bridge
1 parent 9ed27d0 commit ab00efd

17 files changed

+620
-0
lines changed

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

+1
Original file line numberDiff line numberDiff line change
@@ -2627,6 +2627,7 @@ private function registerMailerConfiguration(array $config, ContainerBuilder $co
26272627
MailerBridge\Scaleway\Transport\ScalewayTransportFactory::class => 'mailer.transport_factory.scaleway',
26282628
MailerBridge\Sendgrid\Transport\SendgridTransportFactory::class => 'mailer.transport_factory.sendgrid',
26292629
MailerBridge\Amazon\Transport\SesTransportFactory::class => 'mailer.transport_factory.amazon',
2630+
MailerBridge\Sweego\Transport\SweegoTransportFactory::class => 'mailer.transport_factory.sweego',
26302631
];
26312632

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

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

+2
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
use Symfony\Component\Mailer\Bridge\Resend\Transport\ResendTransportFactory;
2626
use Symfony\Component\Mailer\Bridge\Scaleway\Transport\ScalewayTransportFactory;
2727
use Symfony\Component\Mailer\Bridge\Sendgrid\Transport\SendgridTransportFactory;
28+
use Symfony\Component\Mailer\Bridge\Sweego\Transport\SweegoTransportFactory;
2829
use Symfony\Component\Mailer\Transport\AbstractTransportFactory;
2930
use Symfony\Component\Mailer\Transport\NativeTransportFactory;
3031
use Symfony\Component\Mailer\Transport\NullTransportFactory;
@@ -61,6 +62,7 @@
6162
'sendgrid' => SendgridTransportFactory::class,
6263
'sendmail' => SendmailTransportFactory::class,
6364
'smtp' => EsmtpTransportFactory::class,
65+
'sweego' => SweegoTransportFactory::class,
6466
];
6567

6668
foreach ($factories as $name => $class) {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
/Tests export-ignore
2+
/phpunit.xml.dist export-ignore
3+
/.git* export-ignore
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
vendor/
2+
composer.lock
3+
phpunit.xml
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
CHANGELOG
2+
=========
3+
4+
7.2
5+
---
6+
7+
* Add the bridge
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
Copyright (c) 2024-present 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.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
Sweego Bridge
2+
=============
3+
4+
Provides Sweego integration for Symfony Mailer.
5+
6+
Configuration example:
7+
8+
```env
9+
# SMTP
10+
MAILER_DSN=sweego+smtp://LOGIN:PASSWORD@HOST:PORT
11+
```
12+
13+
where:
14+
- `LOGIN` is your Sweego SMTP login
15+
- `PASSWORD` is your Sweego SMTP password
16+
- `HOST` is your Sweego SMTP host
17+
- `PORT` is your Sweego SMTP port
18+
19+
```env
20+
# API
21+
MAILER_DSN=sweego+api://API_KEY@default
22+
```
23+
24+
where:
25+
- `API_KEY` is your Sweego API Key
26+
27+
Resources
28+
---------
29+
30+
* [Contributing](https://symfony.com/doc/current/contributing/index.html)
31+
* [Report issues](https://github.com/symfony/symfony/issues) and
32+
[send Pull Requests](https://github.com/symfony/symfony/pulls)
33+
in the [main Symfony repository](https://github.com/symfony/symfony)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,176 @@
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\Sweego\Tests\Transport;
13+
14+
use PHPUnit\Framework\TestCase;
15+
use Symfony\Component\HttpClient\MockHttpClient;
16+
use Symfony\Component\HttpClient\Response\JsonMockResponse;
17+
use Symfony\Component\Mailer\Bridge\Sweego\Transport\SweegoApiTransport;
18+
use Symfony\Component\Mailer\Envelope;
19+
use Symfony\Component\Mailer\Exception\HttpTransportException;
20+
use Symfony\Component\Mailer\Header\MetadataHeader;
21+
use Symfony\Component\Mime\Address;
22+
use Symfony\Component\Mime\Email;
23+
use Symfony\Contracts\HttpClient\ResponseInterface;
24+
25+
class SweegoApiTransportTest extends TestCase
26+
{
27+
/**
28+
* @dataProvider getTransportData
29+
*/
30+
public function testToString(SweegoApiTransport $transport, string $expected)
31+
{
32+
$this->assertSame($expected, (string) $transport);
33+
}
34+
35+
public static function getTransportData(): \Generator
36+
{
37+
yield [
38+
new SweegoApiTransport('ACCESS_KEY'),
39+
'sweego+api://api.sweego.io',
40+
];
41+
42+
yield [
43+
(new SweegoApiTransport('ACCESS_KEY'))->setHost('example.com'),
44+
'sweego+api://example.com',
45+
];
46+
47+
yield [
48+
(new SweegoApiTransport('ACCESS_KEY'))->setHost('example.com')->setPort(99),
49+
'sweego+api://example.com:99',
50+
];
51+
}
52+
53+
public function testCustomHeader()
54+
{
55+
$params = ['param1' => 'foo', 'param2' => 'bar'];
56+
$json = json_encode(['custom_header_1' => 'custom_value_1']);
57+
58+
$email = new Email();
59+
$email->getHeaders()
60+
->add(new MetadataHeader('custom', $json))
61+
->addTextHeader('templateId', 1)
62+
->addParameterizedHeader('params', 'params', $params)
63+
->addTextHeader('foo', 'bar');
64+
$envelope = new Envelope(new Address('alice@system.com', 'Alice'), [new Address('bob@system.com', 'Bob')]);
65+
66+
$transport = new SweegoApiTransport('ACCESS_KEY');
67+
$method = new \ReflectionMethod(SweegoApiTransport::class, 'getPayload');
68+
$payload = $method->invoke($transport, $email, $envelope);
69+
70+
$this->assertArrayHasKey('X-Metadata-custom', $payload['headers']);
71+
$this->assertEquals($json, $payload['headers']['X-Metadata-custom']);
72+
$this->assertArrayHasKey('templateId', $payload['headers']);
73+
$this->assertEquals('1', $payload['headers']['templateId']);
74+
$this->assertArrayHasKey('params', $payload['headers']);
75+
$this->assertEquals('params; param1=foo; param2=bar', $payload['headers']['params']);
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.sweego.io:8984/send', $url);
85+
$this->assertStringContainsString('Accept: */*', $options['headers'][2] ?? $options['request_headers'][1]);
86+
87+
return new JsonMockResponse(['message' => 'i\'m a teapot'], [
88+
'http_code' => 418,
89+
]);
90+
});
91+
92+
$transport = new SweegoApiTransport('ACCESS_KEY', $client);
93+
$transport->setPort(8984);
94+
95+
$mail = new Email();
96+
$mail->subject('Hello!')
97+
->to(new Address('tony.stark@marvel.com', 'Tony Stark'))
98+
->from(new Address('fabpot@symfony.com', 'Fabien'))
99+
->text('Hello There!');
100+
101+
$this->expectException(HttpTransportException::class);
102+
$this->expectExceptionMessage('Unable to send an email: {"message":"i\'m a teapot"} (code 418).');
103+
$transport->send($mail);
104+
}
105+
106+
public function testSend()
107+
{
108+
$client = new MockHttpClient(function (string $method, string $url, array $options): ResponseInterface {
109+
$this->assertSame('POST', $method);
110+
$this->assertSame('https://api.sweego.io:8984/send', $url);
111+
$this->assertStringContainsString('Accept: */*', $options['headers'][2] ?? $options['request_headers'][1]);
112+
113+
return new JsonMockResponse(['id' => 'foobar'], [
114+
'http_code' => 200,
115+
]);
116+
});
117+
118+
$transport = new SweegoApiTransport('ACCESS_KEY', $client);
119+
$transport->setPort(8984);
120+
121+
$mail = new Email();
122+
$mail->subject('Hello!')
123+
->to(new Address('tony.stark@marvel.com', 'Tony Stark'))
124+
->from(new Address('fabpot@symfony.com', 'Fabien'))
125+
->text('Hello here!')
126+
->html('Hello there!')
127+
->addCc('foo@bar.fr')
128+
->addBcc('foo@bar.fr')
129+
;
130+
131+
$message = $transport->send($mail);
132+
133+
$this->assertSame('foobar', $message->getMessageId());
134+
}
135+
136+
/**
137+
* IDN (internationalized domain names) like kältetechnik-xyz.de need to be transformed to ACE
138+
* (ASCII Compatible Encoding) e.g.xn--kltetechnik-xyz-0kb.de, otherwise Sweego api answers with 400 http code.
139+
*/
140+
public function testSendForIdnDomains()
141+
{
142+
$client = new MockHttpClient(function (string $method, string $url, array $options): ResponseInterface {
143+
$this->assertSame('POST', $method);
144+
$this->assertSame('https://api.sweego.io:8984/send', $url);
145+
$this->assertStringContainsString('Accept: */*', $options['headers'][2] ?? $options['request_headers'][1]);
146+
147+
$body = json_decode($options['body'], true);
148+
// to
149+
$this->assertSame([
150+
'email' => 'kältetechnik@xn--kltetechnik-xyz-0kb.de',
151+
'name' => 'Kältetechnik Xyz',
152+
], $body['recipients'][0]);
153+
// sender
154+
$this->assertStringContainsString('info@xn--kltetechnik-xyz-0kb.de', $body['from']['email']);
155+
$this->assertStringContainsString('Kältetechnik Xyz', $body['from']['name']);
156+
157+
return new JsonMockResponse(['id' => 'foobar'], [
158+
'http_code' => 200,
159+
]);
160+
});
161+
162+
$transport = new SweegoApiTransport('ACCESS_KEY', $client);
163+
$transport->setPort(8984);
164+
165+
$mail = new Email();
166+
$mail->subject('Hello!')
167+
->to(new Address('kältetechnik@kältetechnik-xyz.de', 'Kältetechnik Xyz'))
168+
->from(new Address('info@kältetechnik-xyz.de', 'Kältetechnik Xyz'))
169+
->text('Hello here!')
170+
->html('Hello there!');
171+
172+
$message = $transport->send($mail);
173+
174+
$this->assertSame('foobar', $message->getMessageId());
175+
}
176+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
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\Sweego\Tests\Transport;
13+
14+
use Psr\Log\NullLogger;
15+
use Symfony\Component\HttpClient\MockHttpClient;
16+
use Symfony\Component\Mailer\Bridge\Sweego\Transport\SweegoApiTransport;
17+
use Symfony\Component\Mailer\Bridge\Sweego\Transport\SweegoSmtpTransport;
18+
use Symfony\Component\Mailer\Bridge\Sweego\Transport\SweegoTransportFactory;
19+
use Symfony\Component\Mailer\Test\TransportFactoryTestCase;
20+
use Symfony\Component\Mailer\Transport\Dsn;
21+
use Symfony\Component\Mailer\Transport\TransportFactoryInterface;
22+
23+
class SweegoTransportFactoryTest extends TransportFactoryTestCase
24+
{
25+
public function getFactory(): TransportFactoryInterface
26+
{
27+
return new SweegoTransportFactory(null, new MockHttpClient(), new NullLogger());
28+
}
29+
30+
public static function supportsProvider(): iterable
31+
{
32+
yield [
33+
new Dsn('sweego', 'default'),
34+
true,
35+
];
36+
37+
yield [
38+
new Dsn('sweego+smtp', 'default'),
39+
true,
40+
];
41+
42+
yield [
43+
new Dsn('sweego+smtp', 'example.com'),
44+
true,
45+
];
46+
47+
yield [
48+
new Dsn('sweego+api', 'default'),
49+
true,
50+
];
51+
}
52+
53+
public static function createProvider(): iterable
54+
{
55+
yield [
56+
new Dsn('sweego', 'default', self::USER, self::PASSWORD, 465),
57+
new SweegoSmtpTransport('default', 465, self::USER, self::PASSWORD, null, new NullLogger()),
58+
];
59+
60+
yield [
61+
new Dsn('sweego+smtp', 'default', self::USER, self::PASSWORD, 465),
62+
new SweegoSmtpTransport('default', 465, self::USER, self::PASSWORD, null, new NullLogger()),
63+
];
64+
65+
yield [
66+
new Dsn('sweego+smtp', 'default', self::USER, self::PASSWORD, 465),
67+
new SweegoSmtpTransport('default', 465, self::USER, self::PASSWORD, null, new NullLogger()),
68+
];
69+
70+
yield [
71+
new Dsn('sweego+api', 'default', self::USER),
72+
new SweegoApiTransport(self::USER, new MockHttpClient(), null, new NullLogger()),
73+
];
74+
}
75+
76+
public static function unsupportedSchemeProvider(): iterable
77+
{
78+
yield [
79+
new Dsn('sweego+foo', 'default', self::USER, self::PASSWORD, 465),
80+
'The "sweego+foo" scheme is not supported; supported schemes for mailer "sweego" are: "sweego", "sweego+smtp", "sweego+api".',
81+
];
82+
}
83+
84+
public static function incompleteDsnProvider(): iterable
85+
{
86+
yield [new Dsn('sweego+smtp', 'default', self::USER)];
87+
88+
yield [new Dsn('sweego+api', 'default')];
89+
}
90+
}

0 commit comments

Comments
 (0)