diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php index a7749cd30faad..0c87c4aea26f5 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php @@ -2670,6 +2670,7 @@ private function registerMailerConfiguration(array $config, ContainerBuilder $co } $classToServices = [ + MailerBridge\AhaSend\Transport\AhaSendTransportFactory::class => 'mailer.transport_factory.ahasend', MailerBridge\Azure\Transport\AzureTransportFactory::class => 'mailer.transport_factory.azure', MailerBridge\Brevo\Transport\BrevoTransportFactory::class => 'mailer.transport_factory.brevo', MailerBridge\Google\Transport\GmailTransportFactory::class => 'mailer.transport_factory.gmail', @@ -2700,6 +2701,7 @@ private function registerMailerConfiguration(array $config, ContainerBuilder $co if ($webhookEnabled) { $webhookRequestParsers = [ + MailerBridge\AhaSend\Webhook\AhaSendRequestParser::class => 'mailer.webhook.request_parser.ahasend', MailerBridge\Brevo\Webhook\BrevoRequestParser::class => 'mailer.webhook.request_parser.brevo', MailerBridge\MailerSend\Webhook\MailerSendRequestParser::class => 'mailer.webhook.request_parser.mailersend', MailerBridge\Mailchimp\Webhook\MailchimpRequestParser::class => 'mailer.webhook.request_parser.mailchimp', diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/mailer_transports.php b/src/Symfony/Bundle/FrameworkBundle/Resources/config/mailer_transports.php index c0e7cc06a4eb8..2c79b4d55556f 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/mailer_transports.php +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/mailer_transports.php @@ -11,6 +11,7 @@ namespace Symfony\Component\DependencyInjection\Loader\Configurator; +use Symfony\Component\Mailer\Bridge\AhaSend\Transport\AhaSendTransportFactory; use Symfony\Component\Mailer\Bridge\Amazon\Transport\SesTransportFactory; use Symfony\Component\Mailer\Bridge\Azure\Transport\AzureTransportFactory; use Symfony\Component\Mailer\Bridge\Brevo\Transport\BrevoTransportFactory; @@ -47,6 +48,7 @@ ->tag('monolog.logger', ['channel' => 'mailer']); $factories = [ + 'ahasend' => AhaSendTransportFactory::class, 'amazon' => SesTransportFactory::class, 'azure' => AzureTransportFactory::class, 'brevo' => BrevoTransportFactory::class, diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/mailer_webhook.php b/src/Symfony/Bundle/FrameworkBundle/Resources/config/mailer_webhook.php index c574324db0b9f..b815336b2528f 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/mailer_webhook.php +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/mailer_webhook.php @@ -11,6 +11,8 @@ namespace Symfony\Component\DependencyInjection\Loader\Configurator; +use Symfony\Component\Mailer\Bridge\AhaSend\RemoteEvent\AhaSendPayloadConverter; +use Symfony\Component\Mailer\Bridge\AhaSend\Webhook\AhaSendRequestParser; use Symfony\Component\Mailer\Bridge\Brevo\RemoteEvent\BrevoPayloadConverter; use Symfony\Component\Mailer\Bridge\Brevo\Webhook\BrevoRequestParser; use Symfony\Component\Mailer\Bridge\Mailchimp\RemoteEvent\MailchimpPayloadConverter; @@ -86,6 +88,11 @@ ->args([service('mailer.payload_converter.sweego')]) ->alias(SweegoRequestParser::class, 'mailer.webhook.request_parser.sweego') + ->set('mailer.payload_converter.ahasend', AhaSendPayloadConverter::class) + ->set('mailer.webhook.request_parser.ahasend', AhaSendRequestParser::class) + ->args([service('mailer.payload_converter.ahasend')]) + ->alias(AhaSendRequestParser::class, 'mailer.webhook.request_parser.ahasend') + ->set('mailer.payload_converter.mailchimp', MailchimpPayloadConverter::class) ->set('mailer.webhook.request_parser.mailchimp', MailchimpRequestParser::class) ->args([service('mailer.payload_converter.mailchimp')]) diff --git a/src/Symfony/Component/Mailer/Bridge/AhaSend/.gitattributes b/src/Symfony/Component/Mailer/Bridge/AhaSend/.gitattributes new file mode 100644 index 0000000000000..14c3c35940427 --- /dev/null +++ b/src/Symfony/Component/Mailer/Bridge/AhaSend/.gitattributes @@ -0,0 +1,3 @@ +/Tests export-ignore +/phpunit.xml.dist export-ignore +/.git* export-ignore diff --git a/src/Symfony/Component/Mailer/Bridge/AhaSend/.github/PULL_REQUEST_TEMPLATE.md b/src/Symfony/Component/Mailer/Bridge/AhaSend/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000000000..4689c4dad430e --- /dev/null +++ b/src/Symfony/Component/Mailer/Bridge/AhaSend/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,8 @@ +Please do not submit any Pull Requests here. They will be closed. +--- + +Please submit your PR here instead: +https://github.com/symfony/symfony + +This repository is what we call a "subtree split": a read-only subset of that main repository. +We're looking forward to your PR there! diff --git a/src/Symfony/Component/Mailer/Bridge/AhaSend/.github/workflows/close-pull-request.yml b/src/Symfony/Component/Mailer/Bridge/AhaSend/.github/workflows/close-pull-request.yml new file mode 100644 index 0000000000000..e55b47817e69a --- /dev/null +++ b/src/Symfony/Component/Mailer/Bridge/AhaSend/.github/workflows/close-pull-request.yml @@ -0,0 +1,20 @@ +name: Close Pull Request + +on: + pull_request_target: + types: [opened] + +jobs: + run: + runs-on: ubuntu-latest + steps: + - uses: superbrothers/close-pull-request@v3 + with: + comment: | + Thanks for your Pull Request! We love contributions. + + However, you should instead open your PR on the main repository: + https://github.com/symfony/symfony + + This repository is what we call a "subtree split": a read-only subset of that main repository. + We're looking forward to your PR there! diff --git a/src/Symfony/Component/Mailer/Bridge/AhaSend/.gitignore b/src/Symfony/Component/Mailer/Bridge/AhaSend/.gitignore new file mode 100644 index 0000000000000..c49a5d8df5c65 --- /dev/null +++ b/src/Symfony/Component/Mailer/Bridge/AhaSend/.gitignore @@ -0,0 +1,3 @@ +vendor/ +composer.lock +phpunit.xml diff --git a/src/Symfony/Component/Mailer/Bridge/AhaSend/CHANGELOG.md b/src/Symfony/Component/Mailer/Bridge/AhaSend/CHANGELOG.md new file mode 100644 index 0000000000000..20bfa193845de --- /dev/null +++ b/src/Symfony/Component/Mailer/Bridge/AhaSend/CHANGELOG.md @@ -0,0 +1,6 @@ +CHANGELOG +========= + +7.3 +--- + * Add the bridge diff --git a/src/Symfony/Component/Mailer/Bridge/AhaSend/Event/AhaSendDeliveryEvent.php b/src/Symfony/Component/Mailer/Bridge/AhaSend/Event/AhaSendDeliveryEvent.php new file mode 100644 index 0000000000000..b0710630b2869 --- /dev/null +++ b/src/Symfony/Component/Mailer/Bridge/AhaSend/Event/AhaSendDeliveryEvent.php @@ -0,0 +1,25 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Mailer\Bridge\AhaSend\Event; + +class AhaSendDeliveryEvent +{ + public function __construct( + private readonly string $message, + ) { + } + + public function getMessage(): string + { + return $this->message; + } +} diff --git a/src/Symfony/Component/Mailer/Bridge/AhaSend/LICENSE b/src/Symfony/Component/Mailer/Bridge/AhaSend/LICENSE new file mode 100644 index 0000000000000..e374a5c8339d3 --- /dev/null +++ b/src/Symfony/Component/Mailer/Bridge/AhaSend/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2024-present Fabien Potencier + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/src/Symfony/Component/Mailer/Bridge/AhaSend/README.md b/src/Symfony/Component/Mailer/Bridge/AhaSend/README.md new file mode 100644 index 0000000000000..8e118251522ac --- /dev/null +++ b/src/Symfony/Component/Mailer/Bridge/AhaSend/README.md @@ -0,0 +1,27 @@ +AhaSend Bridge +============== + +Provides AhaSend integration for Symfony Mailer. + +Configuration example: + +```env +# SMTP +MAILER_DSN=ahasend+smtp://USERNAME:PASSWORD@default + +# API +MAILER_DSN=ahasend+api://API_KEY@default +``` + +where: + - `USERNAME` is your AhaSend SMTP Credentials username + - `PASSWORD` is your AhaSend SMTP Credentials password + - `API_KEY` is your AhaSend API Key credential + +Resources +--------- + + * [Contributing](https://symfony.com/doc/current/contributing/index.html) + * [Report issues](https://github.com/symfony/symfony/issues) and + [send Pull Requests](https://github.com/symfony/symfony/pulls) + in the [main Symfony repository](https://github.com/symfony/symfony) diff --git a/src/Symfony/Component/Mailer/Bridge/AhaSend/RemoteEvent/AhaSendPayloadConverter.php b/src/Symfony/Component/Mailer/Bridge/AhaSend/RemoteEvent/AhaSendPayloadConverter.php new file mode 100644 index 0000000000000..5f411bdf670ef --- /dev/null +++ b/src/Symfony/Component/Mailer/Bridge/AhaSend/RemoteEvent/AhaSendPayloadConverter.php @@ -0,0 +1,58 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Mailer\Bridge\AhaSend\RemoteEvent; + +use Symfony\Component\RemoteEvent\Event\Mailer\AbstractMailerEvent; +use Symfony\Component\RemoteEvent\Event\Mailer\MailerDeliveryEvent; +use Symfony\Component\RemoteEvent\Event\Mailer\MailerEngagementEvent; +use Symfony\Component\RemoteEvent\Exception\ParseException; +use Symfony\Component\RemoteEvent\PayloadConverterInterface; + +final class AhaSendPayloadConverter implements PayloadConverterInterface +{ + public function convert(array $payload): AbstractMailerEvent + { + if (\in_array($payload['type'], ['message.clicked', 'message.opened'])) { + $name = match ($payload['type']) { + 'message.clicked' => MailerEngagementEvent::CLICK, + 'message.opened' => MailerEngagementEvent::OPEN, + default => throw new ParseException(\sprintf('Unsupported event "%s".', $payload['type'])), + }; + $event = new MailerEngagementEvent($name, $payload['data']['id'], $payload); + } elseif (str_starts_with($payload['type'], 'message.')) { + $name = match ($payload['type']) { + 'message.reception' => MailerDeliveryEvent::RECEIVED, + 'message.delivered' => MailerDeliveryEvent::DELIVERED, + 'message.transient_error' => MailerDeliveryEvent::DEFERRED, + 'message.failed', 'message.bounced' => MailerDeliveryEvent::BOUNCE, + 'message.suppressed' => MailerDeliveryEvent::DROPPED, + default => throw new ParseException(\sprintf('Unsupported event "%s".', $payload['type'])), + }; + $event = new MailerDeliveryEvent($name, $payload['data']['id'], $payload); + } else { + // suppressions and domain DNS problem webhooks. Ignore them for now. + throw new ParseException(\sprintf('Unsupported event "%s".', $payload['type'])); + } + + // AhaSend sends timestamps with 9 decimal places for nanosecond precision, + // truncate to 6 decimal places for microseconds. + $truncatedTimestamp = substr($payload['timestamp'], 0, 26).'Z'; + $date = \DateTimeImmutable::createFromFormat(\DateTimeImmutable::ATOM, $payload['timestamp']) ?: \DateTimeImmutable::createFromFormat('Y-m-d\TH:i:s.uT', $truncatedTimestamp); + if (!$date) { + throw new ParseException(\sprintf('Invalid date "%s".', $payload['timestamp'])); + } + $event->setDate($date); + $event->setRecipientEmail($payload['data']['recipient']); + + return $event; + } +} diff --git a/src/Symfony/Component/Mailer/Bridge/AhaSend/Tests/Transport/AhaSendApiTransportTest.php b/src/Symfony/Component/Mailer/Bridge/AhaSend/Tests/Transport/AhaSendApiTransportTest.php new file mode 100644 index 0000000000000..2f9e09ede715a --- /dev/null +++ b/src/Symfony/Component/Mailer/Bridge/AhaSend/Tests/Transport/AhaSendApiTransportTest.php @@ -0,0 +1,306 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Mailer\Bridge\AhaSend\Tests\Transport; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\EventDispatcher\EventDispatcherInterface; +use Symfony\Component\HttpClient\MockHttpClient; +use Symfony\Component\HttpClient\Response\JsonMockResponse; +use Symfony\Component\Mailer\Bridge\AhaSend\Event\AhaSendDeliveryEvent; +use Symfony\Component\Mailer\Bridge\AhaSend\Transport\AhaSendApiTransport; +use Symfony\Component\Mailer\Envelope; +use Symfony\Component\Mailer\Header\TagHeader; +use Symfony\Component\Mime\Address; +use Symfony\Component\Mime\Email; +use Symfony\Component\Mime\Part\DataPart; +use Symfony\Contracts\HttpClient\ResponseInterface; + +class AhaSendApiTransportTest extends TestCase +{ + /** + * @dataProvider getTransportData + */ + public function testToString(AhaSendApiTransport $transport, string $expected) + { + $this->assertSame($expected, (string) $transport); + } + + public static function getTransportData() + { + return [ + [ + new AhaSendApiTransport('KEY'), + 'ahasend+api://send.ahasend.com', + ], + [ + (new AhaSendApiTransport('KEY'))->setHost('example.com'), + 'ahasend+api://example.com', + ], + [ + (new AhaSendApiTransport('KEY'))->setHost('example.com')->setPort(99), + 'ahasend+api://example.com:99', + ], + ]; + } + + public function testSend() + { + $email = new Email(); + $email->from(new Address('foo@example.com', 'Ms. Foo Bar')) + ->to(new Address('bar@example.com', 'Mr. Recipient')) + ->bcc('baz@example.com') + ->subject('An email') + ->text('Test email body') + ->html('
Test email body
') + ->replyTo(new Address('bar2@example.com', 'Mr. Recipient')); + + $client = new MockHttpClient(function (string $method, string $url, array $options): ResponseInterface { + $this->assertSame('POST', $method); + $this->assertSame('https://send.ahasend.com/v1/email/send', $url); + $this->assertStringContainsString('X-Api-Key: foo', $options['headers'][0] ?? $options['request_headers'][0]); + + $body = json_decode($options['body'], true); + $this->assertSame('foo@example.com', $body['from']['email']); + $this->assertSame('Ms. Foo Bar', $body['from']['name']); + $this->assertSame('bar@example.com', $body['recipients'][0]['email']); + $this->assertSame('Mr. Recipient', $body['recipients'][0]['name']); + $this->assertSame('baz@example.com', $body['recipients'][1]['email']); + $this->assertArrayNotHasKey('name', $body['recipients'][1]); + $this->assertSame('An email', $body['content']['subject']); + $this->assertSame('Test email body', $body['content']['text_body']); + $this->assertSame('Test email body
', $body['content']['html_body']); + $this->assertSame('bar2@example.com', $body['content']['reply_to']['email']); + $this->assertSame('Mr. Recipient', $body['content']['reply_to']['name']); + $this->assertSame('baz@example.com', $body['content']['headers']['Bcc']); + + return new JsonMockResponse([ + 'success_count' => 3, + 'fail_count' => 0, + 'failed_recipients' => [], + 'errors' => [], + ], [ + 'http_code' => 201, + ]); + }); + + $mailer = new AhaSendApiTransport('foo', $client); + $mailer->send($email); + } + + public function testSendDeliveryEventIsDispatched() + { + $responseFactory = new JsonMockResponse([ + 'success_count' => 0, + 'fail_count' => 1, + 'failed_recipients' => [ + 'someone@gmil.com', + ], + 'errors' => [ + 'someone@gmil.com: Invalid recipient', + ], + ], [ + 'http_code' => 201, + ]); + $client = new MockHttpClient($responseFactory); + + $email = new Email(); + $email->from(new Address('foo@example.com', 'Ms. Foo Bar')) + ->to(new Address('someone@gmil.com', 'Mr. Someone')) + ->subject('An email') + ->text('Test email body'); + + $expectedEvent = (new AhaSendDeliveryEvent('someone@gmil.com: Invalid recipient')); + + $dispatcher = $this->createMock(EventDispatcherInterface::class); + $dispatcher + ->method('dispatch') + ->willReturnCallback(function ($event) use ($expectedEvent) { + if ($event instanceof AhaSendDeliveryEvent) { + $this->assertEquals($event, $expectedEvent); + } + + return $event; + }); + + $transport = new AhaSendApiTransport('foo', $client, $dispatcher); + + $transport->send($email); + } + + public function testCustomHeader() + { + $email = new Email(); + $email->getHeaders()->addTextHeader('foo', 'bar'); + $envelope = new Envelope(new Address('alice@system.com'), [new Address('bob@system.com')]); + + $transport = new AhaSendApiTransport('ACCESS_KEY'); + $method = new \ReflectionMethod(AhaSendApiTransport::class, 'getPayload'); + $payload = $method->invoke($transport, $email, $envelope); + + $this->assertArrayHasKey('headers', $payload['content']); + $this->assertArrayHasKey('foo', $payload['content']['headers']); + $this->assertEquals('bar', $payload['content']['headers']['foo']); + } + + public function testReplyTo() + { + $from = 'from@example.com'; + $to = 'to@example.com'; + $replyTo = 'replyto@example.com'; + $email = new Email(); + $email->from($from) + ->to($to) + ->replyTo($replyTo) + ->text('content'); + $envelope = new Envelope(new Address($from), [new Address($to)]); + + $transport = new AhaSendApiTransport('ACCESS_KEY'); + $method = new \ReflectionMethod(AhaSendApiTransport::class, 'getPayload'); + $payload = $method->invoke($transport, $email, $envelope); + + $this->assertArrayHasKey('from', $payload); + $this->assertArrayHasKey('email', $payload['from']); + $this->assertSame($from, $payload['from']['email']); + + $this->assertArrayHasKey('reply_to', $payload['content']); + $this->assertArrayHasKey('email', $payload['content']['reply_to']); + $this->assertSame($replyTo, $payload['content']['reply_to']['email']); + } + + public function testEnvelopeSenderAndRecipients() + { + $from = 'from@example.com'; + $to = 'to@example.com'; + $envelopeFrom = 'envelopefrom@example.com'; + $envelopeTo = 'envelopeto@example.com'; + $email = new Email(); + $email->from($from) + ->to($to) + ->cc('cc@example.com') + ->bcc('bcc@example.com') + ->text('content'); + $envelope = new Envelope(new Address($envelopeFrom), [new Address($envelopeTo), new Address('cc@example.com'), new Address('bcc@example.com')]); + + $transport = new AhaSendApiTransport('ACCESS_KEY'); + $method = new \ReflectionMethod(AhaSendApiTransport::class, 'getPayload'); + $payload = $method->invoke($transport, $email, $envelope); + + $this->assertArrayHasKey('from', $payload); + $this->assertArrayHasKey('email', $payload['from']); + $this->assertSame($envelopeFrom, $payload['from']['email']); + + $this->assertArrayHasKey('recipients', $payload); + $this->assertArrayHasKey('email', $payload['recipients'][0]); + $this->assertCount(3, $payload['recipients']); + $this->assertSame($envelopeTo, $payload['recipients'][0]['email']); + } + + public function testTagHeaders() + { + $email = new Email(); + $email->getHeaders()->add(new TagHeader('category-one')); + $email->getHeaders()->add(new TagHeader('category-two')); + $envelope = new Envelope(new Address('alice@system.com'), [new Address('bob@system.com')]); + + $transport = new AhaSendApiTransport('ACCESS_KEY'); + $method = new \ReflectionMethod(AhaSendApiTransport::class, 'getPayload'); + $payload = $method->invoke($transport, $email, $envelope); + + $this->assertArrayHasKey('headers', $payload['content']); + $this->assertArrayHasKey('AhaSend-Tags', $payload['content']['headers']); + + $this->assertCount(1, $payload['content']['headers']); + $this->assertCount(2, explode(',', $payload['content']['headers']['AhaSend-Tags'])); + + $this->assertSame('category-one,category-two', $payload['content']['headers']['AhaSend-Tags']); + } + + public function testInlineWithCustomContentId() + { + $imagePart = (new DataPart('text-contents', 'text.txt')); + $imagePart->asInline(); + $imagePart->setContentId('content-identifier@symfony'); + + $email = new Email(); + $email->addPart($imagePart); + $envelope = new Envelope(new Address('alice@system.com'), [new Address('bob@system.com')]); + + $transport = new AhaSendApiTransport('ACCESS_KEY'); + $method = new \ReflectionMethod(AhaSendApiTransport::class, 'getPayload'); + $payload = $method->invoke($transport, $email, $envelope); + + $this->assertArrayHasKey('attachments', $payload['content']); + $this->assertCount(1, $payload['content']['attachments']); + $this->assertArrayHasKey('content_id', $payload['content']['attachments'][0]); + + $this->assertSame('content-identifier@symfony', $payload['content']['attachments'][0]['content_id']); + } + + public function testInlineWithoutCustomContentId() + { + $imagePart = (new DataPart('text-contents', 'text.txt')); + $imagePart->asInline(); + + $email = new Email(); + $email->addPart($imagePart); + $envelope = new Envelope(new Address('alice@system.com'), [new Address('bob@system.com')]); + + $transport = new AhaSendApiTransport('ACCESS_KEY'); + $method = new \ReflectionMethod(AhaSendApiTransport::class, 'getPayload'); + $payload = $method->invoke($transport, $email, $envelope); + + $this->assertArrayHasKey('attachments', $payload['content']); + $this->assertCount(1, $payload['content']['attachments']); + $this->assertArrayHasKey('content_id', $payload['content']['attachments'][0]); + + $this->assertSame('text.txt', $payload['content']['attachments'][0]['content_id']); + } + + public function testAttachmentWithBase64Encoding() + { + $textPart = (new DataPart('image-contents', 'image.png')); + + $email = new Email(); + $email->addPart($textPart); + $envelope = new Envelope(new Address('alice@system.com'), [new Address('bob@system.com')]); + + $transport = new AhaSendApiTransport('ACCESS_KEY'); + $method = new \ReflectionMethod(AhaSendApiTransport::class, 'getPayload'); + $payload = $method->invoke($transport, $email, $envelope); + + $this->assertArrayHasKey('attachments', $payload['content']); + $this->assertCount(1, $payload['content']['attachments']); + $this->assertArrayHasKey('base64', $payload['content']['attachments'][0]); + + $this->assertTrue($payload['content']['attachments'][0]['base64']); + $this->assertNotSame('image-contents', $payload['content']['attachments'][0]['data']); + } + + public function testAttachmentWithoutBase64Encoding() + { + $textPart = (new DataPart('text-contents', 'text.txt', 'text/plain')); + + $email = new Email(); + $email->addPart($textPart); + $envelope = new Envelope(new Address('alice@system.com'), [new Address('bob@system.com')]); + + $transport = new AhaSendApiTransport('ACCESS_KEY'); + $method = new \ReflectionMethod(AhaSendApiTransport::class, 'getPayload'); + $payload = $method->invoke($transport, $email, $envelope); + + $this->assertArrayHasKey('attachments', $payload['content']); + $this->assertCount(1, $payload['content']['attachments']); + $this->assertArrayHasKey('base64', $payload['content']['attachments'][0]); + + $this->assertFalse($payload['content']['attachments'][0]['base64']); + } +} diff --git a/src/Symfony/Component/Mailer/Bridge/AhaSend/Tests/Transport/AhaSendSmtpTransportTest.php b/src/Symfony/Component/Mailer/Bridge/AhaSend/Tests/Transport/AhaSendSmtpTransportTest.php new file mode 100644 index 0000000000000..581a0601a4f16 --- /dev/null +++ b/src/Symfony/Component/Mailer/Bridge/AhaSend/Tests/Transport/AhaSendSmtpTransportTest.php @@ -0,0 +1,47 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Mailer\Bridge\AhaSend\Tests\Transport; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Mailer\Bridge\AhaSend\Transport\AhaSendSmtpTransport; +use Symfony\Component\Mailer\Header\TagHeader; +use Symfony\Component\Mime\Email; + +class AhaSendSmtpTransportTest extends TestCase +{ + public function testCustomHeader() + { + $email = new Email(); + $email->getHeaders()->addTextHeader('foo', 'bar'); + + $transport = new AhaSendSmtpTransport('USERNAME', 'PASSWORD'); + $method = new \ReflectionMethod(AhaSendSmtpTransport::class, 'addAhaSendHeaders'); + $method->invoke($transport, $email); + + $this->assertCount(1, $email->getHeaders()->toArray()); + $this->assertSame('foo: bar', $email->getHeaders()->get('FOO')->toString()); + } + + public function testMultipleTags() + { + $email = new Email(); + $email->getHeaders()->add(new TagHeader('tag1')); + $email->getHeaders()->add(new TagHeader('tag2')); + + $transport = new AhaSendSmtpTransport('USERNAME', 'PASSWORD'); + $method = new \ReflectionMethod(AhaSendSmtpTransport::class, 'addAhaSendHeaders'); + + $method->invoke($transport, $email); + $headers = $email->getHeaders(); + $this->assertSame('AhaSend-Tags: tag1,tag2', $email->getHeaders()->get('AhaSend-Tags')->toString()); + } +} diff --git a/src/Symfony/Component/Mailer/Bridge/AhaSend/Tests/Transport/AhaSendTransportFactoryTest.php b/src/Symfony/Component/Mailer/Bridge/AhaSend/Tests/Transport/AhaSendTransportFactoryTest.php new file mode 100644 index 0000000000000..445d4e5208705 --- /dev/null +++ b/src/Symfony/Component/Mailer/Bridge/AhaSend/Tests/Transport/AhaSendTransportFactoryTest.php @@ -0,0 +1,100 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Mailer\Bridge\AhaSend\Tests\Transport; + +use Psr\Log\NullLogger; +use Symfony\Component\HttpClient\MockHttpClient; +use Symfony\Component\Mailer\Bridge\AhaSend\Transport\AhaSendApiTransport; +use Symfony\Component\Mailer\Bridge\AhaSend\Transport\AhaSendSmtpTransport; +use Symfony\Component\Mailer\Bridge\AhaSend\Transport\AhaSendTransportFactory; +use Symfony\Component\Mailer\Test\AbstractTransportFactoryTestCase; +use Symfony\Component\Mailer\Test\IncompleteDsnTestTrait; +use Symfony\Component\Mailer\Transport\Dsn; +use Symfony\Component\Mailer\Transport\TransportFactoryInterface; + +class AhaSendTransportFactoryTest extends AbstractTransportFactoryTestCase +{ + use IncompleteDsnTestTrait; + + public function getFactory(): TransportFactoryInterface + { + return new AhaSendTransportFactory(null, new MockHttpClient(), new NullLogger()); + } + + public static function supportsProvider(): iterable + { + yield [ + new Dsn('ahasend+api', 'default'), + true, + ]; + + yield [ + new Dsn('ahasend', 'default'), + true, + ]; + + yield [ + new Dsn('ahasend+smtp', 'default'), + true, + ]; + + yield [ + new Dsn('ahasend+smtp', 'example.com'), + true, + ]; + } + + public static function createProvider(): iterable + { + $logger = new NullLogger(); + + yield [ + new Dsn('ahasend+api', 'default', self::USER), + new AhaSendApiTransport(self::USER, new MockHttpClient(), null, $logger), + ]; + + yield [ + new Dsn('ahasend+api', 'example.com', self::USER, '', 8080), + (new AhaSendApiTransport(self::USER, new MockHttpClient(), null, $logger))->setHost('example.com')->setPort(8080), + ]; + + yield [ + new Dsn('ahasend+api', 'example.com', self::USER, '', 8080, ['message_stream' => 'broadcasts']), + (new AhaSendApiTransport(self::USER, new MockHttpClient(), null, $logger))->setHost('example.com')->setPort(8080), + ]; + + yield [ + new Dsn('ahasend', 'default', self::USER, self::PASSWORD), + new AhaSendSmtpTransport(self::USER, self::PASSWORD, null, $logger), + ]; + + yield [ + new Dsn('ahasend+smtp', 'default', self::USER, self::PASSWORD), + new AhaSendSmtpTransport(self::USER, self::PASSWORD, null, $logger), + ]; + } + + public static function unsupportedSchemeProvider(): iterable + { + yield [ + new Dsn('ahasend+foo', 'default', self::USER), + 'The "ahasend+foo" scheme is not supported; supported schemes for mailer "ahasend" are: "ahasend", "ahasend+api", "ahasend+smtp".', + ]; + } + + public static function incompleteDsnProvider(): iterable + { + yield [new Dsn('ahasend+api', 'default')]; + yield [new Dsn('ahasend+smtp', 'default', self::USER)]; + yield [new Dsn('ahasend', 'default', self::USER)]; + } +} diff --git a/src/Symfony/Component/Mailer/Bridge/AhaSend/Tests/Webhook/AhaSendRequestParserTest.php b/src/Symfony/Component/Mailer/Bridge/AhaSend/Tests/Webhook/AhaSendRequestParserTest.php new file mode 100644 index 0000000000000..2a25869e97439 --- /dev/null +++ b/src/Symfony/Component/Mailer/Bridge/AhaSend/Tests/Webhook/AhaSendRequestParserTest.php @@ -0,0 +1,54 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Mailer\Bridge\AhaSend\Tests\Webhook; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\Mailer\Bridge\AhaSend\RemoteEvent\AhaSendPayloadConverter; +use Symfony\Component\Mailer\Bridge\AhaSend\Webhook\AhaSendRequestParser; +use Symfony\Component\Webhook\Client\RequestParserInterface; +use Symfony\Component\Webhook\Test\AbstractRequestParserTestCase; + +class AhaSendRequestParserTest extends AbstractRequestParserTestCase +{ + private const SECRET = 'nxLe:L:fZLb7J_Wb3uFeWX/&z4Ed#9&DxPL%Ud&:jhpAW1gLaR%AEFwfKnwp60cC'; + + protected function createRequestParser(): RequestParserInterface + { + return new AhaSendRequestParser(new AhaSendPayloadConverter()); + } + + protected function createRequest(string $payload): Request + { + $payloadArray = json_decode($payload, true); + + $currentDir = \dirname((new \ReflectionClass(static::class))->getFileName()); + $type = str_replace('message.', '', $payloadArray['type']); + $headers = file_get_contents($currentDir.'/Fixtures/'.$type.'_headers.txt'); + $server = [ + 'Content-Type' => 'application/json', + ]; + foreach (explode("\n", $headers) as $row) { + $header = explode(':', $row); + if (2 == \count($header)) { + $server['HTTP_'.$header[0]] = $header[1]; + } + } + $payload = json_encode($payloadArray, \JSON_UNESCAPED_SLASHES); + + return Request::create('/', 'POST', [], [], [], $server, $payload); + } + + protected function getSecret(): string + { + return self::SECRET; + } +} diff --git a/src/Symfony/Component/Mailer/Bridge/AhaSend/Tests/Webhook/Fixtures/bounced.json b/src/Symfony/Component/Mailer/Bridge/AhaSend/Tests/Webhook/Fixtures/bounced.json new file mode 100644 index 0000000000000..2599c67e33fe9 --- /dev/null +++ b/src/Symfony/Component/Mailer/Bridge/AhaSend/Tests/Webhook/Fixtures/bounced.json @@ -0,0 +1,15 @@ +{ + "type": "message.bounced", + "timestamp": "2024-10-27T19:35:58.267106256Z", + "data": { + "account_id": "4cdd7bdd-294e-4762-892f-83d40abf5a87", + "event": "on_bounced", + "from": "info@example.com", + "recipient": "someone@example.com", + "subject": "Sample email for testing webhooks", + "message_id_header": "message-id-header", + "user_agent": "", + "is_bot": "", + "id": "ahasend-message-id" + } +} diff --git a/src/Symfony/Component/Mailer/Bridge/AhaSend/Tests/Webhook/Fixtures/bounced.php b/src/Symfony/Component/Mailer/Bridge/AhaSend/Tests/Webhook/Fixtures/bounced.php new file mode 100644 index 0000000000000..b38e668903535 --- /dev/null +++ b/src/Symfony/Component/Mailer/Bridge/AhaSend/Tests/Webhook/Fixtures/bounced.php @@ -0,0 +1,9 @@ +setRecipientEmail('someone@example.com'); +$wh->setDate(\DateTimeImmutable::createFromFormat('Y-m-d\TH:i:s.uT', '2024-10-27T19:35:58.267106Z')); + +return $wh; diff --git a/src/Symfony/Component/Mailer/Bridge/AhaSend/Tests/Webhook/Fixtures/bounced_headers.txt b/src/Symfony/Component/Mailer/Bridge/AhaSend/Tests/Webhook/Fixtures/bounced_headers.txt new file mode 100644 index 0000000000000..b0840a72f9f45 --- /dev/null +++ b/src/Symfony/Component/Mailer/Bridge/AhaSend/Tests/Webhook/Fixtures/bounced_headers.txt @@ -0,0 +1,3 @@ +webhook-id:ijDIIJKmF2EmV9oZ7B9t5Uwx9rEB0coJAGeVxhJgyU0UBIWeXcyzMgW6KfG3Iwel +webhook-timestamp:1730057863 +webhook-signature:v1,eY+xFpew7iX16FK8dPLWepQ9XVpSmzLBlUx7fqLBStw= diff --git a/src/Symfony/Component/Mailer/Bridge/AhaSend/Tests/Webhook/Fixtures/clicked.json b/src/Symfony/Component/Mailer/Bridge/AhaSend/Tests/Webhook/Fixtures/clicked.json new file mode 100644 index 0000000000000..4c5a17fb5af3d --- /dev/null +++ b/src/Symfony/Component/Mailer/Bridge/AhaSend/Tests/Webhook/Fixtures/clicked.json @@ -0,0 +1,17 @@ +{ + "type": "message.clicked", + "timestamp": "2024-10-28T18:30:01.7994491Z", + "data": { + "account_id": "4cdd7bdd-294e-4762-892f-83d40abf5a87", + "event": "on_clicked", + "from": "info@example.com", + "recipient": "someone@example.com", + "subject": "Sample email for testing webhooks", + "message_id_header": "message-id-header", + "url": "https://ahasend.com", + "user_agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/42.0.2311.135 Safari/537.36 Edge/12.246", + "ip": "1.2.3.4", + "id": "ahasend-message-id", + "is_bot": false + } +} diff --git a/src/Symfony/Component/Mailer/Bridge/AhaSend/Tests/Webhook/Fixtures/clicked.php b/src/Symfony/Component/Mailer/Bridge/AhaSend/Tests/Webhook/Fixtures/clicked.php new file mode 100644 index 0000000000000..aeda8913a27ef --- /dev/null +++ b/src/Symfony/Component/Mailer/Bridge/AhaSend/Tests/Webhook/Fixtures/clicked.php @@ -0,0 +1,9 @@ +setRecipientEmail('someone@example.com'); +$wh->setDate(\DateTimeImmutable::createFromFormat('Y-m-d\TH:i:s.uT', '2024-10-28T18:30:01.799449Z')); + +return $wh; diff --git a/src/Symfony/Component/Mailer/Bridge/AhaSend/Tests/Webhook/Fixtures/clicked_headers.txt b/src/Symfony/Component/Mailer/Bridge/AhaSend/Tests/Webhook/Fixtures/clicked_headers.txt new file mode 100644 index 0000000000000..cbd19e7d60e35 --- /dev/null +++ b/src/Symfony/Component/Mailer/Bridge/AhaSend/Tests/Webhook/Fixtures/clicked_headers.txt @@ -0,0 +1,3 @@ +webhook-id:rxIB4LsE0TB0OxIyQQuEHhZ3GEIgYOKVlJ0u30g5xiEFQ8NiZqIsE9Vl8KDUtvy9 +webhook-timestamp:1730140201 +webhook-signature:v1,xGcu5GnTMTlN8qUGvKu8vu8bzTvMQfVoR6UReFjacis= diff --git a/src/Symfony/Component/Mailer/Bridge/AhaSend/Tests/Webhook/Fixtures/delivered.json b/src/Symfony/Component/Mailer/Bridge/AhaSend/Tests/Webhook/Fixtures/delivered.json new file mode 100644 index 0000000000000..4a6fc3a05e062 --- /dev/null +++ b/src/Symfony/Component/Mailer/Bridge/AhaSend/Tests/Webhook/Fixtures/delivered.json @@ -0,0 +1,15 @@ +{ + "type": "message.delivered", + "timestamp": "2024-10-27T19:37:30.928534039Z", + "data": { + "account_id": "4cdd7bdd-294e-4762-892f-83d40abf5a87", + "event": "on_delivered", + "from": "info@example.com", + "recipient": "someone@example.com", + "subject": "Sample email for testing webhooks", + "message_id_header": "message-id-header", + "user_agent": "", + "is_bot": "", + "id": "ahasend-message-id" + } +} diff --git a/src/Symfony/Component/Mailer/Bridge/AhaSend/Tests/Webhook/Fixtures/delivered.php b/src/Symfony/Component/Mailer/Bridge/AhaSend/Tests/Webhook/Fixtures/delivered.php new file mode 100644 index 0000000000000..7f802156ba3ef --- /dev/null +++ b/src/Symfony/Component/Mailer/Bridge/AhaSend/Tests/Webhook/Fixtures/delivered.php @@ -0,0 +1,9 @@ +setRecipientEmail('someone@example.com'); +$wh->setDate(\DateTimeImmutable::createFromFormat('Y-m-d\TH:i:s.uT', '2024-10-27T19:37:30.928534Z')); + +return $wh; diff --git a/src/Symfony/Component/Mailer/Bridge/AhaSend/Tests/Webhook/Fixtures/delivered_headers.txt b/src/Symfony/Component/Mailer/Bridge/AhaSend/Tests/Webhook/Fixtures/delivered_headers.txt new file mode 100644 index 0000000000000..960bf966c9c41 --- /dev/null +++ b/src/Symfony/Component/Mailer/Bridge/AhaSend/Tests/Webhook/Fixtures/delivered_headers.txt @@ -0,0 +1,3 @@ +webhook-id:mA5gq7fS2T3jAVOpJZVHX077DYrpZv3IOe0CULKb2aBIqgAxZoRpuWYeEgZQdK3m +webhook-timestamp:1730057850 +webhook-signature:v1,vqb6WYaPO7TO47N1/X9TGUmPcoHfAM9TpBgCM3TmaQI= diff --git a/src/Symfony/Component/Mailer/Bridge/AhaSend/Tests/Webhook/Fixtures/opened.json b/src/Symfony/Component/Mailer/Bridge/AhaSend/Tests/Webhook/Fixtures/opened.json new file mode 100644 index 0000000000000..d8ed7927b4098 --- /dev/null +++ b/src/Symfony/Component/Mailer/Bridge/AhaSend/Tests/Webhook/Fixtures/opened.json @@ -0,0 +1,16 @@ +{ + "type": "message.opened", + "timestamp": "2024-10-28T18:30:01.797985335Z", + "data": { + "account_id": "4cdd7bdd-294e-4762-892f-83d40abf5a87", + "event": "on_opened", + "from": "info@example.com", + "recipient": "someone@example.com", + "subject": "Sample email for testing webhooks", + "message_id_header": "message-id-header", + "user_agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/42.0.2311.135 Safari/537.36 Edge/12.246", + "ip": "1.2.3.4", + "is_bot": "", + "id": "ahasend-message-id" + } +} diff --git a/src/Symfony/Component/Mailer/Bridge/AhaSend/Tests/Webhook/Fixtures/opened.php b/src/Symfony/Component/Mailer/Bridge/AhaSend/Tests/Webhook/Fixtures/opened.php new file mode 100644 index 0000000000000..7c324b0c10390 --- /dev/null +++ b/src/Symfony/Component/Mailer/Bridge/AhaSend/Tests/Webhook/Fixtures/opened.php @@ -0,0 +1,9 @@ +setRecipientEmail('someone@example.com'); +$wh->setDate(\DateTimeImmutable::createFromFormat('Y-m-d\TH:i:s.uT', '2024-10-28T18:30:01.797985Z')); + +return $wh; diff --git a/src/Symfony/Component/Mailer/Bridge/AhaSend/Tests/Webhook/Fixtures/opened_headers.txt b/src/Symfony/Component/Mailer/Bridge/AhaSend/Tests/Webhook/Fixtures/opened_headers.txt new file mode 100644 index 0000000000000..9f87a715e4f5b --- /dev/null +++ b/src/Symfony/Component/Mailer/Bridge/AhaSend/Tests/Webhook/Fixtures/opened_headers.txt @@ -0,0 +1,3 @@ +webhook-id:CbxBj2jz0iDsncSDo4cQROSnRG2UaArVNKRyd6vq8Ds7MbYf0Wnu9tBC0HbTTtNO +webhook-timestamp:1730140201 +webhook-signature:v1,qxyLy8OGBe0vs1T7ht/0XbMNlsPClvqQJL5Es7qQWu4= diff --git a/src/Symfony/Component/Mailer/Bridge/AhaSend/Tests/Webhook/Fixtures/reception.json b/src/Symfony/Component/Mailer/Bridge/AhaSend/Tests/Webhook/Fixtures/reception.json new file mode 100644 index 0000000000000..6519b5c8f2f87 --- /dev/null +++ b/src/Symfony/Component/Mailer/Bridge/AhaSend/Tests/Webhook/Fixtures/reception.json @@ -0,0 +1,15 @@ +{ + "type": "message.reception", + "timestamp": "2024-10-27T19:37:30.92621021Z", + "data": { + "account_id": "4cdd7bdd-294e-4762-892f-83d40abf5a87", + "event": "on_reception", + "from": "info@example.com", + "recipient": "someone@example.com", + "subject": "Sample email for testing webhooks", + "message_id_header": "message-id-header", + "user_agent": "", + "is_bot": "", + "id": "ahasend-message-id" + } +} diff --git a/src/Symfony/Component/Mailer/Bridge/AhaSend/Tests/Webhook/Fixtures/reception.php b/src/Symfony/Component/Mailer/Bridge/AhaSend/Tests/Webhook/Fixtures/reception.php new file mode 100644 index 0000000000000..03d3072a6cc25 --- /dev/null +++ b/src/Symfony/Component/Mailer/Bridge/AhaSend/Tests/Webhook/Fixtures/reception.php @@ -0,0 +1,9 @@ +setRecipientEmail('someone@example.com'); +$wh->setDate(\DateTimeImmutable::createFromFormat('Y-m-d\TH:i:s.uT', '2024-10-27T19:37:30.926210Z')); + +return $wh; diff --git a/src/Symfony/Component/Mailer/Bridge/AhaSend/Tests/Webhook/Fixtures/reception_headers.txt b/src/Symfony/Component/Mailer/Bridge/AhaSend/Tests/Webhook/Fixtures/reception_headers.txt new file mode 100644 index 0000000000000..b24ad37e17602 --- /dev/null +++ b/src/Symfony/Component/Mailer/Bridge/AhaSend/Tests/Webhook/Fixtures/reception_headers.txt @@ -0,0 +1,3 @@ +webhook-id:0WLfjto3SldBI7vODvV6y9XTAFcNLQeyLzj0ZM2YEDeLWOI43L0ulHgXwtVNu0pG +webhook-timestamp:1730057850 +webhook-signature:v1,/ILaCCEKLOaeH8rL9TT8zkgeoWwMnRy41JdwlVo5ZSk= diff --git a/src/Symfony/Component/Mailer/Bridge/AhaSend/Tests/Webhook/Fixtures/suppressed.json b/src/Symfony/Component/Mailer/Bridge/AhaSend/Tests/Webhook/Fixtures/suppressed.json new file mode 100644 index 0000000000000..1ebdaf6bbabed --- /dev/null +++ b/src/Symfony/Component/Mailer/Bridge/AhaSend/Tests/Webhook/Fixtures/suppressed.json @@ -0,0 +1,15 @@ +{ + "type": "message.suppressed", + "timestamp": "2024-10-27T19:37:30.935569544Z", + "data": { + "account_id": "4cdd7bdd-294e-4762-892f-83d40abf5a87", + "event": "on_suppressed", + "from": "info@example.com", + "recipient": "someone@example.com", + "subject": "Sample email for testing webhooks", + "message_id_header": "message-id-header", + "user_agent": "", + "is_bot": "", + "id": "ahasend-message-id" + } +} diff --git a/src/Symfony/Component/Mailer/Bridge/AhaSend/Tests/Webhook/Fixtures/suppressed.php b/src/Symfony/Component/Mailer/Bridge/AhaSend/Tests/Webhook/Fixtures/suppressed.php new file mode 100644 index 0000000000000..303bc6835113b --- /dev/null +++ b/src/Symfony/Component/Mailer/Bridge/AhaSend/Tests/Webhook/Fixtures/suppressed.php @@ -0,0 +1,9 @@ +setRecipientEmail('someone@example.com'); +$wh->setDate(\DateTimeImmutable::createFromFormat('Y-m-d\TH:i:s.uT', '2024-10-27T19:37:30.935569Z')); + +return $wh; diff --git a/src/Symfony/Component/Mailer/Bridge/AhaSend/Tests/Webhook/Fixtures/suppressed_headers.txt b/src/Symfony/Component/Mailer/Bridge/AhaSend/Tests/Webhook/Fixtures/suppressed_headers.txt new file mode 100644 index 0000000000000..b21c37890da56 --- /dev/null +++ b/src/Symfony/Component/Mailer/Bridge/AhaSend/Tests/Webhook/Fixtures/suppressed_headers.txt @@ -0,0 +1,3 @@ +webhook-id:Gg09J0HB07f8gd7pi6B6JSh0tU6jVrc0j8JpVfXaTOV7w9bb7bcDc0upcqEsSTXd +webhook-timestamp:1730057850 +webhook-signature:v1,SkV4R0DJccOQJ0pzXadnNtIIDzH7rAduPVOaXvVk/Ss= diff --git a/src/Symfony/Component/Mailer/Bridge/AhaSend/Tests/Webhook/Fixtures/transient_error.json b/src/Symfony/Component/Mailer/Bridge/AhaSend/Tests/Webhook/Fixtures/transient_error.json new file mode 100644 index 0000000000000..4639114498f7b --- /dev/null +++ b/src/Symfony/Component/Mailer/Bridge/AhaSend/Tests/Webhook/Fixtures/transient_error.json @@ -0,0 +1,15 @@ +{ + "type": "message.transient_error", + "timestamp": "2024-10-27T19:37:30.929792119Z", + "data": { + "account_id": "4cdd7bdd-294e-4762-892f-83d40abf5a87", + "event": "on_transient_error", + "from": "info@example.com", + "recipient": "someone@example.com", + "subject": "Sample email for testing webhooks", + "message_id_header": "message-id-header", + "user_agent": "", + "is_bot": "", + "id": "ahasend-message-id" + } +} diff --git a/src/Symfony/Component/Mailer/Bridge/AhaSend/Tests/Webhook/Fixtures/transient_error.php b/src/Symfony/Component/Mailer/Bridge/AhaSend/Tests/Webhook/Fixtures/transient_error.php new file mode 100644 index 0000000000000..9cc3b78a3f6f5 --- /dev/null +++ b/src/Symfony/Component/Mailer/Bridge/AhaSend/Tests/Webhook/Fixtures/transient_error.php @@ -0,0 +1,9 @@ +setRecipientEmail('someone@example.com'); +$wh->setDate(\DateTimeImmutable::createFromFormat('Y-m-d\TH:i:s.uT', '2024-10-27T19:37:30.929792Z')); + +return $wh; diff --git a/src/Symfony/Component/Mailer/Bridge/AhaSend/Tests/Webhook/Fixtures/transient_error_headers.txt b/src/Symfony/Component/Mailer/Bridge/AhaSend/Tests/Webhook/Fixtures/transient_error_headers.txt new file mode 100644 index 0000000000000..3e8fed992f2a6 --- /dev/null +++ b/src/Symfony/Component/Mailer/Bridge/AhaSend/Tests/Webhook/Fixtures/transient_error_headers.txt @@ -0,0 +1,3 @@ +webhook-id:2J5f1ZkzTKDOhfoVWkQzUn5cE0Vn0B22fJOnHfPXSmnL5i7lMi0CwE3x5DZQdtAt +webhook-timestamp:1730057850 +webhook-signature:v1,dTzgUl/tlyRltFj3HJ66m8qn4GngYJsafNiEks2hQWk= diff --git a/src/Symfony/Component/Mailer/Bridge/AhaSend/Transport/AhaSendApiTransport.php b/src/Symfony/Component/Mailer/Bridge/AhaSend/Transport/AhaSendApiTransport.php new file mode 100644 index 0000000000000..496557addf9ed --- /dev/null +++ b/src/Symfony/Component/Mailer/Bridge/AhaSend/Transport/AhaSendApiTransport.php @@ -0,0 +1,211 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Mailer\Bridge\AhaSend\Transport; + +use Psr\EventDispatcher\EventDispatcherInterface; +use Psr\Log\LoggerInterface; +use Symfony\Component\Mailer\Bridge\AhaSend\Event\AhaSendDeliveryEvent; +use Symfony\Component\Mailer\Envelope; +use Symfony\Component\Mailer\Exception\HttpTransportException; +use Symfony\Component\Mailer\Header\TagHeader; +use Symfony\Component\Mailer\SentMessage; +use Symfony\Component\Mailer\Transport\AbstractApiTransport; +use Symfony\Component\Mime\Address; +use Symfony\Component\Mime\Email; +use Symfony\Component\Mime\Header\Headers; +use Symfony\Contracts\HttpClient\Exception\DecodingExceptionInterface; +use Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface; +use Symfony\Contracts\HttpClient\HttpClientInterface; +use Symfony\Contracts\HttpClient\ResponseInterface; + +/** + * @author Farhad HedayatifardNote: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.
Alternative Proxies: