From c79a984ee87a588d6ed1baba43c2558cdd677434 Mon Sep 17 00:00:00 2001 From: Kevin Bond Date: Sat, 14 Sep 2024 17:20:04 -0400 Subject: [PATCH] [Mailer] add Mailtrap webhook support --- .../FrameworkExtension.php | 1 + .../Resources/config/mailer_webhook.php | 7 +++ .../RemoteEvent/MailtrapPayloadConverter.php | 62 +++++++++++++++++++ .../Tests/Webhook/Fixtures/batch.json | 34 ++++++++++ .../Mailtrap/Tests/Webhook/Fixtures/batch.php | 19 ++++++ .../Tests/Webhook/Fixtures/bounce.json | 19 ++++++ .../Tests/Webhook/Fixtures/bounce.php | 12 ++++ .../Tests/Webhook/Fixtures/click.json | 19 ++++++ .../Mailtrap/Tests/Webhook/Fixtures/click.php | 11 ++++ .../Tests/Webhook/Fixtures/delivery.json | 16 +++++ .../Tests/Webhook/Fixtures/delivery.php | 12 ++++ .../Mailtrap/Tests/Webhook/Fixtures/open.json | 18 ++++++ .../Mailtrap/Tests/Webhook/Fixtures/open.php | 11 ++++ .../Tests/Webhook/Fixtures/reject.json | 17 +++++ .../Tests/Webhook/Fixtures/reject.php | 12 ++++ .../Tests/Webhook/Fixtures/soft_bounce.json | 19 ++++++ .../Tests/Webhook/Fixtures/soft_bounce.php | 12 ++++ .../Mailtrap/Tests/Webhook/Fixtures/spam.json | 16 +++++ .../Mailtrap/Tests/Webhook/Fixtures/spam.php | 11 ++++ .../Tests/Webhook/Fixtures/suspension.json | 17 +++++ .../Tests/Webhook/Fixtures/suspension.php | 12 ++++ .../Tests/Webhook/Fixtures/unsubscribe.json | 16 +++++ .../Tests/Webhook/Fixtures/unsubscribe.php | 11 ++++ .../Webhook/MailtrapRequestParserTest.php | 25 ++++++++ .../Webhook/MailtrapRequestParser.php | 60 ++++++++++++++++++ .../Mailer/Bridge/Mailtrap/composer.json | 6 +- 26 files changed, 474 insertions(+), 1 deletion(-) create mode 100644 src/Symfony/Component/Mailer/Bridge/Mailtrap/RemoteEvent/MailtrapPayloadConverter.php create mode 100644 src/Symfony/Component/Mailer/Bridge/Mailtrap/Tests/Webhook/Fixtures/batch.json create mode 100644 src/Symfony/Component/Mailer/Bridge/Mailtrap/Tests/Webhook/Fixtures/batch.php create mode 100644 src/Symfony/Component/Mailer/Bridge/Mailtrap/Tests/Webhook/Fixtures/bounce.json create mode 100644 src/Symfony/Component/Mailer/Bridge/Mailtrap/Tests/Webhook/Fixtures/bounce.php create mode 100644 src/Symfony/Component/Mailer/Bridge/Mailtrap/Tests/Webhook/Fixtures/click.json create mode 100644 src/Symfony/Component/Mailer/Bridge/Mailtrap/Tests/Webhook/Fixtures/click.php create mode 100644 src/Symfony/Component/Mailer/Bridge/Mailtrap/Tests/Webhook/Fixtures/delivery.json create mode 100644 src/Symfony/Component/Mailer/Bridge/Mailtrap/Tests/Webhook/Fixtures/delivery.php create mode 100644 src/Symfony/Component/Mailer/Bridge/Mailtrap/Tests/Webhook/Fixtures/open.json create mode 100644 src/Symfony/Component/Mailer/Bridge/Mailtrap/Tests/Webhook/Fixtures/open.php create mode 100644 src/Symfony/Component/Mailer/Bridge/Mailtrap/Tests/Webhook/Fixtures/reject.json create mode 100644 src/Symfony/Component/Mailer/Bridge/Mailtrap/Tests/Webhook/Fixtures/reject.php create mode 100644 src/Symfony/Component/Mailer/Bridge/Mailtrap/Tests/Webhook/Fixtures/soft_bounce.json create mode 100644 src/Symfony/Component/Mailer/Bridge/Mailtrap/Tests/Webhook/Fixtures/soft_bounce.php create mode 100644 src/Symfony/Component/Mailer/Bridge/Mailtrap/Tests/Webhook/Fixtures/spam.json create mode 100644 src/Symfony/Component/Mailer/Bridge/Mailtrap/Tests/Webhook/Fixtures/spam.php create mode 100644 src/Symfony/Component/Mailer/Bridge/Mailtrap/Tests/Webhook/Fixtures/suspension.json create mode 100644 src/Symfony/Component/Mailer/Bridge/Mailtrap/Tests/Webhook/Fixtures/suspension.php create mode 100644 src/Symfony/Component/Mailer/Bridge/Mailtrap/Tests/Webhook/Fixtures/unsubscribe.json create mode 100644 src/Symfony/Component/Mailer/Bridge/Mailtrap/Tests/Webhook/Fixtures/unsubscribe.php create mode 100644 src/Symfony/Component/Mailer/Bridge/Mailtrap/Tests/Webhook/MailtrapRequestParserTest.php create mode 100644 src/Symfony/Component/Mailer/Bridge/Mailtrap/Webhook/MailtrapRequestParser.php diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php index 200310ec9302b..83bb23f2bb09f 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php @@ -2681,6 +2681,7 @@ private function registerMailerConfiguration(array $config, ContainerBuilder $co MailerBridge\Mailjet\Webhook\MailjetRequestParser::class => 'mailer.webhook.request_parser.mailjet', MailerBridge\Mailomat\Webhook\MailomatRequestParser::class => 'mailer.webhook.request_parser.mailomat', MailerBridge\Postmark\Webhook\PostmarkRequestParser::class => 'mailer.webhook.request_parser.postmark', + MailerBridge\Mailtrap\Webhook\MailtrapRequestParser::class => 'mailer.webhook.request_parser.mailtrap', MailerBridge\Resend\Webhook\ResendRequestParser::class => 'mailer.webhook.request_parser.resend', MailerBridge\Sendgrid\Webhook\SendgridRequestParser::class => 'mailer.webhook.request_parser.sendgrid', MailerBridge\Sweego\Webhook\SweegoRequestParser::class => 'mailer.webhook.request_parser.sweego', diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/mailer_webhook.php b/src/Symfony/Bundle/FrameworkBundle/Resources/config/mailer_webhook.php index f0a50800f4366..e6f6c425b0232 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/mailer_webhook.php +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/mailer_webhook.php @@ -21,6 +21,8 @@ use Symfony\Component\Mailer\Bridge\Mailjet\Webhook\MailjetRequestParser; use Symfony\Component\Mailer\Bridge\Mailomat\RemoteEvent\MailomatPayloadConverter; use Symfony\Component\Mailer\Bridge\Mailomat\Webhook\MailomatRequestParser; +use Symfony\Component\Mailer\Bridge\Mailtrap\RemoteEvent\MailtrapPayloadConverter; +use Symfony\Component\Mailer\Bridge\Mailtrap\Webhook\MailtrapRequestParser; use Symfony\Component\Mailer\Bridge\Postmark\RemoteEvent\PostmarkPayloadConverter; use Symfony\Component\Mailer\Bridge\Postmark\Webhook\PostmarkRequestParser; use Symfony\Component\Mailer\Bridge\Resend\RemoteEvent\ResendPayloadConverter; @@ -62,6 +64,11 @@ ->args([service('mailer.payload_converter.postmark')]) ->alias(PostmarkRequestParser::class, 'mailer.webhook.request_parser.postmark') + ->set('mailer.payload_converter.mailtrap', MailtrapPayloadConverter::class) + ->set('mailer.webhook.request_parser.mailtrap', MailtrapRequestParser::class) + ->args([service('mailer.payload_converter.mailtrap')]) + ->alias(MailtrapRequestParser::class, 'mailer.webhook.request_parser.mailtrap') + ->set('mailer.payload_converter.resend', ResendPayloadConverter::class) ->set('mailer.webhook.request_parser.resend', ResendRequestParser::class) ->args([service('mailer.payload_converter.resend')]) diff --git a/src/Symfony/Component/Mailer/Bridge/Mailtrap/RemoteEvent/MailtrapPayloadConverter.php b/src/Symfony/Component/Mailer/Bridge/Mailtrap/RemoteEvent/MailtrapPayloadConverter.php new file mode 100644 index 0000000000000..66da5248c5e53 --- /dev/null +++ b/src/Symfony/Component/Mailer/Bridge/Mailtrap/RemoteEvent/MailtrapPayloadConverter.php @@ -0,0 +1,62 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Mailer\Bridge\Mailtrap\RemoteEvent; + +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; +use Symfony\Component\RemoteEvent\RemoteEvent; + +/** + * @author Kevin Bond + */ +final class MailtrapPayloadConverter implements PayloadConverterInterface +{ + public function convert(array $payload): RemoteEvent + { + $type = match ($payload['event']) { + 'delivery' => MailerDeliveryEvent::DELIVERED, + 'open' => MailerEngagementEvent::OPEN, + 'click' => MailerEngagementEvent::CLICK, + 'unsubscribe' => MailerEngagementEvent::UNSUBSCRIBE, + 'spam' => MailerEngagementEvent::SPAM, + 'soft bounce', 'bounce' => MailerDeliveryEvent::BOUNCE, + 'suspension', 'reject' => MailerDeliveryEvent::DROPPED, + default => throw new ParseException(\sprintf('Unsupported event "%s".', $payload['event'])), + }; + + if (\in_array($type, [MailerDeliveryEvent::DELIVERED, MailerDeliveryEvent::BOUNCE, MailerDeliveryEvent::DROPPED], true)) { + $event = new MailerDeliveryEvent($type, $payload['message_id'], $payload); + $event->setReason($payload['reason'] ?? $payload['response'] ?? ''); + } else { + $event = new MailerEngagementEvent($type, $payload['message_id'], $payload); + } + + if (!$date = \DateTimeImmutable::createFromFormat('U', $payload['timestamp'])) { + throw new ParseException(\sprintf('Invalid date "%s".', $payload['timestamp'])); + } + + $event->setDate($date); + $event->setRecipientEmail($payload['email']); + + if (isset($payload['category'])) { + $event->setTags([$payload['category']]); + } + + if (isset($payload['custom_variables'])) { + $event->setMetadata($payload['custom_variables']); + } + + return $event; + } +} diff --git a/src/Symfony/Component/Mailer/Bridge/Mailtrap/Tests/Webhook/Fixtures/batch.json b/src/Symfony/Component/Mailer/Bridge/Mailtrap/Tests/Webhook/Fixtures/batch.json new file mode 100644 index 0000000000000..2119225933b94 --- /dev/null +++ b/src/Symfony/Component/Mailer/Bridge/Mailtrap/Tests/Webhook/Fixtures/batch.json @@ -0,0 +1,34 @@ +{ + "events": [ + { + "event": "bounce", + "category": "Password reset", + "custom_variables": { + "variable_a": "value", + "variable_b": "value2" + }, + "message_id": "00000000-0000-0000-0000-000000000001", + "email": "receiver@example.com", + "timestamp": 1726358034, + "event_id": "bede7236-2284-43d6-a953-1fdcafd0fdbc", + "response": "[CS01] Message rejected due to local policy", + "response_code": 555, + "bounce_category": "spam" + }, + { + "event": "click", + "category": "Password reset", + "custom_variables": { + "variable_a": "value", + "variable_b": "value2" + }, + "message_id": "00000000-0000-0000-0000-000000000002", + "email": "receiver@example.com", + "timestamp": 1726358034, + "event_id": "bede7236-2284-43d6-a953-1fdcafd0fdbc", + "ip": "142.86.27.2", + "user_agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.0.0 Safari/537.36", + "url": "https://mailtrap.io/email-api" + } + ] +} diff --git a/src/Symfony/Component/Mailer/Bridge/Mailtrap/Tests/Webhook/Fixtures/batch.php b/src/Symfony/Component/Mailer/Bridge/Mailtrap/Tests/Webhook/Fixtures/batch.php new file mode 100644 index 0000000000000..1c85133a0fcf9 --- /dev/null +++ b/src/Symfony/Component/Mailer/Bridge/Mailtrap/Tests/Webhook/Fixtures/batch.php @@ -0,0 +1,19 @@ +setRecipientEmail('receiver@example.com'); +$wh1->setTags(['Password reset']); +$wh1->setMetadata(['variable_a' => 'value', 'variable_b' => 'value2']); +$wh1->setReason('[CS01] Message rejected due to local policy'); +$wh1->setDate(\DateTimeImmutable::createFromFormat('U', 1726358034)); + +$wh2 = new MailerEngagementEvent(MailerEngagementEvent::CLICK, '00000000-0000-0000-0000-000000000002', json_decode(file_get_contents(str_replace('.php', '.json', __FILE__)), true)['events'][1]); +$wh2->setRecipientEmail('receiver@example.com'); +$wh2->setTags(['Password reset']); +$wh2->setMetadata(['variable_a' => 'value', 'variable_b' => 'value2']); +$wh2->setDate(\DateTimeImmutable::createFromFormat('U', 1726358034)); + +return [$wh1, $wh2]; diff --git a/src/Symfony/Component/Mailer/Bridge/Mailtrap/Tests/Webhook/Fixtures/bounce.json b/src/Symfony/Component/Mailer/Bridge/Mailtrap/Tests/Webhook/Fixtures/bounce.json new file mode 100644 index 0000000000000..031071ee6868a --- /dev/null +++ b/src/Symfony/Component/Mailer/Bridge/Mailtrap/Tests/Webhook/Fixtures/bounce.json @@ -0,0 +1,19 @@ +{ + "events": [ + { + "event": "bounce", + "category": "Password reset", + "custom_variables": { + "variable_a": "value", + "variable_b": "value2" + }, + "message_id": "00000000-0000-0000-0000-000000000000", + "email": "receiver@example.com", + "timestamp": 1726358034, + "event_id": "bede7236-2284-43d6-a953-1fdcafd0fdbc", + "response": "[CS01] Message rejected due to local policy", + "response_code": 555, + "bounce_category": "spam" + } + ] +} diff --git a/src/Symfony/Component/Mailer/Bridge/Mailtrap/Tests/Webhook/Fixtures/bounce.php b/src/Symfony/Component/Mailer/Bridge/Mailtrap/Tests/Webhook/Fixtures/bounce.php new file mode 100644 index 0000000000000..ef241e5a69975 --- /dev/null +++ b/src/Symfony/Component/Mailer/Bridge/Mailtrap/Tests/Webhook/Fixtures/bounce.php @@ -0,0 +1,12 @@ +setRecipientEmail('receiver@example.com'); +$wh->setTags(['Password reset']); +$wh->setMetadata(['variable_a' => 'value', 'variable_b' => 'value2']); +$wh->setReason('[CS01] Message rejected due to local policy'); +$wh->setDate(\DateTimeImmutable::createFromFormat('U', 1726358034)); + +return [$wh]; diff --git a/src/Symfony/Component/Mailer/Bridge/Mailtrap/Tests/Webhook/Fixtures/click.json b/src/Symfony/Component/Mailer/Bridge/Mailtrap/Tests/Webhook/Fixtures/click.json new file mode 100644 index 0000000000000..417744af55a43 --- /dev/null +++ b/src/Symfony/Component/Mailer/Bridge/Mailtrap/Tests/Webhook/Fixtures/click.json @@ -0,0 +1,19 @@ +{ + "events": [ + { + "event": "click", + "category": "Password reset", + "custom_variables": { + "variable_a": "value", + "variable_b": "value2" + }, + "message_id": "00000000-0000-0000-0000-000000000000", + "email": "receiver@example.com", + "timestamp": 1726358034, + "event_id": "bede7236-2284-43d6-a953-1fdcafd0fdbc", + "ip": "142.86.27.2", + "user_agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.0.0 Safari/537.36", + "url": "https://mailtrap.io/email-api" + } + ] +} diff --git a/src/Symfony/Component/Mailer/Bridge/Mailtrap/Tests/Webhook/Fixtures/click.php b/src/Symfony/Component/Mailer/Bridge/Mailtrap/Tests/Webhook/Fixtures/click.php new file mode 100644 index 0000000000000..659304e8e965d --- /dev/null +++ b/src/Symfony/Component/Mailer/Bridge/Mailtrap/Tests/Webhook/Fixtures/click.php @@ -0,0 +1,11 @@ +setRecipientEmail('receiver@example.com'); +$wh->setTags(['Password reset']); +$wh->setMetadata(['variable_a' => 'value', 'variable_b' => 'value2']); +$wh->setDate(\DateTimeImmutable::createFromFormat('U', 1726358034)); + +return [$wh]; diff --git a/src/Symfony/Component/Mailer/Bridge/Mailtrap/Tests/Webhook/Fixtures/delivery.json b/src/Symfony/Component/Mailer/Bridge/Mailtrap/Tests/Webhook/Fixtures/delivery.json new file mode 100644 index 0000000000000..a3d96eda9a85e --- /dev/null +++ b/src/Symfony/Component/Mailer/Bridge/Mailtrap/Tests/Webhook/Fixtures/delivery.json @@ -0,0 +1,16 @@ +{ + "events": [ + { + "event": "delivery", + "category": "Password reset", + "custom_variables": { + "variable_a": "value", + "variable_b": "value2" + }, + "message_id": "00000000-0000-0000-0000-000000000000", + "email": "receiver@example.com", + "timestamp": 1726358034, + "event_id": "bede7236-2284-43d6-a953-1fdcafd0fdbc" + } + ] +} diff --git a/src/Symfony/Component/Mailer/Bridge/Mailtrap/Tests/Webhook/Fixtures/delivery.php b/src/Symfony/Component/Mailer/Bridge/Mailtrap/Tests/Webhook/Fixtures/delivery.php new file mode 100644 index 0000000000000..decd08e47d364 --- /dev/null +++ b/src/Symfony/Component/Mailer/Bridge/Mailtrap/Tests/Webhook/Fixtures/delivery.php @@ -0,0 +1,12 @@ +setRecipientEmail('receiver@example.com'); +$wh->setTags(['Password reset']); +$wh->setMetadata(['variable_a' => 'value', 'variable_b' => 'value2']); +$wh->setReason(''); +$wh->setDate(\DateTimeImmutable::createFromFormat('U', 1726358034)); + +return [$wh]; diff --git a/src/Symfony/Component/Mailer/Bridge/Mailtrap/Tests/Webhook/Fixtures/open.json b/src/Symfony/Component/Mailer/Bridge/Mailtrap/Tests/Webhook/Fixtures/open.json new file mode 100644 index 0000000000000..44b7d3be47a05 --- /dev/null +++ b/src/Symfony/Component/Mailer/Bridge/Mailtrap/Tests/Webhook/Fixtures/open.json @@ -0,0 +1,18 @@ +{ + "events": [ + { + "event": "open", + "category": "Password reset", + "custom_variables": { + "variable_a": "value", + "variable_b": "value2" + }, + "message_id": "00000000-0000-0000-0000-000000000000", + "email": "receiver@example.com", + "timestamp": 1726358034, + "event_id": "bede7236-2284-43d6-a953-1fdcafd0fdbc", + "ip": "127.138.158.185", + "user_agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.0.0 Safari/537.36" + } + ] +} diff --git a/src/Symfony/Component/Mailer/Bridge/Mailtrap/Tests/Webhook/Fixtures/open.php b/src/Symfony/Component/Mailer/Bridge/Mailtrap/Tests/Webhook/Fixtures/open.php new file mode 100644 index 0000000000000..061a9b7ea4923 --- /dev/null +++ b/src/Symfony/Component/Mailer/Bridge/Mailtrap/Tests/Webhook/Fixtures/open.php @@ -0,0 +1,11 @@ +setRecipientEmail('receiver@example.com'); +$wh->setTags(['Password reset']); +$wh->setMetadata(['variable_a' => 'value', 'variable_b' => 'value2']); +$wh->setDate(\DateTimeImmutable::createFromFormat('U', 1726358034)); + +return [$wh]; diff --git a/src/Symfony/Component/Mailer/Bridge/Mailtrap/Tests/Webhook/Fixtures/reject.json b/src/Symfony/Component/Mailer/Bridge/Mailtrap/Tests/Webhook/Fixtures/reject.json new file mode 100644 index 0000000000000..80ba16286a97f --- /dev/null +++ b/src/Symfony/Component/Mailer/Bridge/Mailtrap/Tests/Webhook/Fixtures/reject.json @@ -0,0 +1,17 @@ +{ + "events": [ + { + "event": "reject", + "category": "Password reset", + "custom_variables": { + "variable_a": "value", + "variable_b": "value2" + }, + "message_id": "00000000-0000-0000-0000-000000000000", + "email": "receiver@example.com", + "timestamp": 1726358034, + "event_id": "bede7236-2284-43d6-a953-1fdcafd0fdbc", + "reason": "Recipient in suppression list. Reason: unsubscription" + } + ] +} diff --git a/src/Symfony/Component/Mailer/Bridge/Mailtrap/Tests/Webhook/Fixtures/reject.php b/src/Symfony/Component/Mailer/Bridge/Mailtrap/Tests/Webhook/Fixtures/reject.php new file mode 100644 index 0000000000000..5ee7390adc9a2 --- /dev/null +++ b/src/Symfony/Component/Mailer/Bridge/Mailtrap/Tests/Webhook/Fixtures/reject.php @@ -0,0 +1,12 @@ +setRecipientEmail('receiver@example.com'); +$wh->setTags(['Password reset']); +$wh->setMetadata(['variable_a' => 'value', 'variable_b' => 'value2']); +$wh->setReason('Recipient in suppression list. Reason: unsubscription'); +$wh->setDate(\DateTimeImmutable::createFromFormat('U', 1726358034)); + +return [$wh]; diff --git a/src/Symfony/Component/Mailer/Bridge/Mailtrap/Tests/Webhook/Fixtures/soft_bounce.json b/src/Symfony/Component/Mailer/Bridge/Mailtrap/Tests/Webhook/Fixtures/soft_bounce.json new file mode 100644 index 0000000000000..5b39c44cbf2ee --- /dev/null +++ b/src/Symfony/Component/Mailer/Bridge/Mailtrap/Tests/Webhook/Fixtures/soft_bounce.json @@ -0,0 +1,19 @@ +{ + "events": [ + { + "event": "soft bounce", + "category": "Password reset", + "custom_variables": { + "variable_a": "value", + "variable_b": "value2" + }, + "message_id": "00000000-0000-0000-0000-000000000000", + "email": "receiver@example.com", + "timestamp": 1726358034, + "event_id": "bede7236-2284-43d6-a953-1fdcafd0fdbc", + "response": "4.7.1 Temporary error, please retry", + "response_code": 451, + "bounce_category": "greylisting" + } + ] +} diff --git a/src/Symfony/Component/Mailer/Bridge/Mailtrap/Tests/Webhook/Fixtures/soft_bounce.php b/src/Symfony/Component/Mailer/Bridge/Mailtrap/Tests/Webhook/Fixtures/soft_bounce.php new file mode 100644 index 0000000000000..c6490ea08c200 --- /dev/null +++ b/src/Symfony/Component/Mailer/Bridge/Mailtrap/Tests/Webhook/Fixtures/soft_bounce.php @@ -0,0 +1,12 @@ +setRecipientEmail('receiver@example.com'); +$wh->setTags(['Password reset']); +$wh->setMetadata(['variable_a' => 'value', 'variable_b' => 'value2']); +$wh->setReason('4.7.1 Temporary error, please retry'); +$wh->setDate(\DateTimeImmutable::createFromFormat('U', 1726358034)); + +return [$wh]; diff --git a/src/Symfony/Component/Mailer/Bridge/Mailtrap/Tests/Webhook/Fixtures/spam.json b/src/Symfony/Component/Mailer/Bridge/Mailtrap/Tests/Webhook/Fixtures/spam.json new file mode 100644 index 0000000000000..bfb6bdc59fc0e --- /dev/null +++ b/src/Symfony/Component/Mailer/Bridge/Mailtrap/Tests/Webhook/Fixtures/spam.json @@ -0,0 +1,16 @@ +{ + "events": [ + { + "event": "spam", + "category": "Password reset", + "custom_variables": { + "variable_a": "value", + "variable_b": "value2" + }, + "message_id": "00000000-0000-0000-0000-000000000000", + "email": "receiver@example.com", + "timestamp": 1726358034, + "event_id": "bede7236-2284-43d6-a953-1fdcafd0fdbc" + } + ] +} diff --git a/src/Symfony/Component/Mailer/Bridge/Mailtrap/Tests/Webhook/Fixtures/spam.php b/src/Symfony/Component/Mailer/Bridge/Mailtrap/Tests/Webhook/Fixtures/spam.php new file mode 100644 index 0000000000000..e0efcff814a67 --- /dev/null +++ b/src/Symfony/Component/Mailer/Bridge/Mailtrap/Tests/Webhook/Fixtures/spam.php @@ -0,0 +1,11 @@ +setRecipientEmail('receiver@example.com'); +$wh->setTags(['Password reset']); +$wh->setMetadata(['variable_a' => 'value', 'variable_b' => 'value2']); +$wh->setDate(\DateTimeImmutable::createFromFormat('U', 1726358034)); + +return [$wh]; diff --git a/src/Symfony/Component/Mailer/Bridge/Mailtrap/Tests/Webhook/Fixtures/suspension.json b/src/Symfony/Component/Mailer/Bridge/Mailtrap/Tests/Webhook/Fixtures/suspension.json new file mode 100644 index 0000000000000..e212441f281bd --- /dev/null +++ b/src/Symfony/Component/Mailer/Bridge/Mailtrap/Tests/Webhook/Fixtures/suspension.json @@ -0,0 +1,17 @@ +{ + "events": [ + { + "event": "suspension", + "category": "Password reset", + "custom_variables": { + "variable_a": "value", + "variable_b": "value2" + }, + "message_id": "00000000-0000-0000-0000-000000000000", + "email": "receiver@example.com", + "timestamp": 1726358034, + "event_id": "bede7236-2284-43d6-a953-1fdcafd0fdbc", + "reason": "Your account has reached its daily sending limit." + } + ] +} diff --git a/src/Symfony/Component/Mailer/Bridge/Mailtrap/Tests/Webhook/Fixtures/suspension.php b/src/Symfony/Component/Mailer/Bridge/Mailtrap/Tests/Webhook/Fixtures/suspension.php new file mode 100644 index 0000000000000..c0b655ddd9d15 --- /dev/null +++ b/src/Symfony/Component/Mailer/Bridge/Mailtrap/Tests/Webhook/Fixtures/suspension.php @@ -0,0 +1,12 @@ +setRecipientEmail('receiver@example.com'); +$wh->setTags(['Password reset']); +$wh->setMetadata(['variable_a' => 'value', 'variable_b' => 'value2']); +$wh->setReason('Your account has reached its daily sending limit.'); +$wh->setDate(\DateTimeImmutable::createFromFormat('U', 1726358034)); + +return [$wh]; diff --git a/src/Symfony/Component/Mailer/Bridge/Mailtrap/Tests/Webhook/Fixtures/unsubscribe.json b/src/Symfony/Component/Mailer/Bridge/Mailtrap/Tests/Webhook/Fixtures/unsubscribe.json new file mode 100644 index 0000000000000..5947bc4437dd9 --- /dev/null +++ b/src/Symfony/Component/Mailer/Bridge/Mailtrap/Tests/Webhook/Fixtures/unsubscribe.json @@ -0,0 +1,16 @@ +{ + "events": [ + { + "event": "unsubscribe", + "category": "Password reset", + "custom_variables": { + "variable_a": "value", + "variable_b": "value2" + }, + "message_id": "00000000-0000-0000-0000-000000000000", + "email": "receiver@example.com", + "timestamp": 1726358034, + "event_id": "bede7236-2284-43d6-a953-1fdcafd0fdbc" + } + ] +} diff --git a/src/Symfony/Component/Mailer/Bridge/Mailtrap/Tests/Webhook/Fixtures/unsubscribe.php b/src/Symfony/Component/Mailer/Bridge/Mailtrap/Tests/Webhook/Fixtures/unsubscribe.php new file mode 100644 index 0000000000000..34f0464b47988 --- /dev/null +++ b/src/Symfony/Component/Mailer/Bridge/Mailtrap/Tests/Webhook/Fixtures/unsubscribe.php @@ -0,0 +1,11 @@ +setRecipientEmail('receiver@example.com'); +$wh->setTags(['Password reset']); +$wh->setMetadata(['variable_a' => 'value', 'variable_b' => 'value2']); +$wh->setDate(\DateTimeImmutable::createFromFormat('U', 1726358034)); + +return [$wh]; diff --git a/src/Symfony/Component/Mailer/Bridge/Mailtrap/Tests/Webhook/MailtrapRequestParserTest.php b/src/Symfony/Component/Mailer/Bridge/Mailtrap/Tests/Webhook/MailtrapRequestParserTest.php new file mode 100644 index 0000000000000..8af3780f17020 --- /dev/null +++ b/src/Symfony/Component/Mailer/Bridge/Mailtrap/Tests/Webhook/MailtrapRequestParserTest.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\Mailtrap\Tests\Webhook; + +use Symfony\Component\Mailer\Bridge\Mailtrap\RemoteEvent\MailtrapPayloadConverter; +use Symfony\Component\Mailer\Bridge\Mailtrap\Webhook\MailtrapRequestParser; +use Symfony\Component\Webhook\Client\RequestParserInterface; +use Symfony\Component\Webhook\Test\AbstractRequestParserTestCase; + +class MailtrapRequestParserTest extends AbstractRequestParserTestCase +{ + protected function createRequestParser(): RequestParserInterface + { + return new MailtrapRequestParser(new MailtrapPayloadConverter()); + } +} diff --git a/src/Symfony/Component/Mailer/Bridge/Mailtrap/Webhook/MailtrapRequestParser.php b/src/Symfony/Component/Mailer/Bridge/Mailtrap/Webhook/MailtrapRequestParser.php new file mode 100644 index 0000000000000..335e2624a1b9b --- /dev/null +++ b/src/Symfony/Component/Mailer/Bridge/Mailtrap/Webhook/MailtrapRequestParser.php @@ -0,0 +1,60 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Mailer\Bridge\Mailtrap\Webhook; + +use Symfony\Component\HttpFoundation\ChainRequestMatcher; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\RequestMatcher\IsJsonRequestMatcher; +use Symfony\Component\HttpFoundation\RequestMatcher\MethodRequestMatcher; +use Symfony\Component\HttpFoundation\RequestMatcherInterface; +use Symfony\Component\Mailer\Bridge\Mailtrap\RemoteEvent\MailtrapPayloadConverter; +use Symfony\Component\RemoteEvent\Exception\ParseException; +use Symfony\Component\RemoteEvent\RemoteEvent; +use Symfony\Component\Webhook\Client\AbstractRequestParser; +use Symfony\Component\Webhook\Exception\RejectWebhookException; + +/** + * @author Kevin Bond + */ +final class MailtrapRequestParser extends AbstractRequestParser +{ + public function __construct( + private readonly MailtrapPayloadConverter $converter, + ) { + } + + protected function getRequestMatcher(): RequestMatcherInterface + { + return new ChainRequestMatcher([ + new MethodRequestMatcher('POST'), + new IsJsonRequestMatcher(), + ]); + } + + protected function doParse(Request $request, #[\SensitiveParameter] string $secret): RemoteEvent|array|null + { + $payload = $request->toArray(); + + if ( + !isset($payload['events'][0]['event']) + || !isset($payload['events'][0]['message_id']) + ) { + throw new RejectWebhookException(406, 'Payload is malformed.'); + } + + try { + return array_map($this->converter->convert(...), $payload['events']); + } catch (ParseException $e) { + throw new RejectWebhookException(406, $e->getMessage(), $e); + } + } +} diff --git a/src/Symfony/Component/Mailer/Bridge/Mailtrap/composer.json b/src/Symfony/Component/Mailer/Bridge/Mailtrap/composer.json index a1558309baef9..7d448e7c40768 100644 --- a/src/Symfony/Component/Mailer/Bridge/Mailtrap/composer.json +++ b/src/Symfony/Component/Mailer/Bridge/Mailtrap/composer.json @@ -21,7 +21,11 @@ "symfony/mailer": "^7.2" }, "require-dev": { - "symfony/http-client": "^6.4|^7.0" + "symfony/http-client": "^6.4|^7.0", + "symfony/webhook": "^7.2" + }, + "conflict": { + "symfony/webhook": "<7.2" }, "autoload": { "psr-4": { "Symfony\\Component\\Mailer\\Bridge\\Mailtrap\\": "" },