diff --git a/src/Symfony/Component/Mailer/Bridge/Brevo/CHANGELOG.md b/src/Symfony/Component/Mailer/Bridge/Brevo/CHANGELOG.md index e37a2af035ffc..1db703587e1f7 100644 --- a/src/Symfony/Component/Mailer/Bridge/Brevo/CHANGELOG.md +++ b/src/Symfony/Component/Mailer/Bridge/Brevo/CHANGELOG.md @@ -4,4 +4,5 @@ CHANGELOG 6.4 --- +* Add support for `RemoteEvent` and `Webhook` * Add the bridge as a replacement of the deprecated Sendinblue one diff --git a/src/Symfony/Component/Mailer/Bridge/Brevo/RemoteEvent/BrevoPayloadConverter.php b/src/Symfony/Component/Mailer/Bridge/Brevo/RemoteEvent/BrevoPayloadConverter.php new file mode 100644 index 0000000000000..8d556b8f37c9a --- /dev/null +++ b/src/Symfony/Component/Mailer/Bridge/Brevo/RemoteEvent/BrevoPayloadConverter.php @@ -0,0 +1,67 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Mailer\Bridge\Brevo\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 BrevoPayloadConverter implements PayloadConverterInterface +{ + public function convert(array $payload): AbstractMailerEvent + { + if (\in_array($payload['event'], ['request', 'deferred', 'delivered', 'soft_bounce', 'hard_bounce', 'invalid_email', 'blocked', 'error'], true)) { + $name = match ($payload['event']) { + 'request' => MailerDeliveryEvent::RECEIVED, + 'deferred' => MailerDeliveryEvent::DEFERRED, + 'delivered' => MailerDeliveryEvent::DELIVERED, + 'soft_bounce' => MailerDeliveryEvent::BOUNCE, + 'hard_bounce' => MailerDeliveryEvent::BOUNCE, + 'invalid_email' => MailerDeliveryEvent::DROPPED, + 'blocked' => MailerDeliveryEvent::DROPPED, + 'error' => MailerDeliveryEvent::DROPPED, + }; + + $event = new MailerDeliveryEvent($name, $payload['message-id'], $payload); + } else { + $name = match ($payload['event']) { + 'click' => MailerEngagementEvent::CLICK, + 'unsubscribed' => MailerEngagementEvent::UNSUBSCRIBE, + 'unique_opened' => MailerEngagementEvent::OPEN, + 'opened' => MailerEngagementEvent::OPEN, + 'proxy_open' => MailerEngagementEvent::OPEN, + 'complaint' => MailerEngagementEvent::SPAM, + default => throw new ParseException(sprintf('Unsupported event "%s".', $payload['event'])), + }; + $event = new MailerEngagementEvent($name, $payload['message-id'], $payload); + } + + if (!$date = \DateTimeImmutable::createFromFormat('U', $payload['ts_event'])) { + throw new ParseException(sprintf('Invalid date "%s".', $payload['ts_event'])); + } + + if ( + \in_array($payload['event'], ['deferred', 'soft_bounce', 'hard_bounce', 'invalid_email', 'blocked', 'error'], true) + && isset($payload['reason']) + ) { + $event->setReason($payload['reason']); + } + + $event->setDate($date); + $event->setRecipientEmail($payload['email']); + $event->setTags($payload['tags']); + + return $event; + } +} diff --git a/src/Symfony/Component/Mailer/Bridge/Brevo/Tests/Webhook/BrevoRequestParserTest.php b/src/Symfony/Component/Mailer/Bridge/Brevo/Tests/Webhook/BrevoRequestParserTest.php new file mode 100644 index 0000000000000..ca6ec013a800b --- /dev/null +++ b/src/Symfony/Component/Mailer/Bridge/Brevo/Tests/Webhook/BrevoRequestParserTest.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\Brevo\Tests\Webhook; + +use Symfony\Component\Mailer\Bridge\Brevo\RemoteEvent\BrevoPayloadConverter; +use Symfony\Component\Mailer\Bridge\Brevo\Webhook\BrevoRequestParser; +use Symfony\Component\Webhook\Client\RequestParserInterface; +use Symfony\Component\Webhook\Test\AbstractRequestParserTestCase; + +class BrevoRequestParserTest extends AbstractRequestParserTestCase +{ + protected function createRequestParser(): RequestParserInterface + { + return new BrevoRequestParser(new BrevoPayloadConverter()); + } +} diff --git a/src/Symfony/Component/Mailer/Bridge/Brevo/Tests/Webhook/Fixtures/blocked.json b/src/Symfony/Component/Mailer/Bridge/Brevo/Tests/Webhook/Fixtures/blocked.json new file mode 100644 index 0000000000000..7345b9ccc5b26 --- /dev/null +++ b/src/Symfony/Component/Mailer/Bridge/Brevo/Tests/Webhook/Fixtures/blocked.json @@ -0,0 +1,17 @@ +{ + "id": 814119, + "email": "example@gmail.com", + "message-id": "<202305311400.29297141656@smtp-relay.mailin.fr>", + "date": "2023-05-31 16:00:08", + "tags": [ + "tag1", + "tag2" + ], + "tag": "[\"tag1\",\"tag2\"]", + "event": "blocked", + "subject": "Subject Line", + "ts_event": 1685541608, + "ts": 1685541608, + "reason": "blocked : due to unsubscribed user", + "ts_epoch": 1685541608993 +} diff --git a/src/Symfony/Component/Mailer/Bridge/Brevo/Tests/Webhook/Fixtures/blocked.php b/src/Symfony/Component/Mailer/Bridge/Brevo/Tests/Webhook/Fixtures/blocked.php new file mode 100644 index 0000000000000..0a7255e8f3e43 --- /dev/null +++ b/src/Symfony/Component/Mailer/Bridge/Brevo/Tests/Webhook/Fixtures/blocked.php @@ -0,0 +1,11 @@ +', json_decode(file_get_contents(str_replace('.php', '.json', __FILE__)), true, flags: JSON_THROW_ON_ERROR)); +$wh->setRecipientEmail('example@gmail.com'); +$wh->setTags(['tag1', 'tag2']); +$wh->setDate(\DateTimeImmutable::createFromFormat('U', 1685541608)); +$wh->setReason('blocked : due to unsubscribed user'); + +return $wh; diff --git a/src/Symfony/Component/Mailer/Bridge/Brevo/Tests/Webhook/Fixtures/click.json b/src/Symfony/Component/Mailer/Bridge/Brevo/Tests/Webhook/Fixtures/click.json new file mode 100644 index 0000000000000..d6f62f4e710eb --- /dev/null +++ b/src/Symfony/Component/Mailer/Bridge/Brevo/Tests/Webhook/Fixtures/click.json @@ -0,0 +1,18 @@ +{ + "id": 814119, + "email": "example@gmail.com", + "message-id": "<202305311408.17112967225@smtp-relay.mailin.fr>", + "date": "2023-05-31 16:08:28", + "tags": [ + "welcome", + "tag2" + ], + "tag": "[\"welcome\",\"tag2\"]", + "event": "click", + "subject": "Subject Line", + "sending_ip": "127.0.0.1", + "ts": 1685542108, + "ts_epoch": 1685542108462, + "ts_event": 1685542108, + "link": "https://www.google.com" +} diff --git a/src/Symfony/Component/Mailer/Bridge/Brevo/Tests/Webhook/Fixtures/click.php b/src/Symfony/Component/Mailer/Bridge/Brevo/Tests/Webhook/Fixtures/click.php new file mode 100644 index 0000000000000..acd62b0726d0c --- /dev/null +++ b/src/Symfony/Component/Mailer/Bridge/Brevo/Tests/Webhook/Fixtures/click.php @@ -0,0 +1,10 @@ +', json_decode(file_get_contents(str_replace('.php', '.json', __FILE__)), true, flags: JSON_THROW_ON_ERROR)); +$wh->setRecipientEmail('example@gmail.com'); +$wh->setTags(['welcome', 'tag2']); +$wh->setDate(\DateTimeImmutable::createFromFormat('U', 1685542108)); + +return $wh; diff --git a/src/Symfony/Component/Mailer/Bridge/Brevo/Tests/Webhook/Fixtures/complaint.json b/src/Symfony/Component/Mailer/Bridge/Brevo/Tests/Webhook/Fixtures/complaint.json new file mode 100644 index 0000000000000..c91a73273bb6b --- /dev/null +++ b/src/Symfony/Component/Mailer/Bridge/Brevo/Tests/Webhook/Fixtures/complaint.json @@ -0,0 +1,11 @@ +{ + "event": "complaint", + "email": "example@gmail.com", + "id": 814119, + "date": "2020-10-09 00:00:00", + "ts": 1604933619, + "message-id": "<201798300811.5787683@smtp-relay.mailin.fr>", + "ts_event": 1604933654, + "X-Mailin-custom": "some_custom_header", + "tags": ["transac_messages"] +} diff --git a/src/Symfony/Component/Mailer/Bridge/Brevo/Tests/Webhook/Fixtures/complaint.php b/src/Symfony/Component/Mailer/Bridge/Brevo/Tests/Webhook/Fixtures/complaint.php new file mode 100644 index 0000000000000..7530160979788 --- /dev/null +++ b/src/Symfony/Component/Mailer/Bridge/Brevo/Tests/Webhook/Fixtures/complaint.php @@ -0,0 +1,10 @@ +', json_decode(file_get_contents(str_replace('.php', '.json', __FILE__)), true, flags: JSON_THROW_ON_ERROR)); +$wh->setRecipientEmail('example@gmail.com'); +$wh->setTags(['transac_messages']); +$wh->setDate(\DateTimeImmutable::createFromFormat('U', 1604933654)); + +return $wh; diff --git a/src/Symfony/Component/Mailer/Bridge/Brevo/Tests/Webhook/Fixtures/defered.json b/src/Symfony/Component/Mailer/Bridge/Brevo/Tests/Webhook/Fixtures/defered.json new file mode 100644 index 0000000000000..0ecf0d8c2faaf --- /dev/null +++ b/src/Symfony/Component/Mailer/Bridge/Brevo/Tests/Webhook/Fixtures/defered.json @@ -0,0 +1,16 @@ +{ + "event":"deferred", + "email": "example@domain.com", + "id": 814119, + "date": "2020-10-09 00:00:00", + "ts": 1604933619, + "message-id": "<202305311400.29297141656@smtp-relay.mailin.fr>", + "ts_event": 1604933654, + "subject": "My first Transactional", + "X-Mailin-custom": "some_custom_header", + "sending_ip": "127.0.0.1", + "ts_epoch": 1604933654, + "template_id": 22, + "tags": ["transac_messages"], + "reason": "spam" +} diff --git a/src/Symfony/Component/Mailer/Bridge/Brevo/Tests/Webhook/Fixtures/defered.php b/src/Symfony/Component/Mailer/Bridge/Brevo/Tests/Webhook/Fixtures/defered.php new file mode 100644 index 0000000000000..f6cf4c1bb73a7 --- /dev/null +++ b/src/Symfony/Component/Mailer/Bridge/Brevo/Tests/Webhook/Fixtures/defered.php @@ -0,0 +1,11 @@ +', json_decode(file_get_contents(str_replace('.php', '.json', __FILE__)), true, flags: JSON_THROW_ON_ERROR)); +$wh->setRecipientEmail('example@domain.com'); +$wh->setTags(['transac_messages']); +$wh->setDate(\DateTimeImmutable::createFromFormat('U', 1604933654)); +$wh->setReason('spam'); + +return $wh; diff --git a/src/Symfony/Component/Mailer/Bridge/Brevo/Tests/Webhook/Fixtures/delivered.json b/src/Symfony/Component/Mailer/Bridge/Brevo/Tests/Webhook/Fixtures/delivered.json new file mode 100644 index 0000000000000..6c7fab012453e --- /dev/null +++ b/src/Symfony/Component/Mailer/Bridge/Brevo/Tests/Webhook/Fixtures/delivered.json @@ -0,0 +1,18 @@ +{ + "id": 814119, + "email": "example@gmail.com", + "message-id": "<202305311328.81899448177@smtp-relay.mailin.fr>", + "date": "2023-05-31 15:28:33", + "tags": [ + "tag1", + "tag2" + ], + "tag": "[\"tag1\",\"tag2\"]", + "event": "delivered", + "subject": "Subject Line", + "sending_ip": "127.0.0.1", + "ts_event": 1685539713, + "ts": 1685539713, + "reason": "sent", + "ts_epoch": 1685539713018 +} diff --git a/src/Symfony/Component/Mailer/Bridge/Brevo/Tests/Webhook/Fixtures/delivered.php b/src/Symfony/Component/Mailer/Bridge/Brevo/Tests/Webhook/Fixtures/delivered.php new file mode 100644 index 0000000000000..f74c1cebbe473 --- /dev/null +++ b/src/Symfony/Component/Mailer/Bridge/Brevo/Tests/Webhook/Fixtures/delivered.php @@ -0,0 +1,10 @@ +', json_decode(file_get_contents(str_replace('.php', '.json', __FILE__)), true, flags: JSON_THROW_ON_ERROR)); +$wh->setRecipientEmail('example@gmail.com'); +$wh->setTags(['tag1', 'tag2']); +$wh->setDate(\DateTimeImmutable::createFromFormat('U', 1685539713)); + +return $wh; diff --git a/src/Symfony/Component/Mailer/Bridge/Brevo/Tests/Webhook/Fixtures/error.json b/src/Symfony/Component/Mailer/Bridge/Brevo/Tests/Webhook/Fixtures/error.json new file mode 100644 index 0000000000000..fd2f8a7643329 --- /dev/null +++ b/src/Symfony/Component/Mailer/Bridge/Brevo/Tests/Webhook/Fixtures/error.json @@ -0,0 +1,14 @@ +{ + "event": "invalid_email", + "email": "example@gmail.com", + "id": 814119, + "date": "2020-10-09 00:00:00", + "ts": 1604933619, + "message-id": "<201798300811.5787683@smtp-relay.mailin.fr>", + "ts_event": 1604933654, + "subject": "My first Transactional", + "X-Mailin-custom": "some_custom_header", + "template_id": 22, + "tags": ["transac_messages"], + "ts_epoch": 1604933623 +} diff --git a/src/Symfony/Component/Mailer/Bridge/Brevo/Tests/Webhook/Fixtures/error.php b/src/Symfony/Component/Mailer/Bridge/Brevo/Tests/Webhook/Fixtures/error.php new file mode 100644 index 0000000000000..d569784cb5c82 --- /dev/null +++ b/src/Symfony/Component/Mailer/Bridge/Brevo/Tests/Webhook/Fixtures/error.php @@ -0,0 +1,10 @@ +', json_decode(file_get_contents(str_replace('.php', '.json', __FILE__)), true, flags: JSON_THROW_ON_ERROR)); +$wh->setRecipientEmail('example@gmail.com'); +$wh->setTags(['transac_messages']); +$wh->setDate(\DateTimeImmutable::createFromFormat('U', 1604933654)); + +return $wh; diff --git a/src/Symfony/Component/Mailer/Bridge/Brevo/Tests/Webhook/Fixtures/hard_bounce.json b/src/Symfony/Component/Mailer/Bridge/Brevo/Tests/Webhook/Fixtures/hard_bounce.json new file mode 100644 index 0000000000000..e767033a655ba --- /dev/null +++ b/src/Symfony/Component/Mailer/Bridge/Brevo/Tests/Webhook/Fixtures/hard_bounce.json @@ -0,0 +1,18 @@ +{ + "id": 814119, + "email": "example@gmail.com", + "message-id": "<202305311437.85220493321@smtp-relay.mailin.fr>", + "date": "2023-05-31 16:37:07", + "tags": [ + "welcome", + "tag2" + ], + "tag": "[\"welcome\",\"tag2\"]", + "event": "hard_bounce", + "subject": "Subject Line", + "sending_ip": "127.0.0.1", + "ts_event": 1685543827, + "ts": 1685543827, + "reason": "550-5.1.1 The email account that you tried to reach does not exist. Please try\n 550-5.1.1 double-checking the recipient's email address for typos or\n 550-5.1.1 unnecessary spaces. Learn more at\n 550 5.1.1 https://support.google.com/mail/?p=NoSuchUser g14-20020a5d698e000000b0030aefbb8608si2668200wru.496 - gsmtp", + "ts_epoch": 1685543827372 +} diff --git a/src/Symfony/Component/Mailer/Bridge/Brevo/Tests/Webhook/Fixtures/hard_bounce.php b/src/Symfony/Component/Mailer/Bridge/Brevo/Tests/Webhook/Fixtures/hard_bounce.php new file mode 100644 index 0000000000000..065e46ad4991e --- /dev/null +++ b/src/Symfony/Component/Mailer/Bridge/Brevo/Tests/Webhook/Fixtures/hard_bounce.php @@ -0,0 +1,11 @@ +', json_decode(file_get_contents(str_replace('.php', '.json', __FILE__)), true, flags: JSON_THROW_ON_ERROR)); +$wh->setRecipientEmail('example@gmail.com'); +$wh->setTags(['welcome', 'tag2']); +$wh->setDate(\DateTimeImmutable::createFromFormat('U', 1685543827)); +$wh->setReason("550-5.1.1 The email account that you tried to reach does not exist. Please try\n 550-5.1.1 double-checking the recipient's email address for typos or\n 550-5.1.1 unnecessary spaces. Learn more at\n 550 5.1.1 https://support.google.com/mail/?p=NoSuchUser g14-20020a5d698e000000b0030aefbb8608si2668200wru.496 - gsmtp"); + +return $wh; diff --git a/src/Symfony/Component/Mailer/Bridge/Brevo/Tests/Webhook/Fixtures/invalid_email.json b/src/Symfony/Component/Mailer/Bridge/Brevo/Tests/Webhook/Fixtures/invalid_email.json new file mode 100644 index 0000000000000..fbc62ac92c5de --- /dev/null +++ b/src/Symfony/Component/Mailer/Bridge/Brevo/Tests/Webhook/Fixtures/invalid_email.json @@ -0,0 +1,14 @@ +{ + "event": "invalid_email", + "email": "example@gmail.com", + "id": 625895, + "date": "2020-10-09 00:00:00", + "ts": 1604933619, + "message-id": "<201798300811.5787683@smtp-relay.mailin.fr>", + "ts_event": 1604933654, + "subject": "My first Transactional", + "X-Mailin-custom": "some_custom_header", + "template_id": 22, + "tags": ["transac_messages"], + "ts_epoch": 1604933623 +} diff --git a/src/Symfony/Component/Mailer/Bridge/Brevo/Tests/Webhook/Fixtures/invalid_email.php b/src/Symfony/Component/Mailer/Bridge/Brevo/Tests/Webhook/Fixtures/invalid_email.php new file mode 100644 index 0000000000000..d569784cb5c82 --- /dev/null +++ b/src/Symfony/Component/Mailer/Bridge/Brevo/Tests/Webhook/Fixtures/invalid_email.php @@ -0,0 +1,10 @@ +', json_decode(file_get_contents(str_replace('.php', '.json', __FILE__)), true, flags: JSON_THROW_ON_ERROR)); +$wh->setRecipientEmail('example@gmail.com'); +$wh->setTags(['transac_messages']); +$wh->setDate(\DateTimeImmutable::createFromFormat('U', 1604933654)); + +return $wh; diff --git a/src/Symfony/Component/Mailer/Bridge/Brevo/Tests/Webhook/Fixtures/opened.json b/src/Symfony/Component/Mailer/Bridge/Brevo/Tests/Webhook/Fixtures/opened.json new file mode 100644 index 0000000000000..51eb9a524ce9c --- /dev/null +++ b/src/Symfony/Component/Mailer/Bridge/Brevo/Tests/Webhook/Fixtures/opened.json @@ -0,0 +1,18 @@ +{ + "id": 814119, + "email": "example@gmail.com", + "message-id": "<202305311408.17112967225@smtp-relay.mailin.fr>", + "date": "2023-05-31 16:45:09", + "tags": [ + "welcome", + "tag2" + ], + "tag": "[\"welcome\",\"tag2\"]", + "event": "opened", + "subject": "Subject Line", + "sending_ip": "127.0.0.1", + "ts": 1685544309, + "ts_epoch": 1685544309368, + "ts_event": 1685544309, + "link": "" +} diff --git a/src/Symfony/Component/Mailer/Bridge/Brevo/Tests/Webhook/Fixtures/opened.php b/src/Symfony/Component/Mailer/Bridge/Brevo/Tests/Webhook/Fixtures/opened.php new file mode 100644 index 0000000000000..72688987f0bf8 --- /dev/null +++ b/src/Symfony/Component/Mailer/Bridge/Brevo/Tests/Webhook/Fixtures/opened.php @@ -0,0 +1,10 @@ +', json_decode(file_get_contents(str_replace('.php', '.json', __FILE__)), true, flags: JSON_THROW_ON_ERROR)); +$wh->setRecipientEmail('example@gmail.com'); +$wh->setTags(['welcome', 'tag2']); +$wh->setDate(\DateTimeImmutable::createFromFormat('U', 1685544309)); + +return $wh; diff --git a/src/Symfony/Component/Mailer/Bridge/Brevo/Tests/Webhook/Fixtures/proxy_open.json b/src/Symfony/Component/Mailer/Bridge/Brevo/Tests/Webhook/Fixtures/proxy_open.json new file mode 100644 index 0000000000000..79a36d9c251cc --- /dev/null +++ b/src/Symfony/Component/Mailer/Bridge/Brevo/Tests/Webhook/Fixtures/proxy_open.json @@ -0,0 +1,14 @@ +{ + "event": "proxy_open", + "email": "example@gmail.com", + "id": 1, + "date": "2020-10-09 00:00:00", + "message-id": "<201798300811.5787683@relay.domain.com>", + "subject": "My first Transactional", + "tags": ["transactionalTag"], + "sending_ip": "127.0.0.1", + "s_epoch": 1534486682000, + "template_id": 1, + "ts": 1604933654, + "ts_event": 1604933654 +} diff --git a/src/Symfony/Component/Mailer/Bridge/Brevo/Tests/Webhook/Fixtures/proxy_open.php b/src/Symfony/Component/Mailer/Bridge/Brevo/Tests/Webhook/Fixtures/proxy_open.php new file mode 100644 index 0000000000000..17729c405d7c4 --- /dev/null +++ b/src/Symfony/Component/Mailer/Bridge/Brevo/Tests/Webhook/Fixtures/proxy_open.php @@ -0,0 +1,10 @@ +', json_decode(file_get_contents(str_replace('.php', '.json', __FILE__)), true, flags: JSON_THROW_ON_ERROR)); +$wh->setRecipientEmail('example@gmail.com'); +$wh->setTags(['transactionalTag']); +$wh->setDate(\DateTimeImmutable::createFromFormat('U', 1604933654)); + +return $wh; diff --git a/src/Symfony/Component/Mailer/Bridge/Brevo/Tests/Webhook/Fixtures/request.json b/src/Symfony/Component/Mailer/Bridge/Brevo/Tests/Webhook/Fixtures/request.json new file mode 100644 index 0000000000000..67ebfd1041c39 --- /dev/null +++ b/src/Symfony/Component/Mailer/Bridge/Brevo/Tests/Webhook/Fixtures/request.json @@ -0,0 +1,18 @@ +{ + "id": 814119, + "email": "example@gmail.com", + "message-id": "<202305311313.92192897094@smtp-relay.mailin.fr>", + "date": "2023-05-31 15:13:08", + "tags": [ + "tag1", + "tag2" + ], + "tag": "[\"tag1\",\"tag2\"]", + "event": "request", + "subject": "Subject Line", + "sending_ip": "127.0.0.1", + "ts_event": 1685538788, + "ts": 1685538788, + "reason": "sent", + "ts_epoch": 1685538788179 +} diff --git a/src/Symfony/Component/Mailer/Bridge/Brevo/Tests/Webhook/Fixtures/request.php b/src/Symfony/Component/Mailer/Bridge/Brevo/Tests/Webhook/Fixtures/request.php new file mode 100644 index 0000000000000..138865dd520fe --- /dev/null +++ b/src/Symfony/Component/Mailer/Bridge/Brevo/Tests/Webhook/Fixtures/request.php @@ -0,0 +1,10 @@ +', json_decode(file_get_contents(str_replace('.php', '.json', __FILE__)), true, flags: JSON_THROW_ON_ERROR)); +$wh->setRecipientEmail('example@gmail.com'); +$wh->setTags(['tag1', 'tag2']); +$wh->setDate(\DateTimeImmutable::createFromFormat('U', 1685538788)); + +return $wh; diff --git a/src/Symfony/Component/Mailer/Bridge/Brevo/Tests/Webhook/Fixtures/soft_bounce.json b/src/Symfony/Component/Mailer/Bridge/Brevo/Tests/Webhook/Fixtures/soft_bounce.json new file mode 100644 index 0000000000000..0043a2d183c6f --- /dev/null +++ b/src/Symfony/Component/Mailer/Bridge/Brevo/Tests/Webhook/Fixtures/soft_bounce.json @@ -0,0 +1,15 @@ +{ + "event": "soft_bounce", + "email": "example@gmail.com", + "id": 81205, + "date": "2020-10-09 00:00:00", + "ts": 1604933619, + "message-id": "<201798300811.5787683@smtp-relay.mailin.fr>", + "ts_event": 1604933654, + "subject": "My first Transactional", + "X-Mailin-custom": "some_custom_header", + "sending_ip": "127.0.0.1", + "template_id": 22, + "tags": ["transac_messages"], + "reason": "server is down" +} diff --git a/src/Symfony/Component/Mailer/Bridge/Brevo/Tests/Webhook/Fixtures/soft_bounce.php b/src/Symfony/Component/Mailer/Bridge/Brevo/Tests/Webhook/Fixtures/soft_bounce.php new file mode 100644 index 0000000000000..04fdcd01aa028 --- /dev/null +++ b/src/Symfony/Component/Mailer/Bridge/Brevo/Tests/Webhook/Fixtures/soft_bounce.php @@ -0,0 +1,11 @@ +', json_decode(file_get_contents(str_replace('.php', '.json', __FILE__)), true, flags: JSON_THROW_ON_ERROR)); +$wh->setRecipientEmail('example@gmail.com'); +$wh->setTags(['transac_messages']); +$wh->setDate(\DateTimeImmutable::createFromFormat('U', 1604933654)); +$wh->setReason("server is down"); + +return $wh; diff --git a/src/Symfony/Component/Mailer/Bridge/Brevo/Tests/Webhook/Fixtures/unique_opened.json b/src/Symfony/Component/Mailer/Bridge/Brevo/Tests/Webhook/Fixtures/unique_opened.json new file mode 100644 index 0000000000000..33d9fefcee89e --- /dev/null +++ b/src/Symfony/Component/Mailer/Bridge/Brevo/Tests/Webhook/Fixtures/unique_opened.json @@ -0,0 +1,18 @@ +{ + "id": 814119, + "email": "example@gmail.com", + "message-id": "<202305311447.66548003588@smtp-relay.mailin.fr>", + "date": "2023-05-31 16:48:52", + "tags": [ + "welcome", + "tag2" + ], + "tag": "[\"welcome\",\"tag2\"]", + "event": "unique_opened", + "subject": "Subject Line", + "sending_ip": "80.12.93.84", + "ts": 1685544532, + "ts_epoch": 1685544532962, + "ts_event": 1685544532, + "link": "" +} diff --git a/src/Symfony/Component/Mailer/Bridge/Brevo/Tests/Webhook/Fixtures/unique_opened.php b/src/Symfony/Component/Mailer/Bridge/Brevo/Tests/Webhook/Fixtures/unique_opened.php new file mode 100644 index 0000000000000..1cdf8c442f1fc --- /dev/null +++ b/src/Symfony/Component/Mailer/Bridge/Brevo/Tests/Webhook/Fixtures/unique_opened.php @@ -0,0 +1,10 @@ +', json_decode(file_get_contents(str_replace('.php', '.json', __FILE__)), true, flags: JSON_THROW_ON_ERROR)); +$wh->setRecipientEmail('example@gmail.com'); +$wh->setTags(['welcome', 'tag2']); +$wh->setDate(\DateTimeImmutable::createFromFormat('U', 1685544532)); + +return $wh; diff --git a/src/Symfony/Component/Mailer/Bridge/Brevo/Tests/Webhook/Fixtures/unsubscribed.json b/src/Symfony/Component/Mailer/Bridge/Brevo/Tests/Webhook/Fixtures/unsubscribed.json new file mode 100644 index 0000000000000..855b2c3f2beee --- /dev/null +++ b/src/Symfony/Component/Mailer/Bridge/Brevo/Tests/Webhook/Fixtures/unsubscribed.json @@ -0,0 +1,17 @@ +{ + "id": 814119, + "email": "example@gmail.com", + "message-id": "<202305311328.81899448177@smtp-relay.mailin.fr>", + "date": "2023-05-31 15:52:01", + "tags": [ + "tag1", + "tag2" + ], + "tag": "[\"tag1\",\"tag2\"]", + "event": "unsubscribed", + "subject": "Subject Line", + "ts_event": 1685541121, + "ts": 1685541121, + "is_returnpath": true, + "ts_epoch": 1685541121606 +} diff --git a/src/Symfony/Component/Mailer/Bridge/Brevo/Tests/Webhook/Fixtures/unsubscribed.php b/src/Symfony/Component/Mailer/Bridge/Brevo/Tests/Webhook/Fixtures/unsubscribed.php new file mode 100644 index 0000000000000..910b4b70ab01e --- /dev/null +++ b/src/Symfony/Component/Mailer/Bridge/Brevo/Tests/Webhook/Fixtures/unsubscribed.php @@ -0,0 +1,10 @@ +', json_decode(file_get_contents(str_replace('.php', '.json', __FILE__)), true, flags: JSON_THROW_ON_ERROR)); +$wh->setRecipientEmail('example@gmail.com'); +$wh->setTags(['tag1', 'tag2']); +$wh->setDate(\DateTimeImmutable::createFromFormat('U', 1685541121)); + +return $wh; diff --git a/src/Symfony/Component/Mailer/Bridge/Brevo/Webhook/BrevoRequestParser.php b/src/Symfony/Component/Mailer/Bridge/Brevo/Webhook/BrevoRequestParser.php new file mode 100644 index 0000000000000..5c13bcc7ac641 --- /dev/null +++ b/src/Symfony/Component/Mailer/Bridge/Brevo/Webhook/BrevoRequestParser.php @@ -0,0 +1,63 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Mailer\Bridge\Brevo\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\RequestMatcher\IpsRequestMatcher; +use Symfony\Component\HttpFoundation\RequestMatcherInterface; +use Symfony\Component\Mailer\Bridge\Brevo\RemoteEvent\BrevoPayloadConverter; +use Symfony\Component\RemoteEvent\Event\Mailer\AbstractMailerEvent; +use Symfony\Component\RemoteEvent\Exception\ParseException; +use Symfony\Component\Webhook\Client\AbstractRequestParser; +use Symfony\Component\Webhook\Exception\RejectWebhookException; + +final class BrevoRequestParser extends AbstractRequestParser +{ + public function __construct( + private readonly BrevoPayloadConverter $converter, + ) { + } + + protected function getRequestMatcher(): RequestMatcherInterface + { + return new ChainRequestMatcher([ + new MethodRequestMatcher('POST'), + new IsJsonRequestMatcher(), + // https://developers.brevo.com/docs/how-to-use-webhooks#securing-your-webhooks + // localhost is added for testing + new IpsRequestMatcher(['185.107.232.1/24', '1.179.112.1/20', '127.0.0.1']), + ]); + } + + protected function doParse(Request $request, string $secret): ?AbstractMailerEvent + { + $content = $request->toArray(); + if ( + !isset($content['event']) + || !isset($content['email']) + || !isset($content['message-id']) + || !isset($content['ts_event']) + || !isset($content['tags']) + ) { + throw new RejectWebhookException(406, 'Payload is malformed.'); + } + + try { + return $this->converter->convert($content); + } catch (ParseException $e) { + throw new RejectWebhookException(406, $e->getMessage(), $e); + } + } +} diff --git a/src/Symfony/Component/Mailer/Bridge/Brevo/composer.json b/src/Symfony/Component/Mailer/Bridge/Brevo/composer.json index dc5a0c996b2ff..daa9d32a8233d 100644 --- a/src/Symfony/Component/Mailer/Bridge/Brevo/composer.json +++ b/src/Symfony/Component/Mailer/Bridge/Brevo/composer.json @@ -6,30 +6,31 @@ "homepage": "https://symfony.com", "license": "MIT", "authors": [ - { - "name": "Pierre Tanguy", - "homepage": "https://github.com/petanguy" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } + { + "name": "Pierre Tanguy", + "homepage": "https://github.com/petanguy" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } ], "require": { "php": ">=8.1", "symfony/mailer": "^5.4.21|^6.2.7" }, "require-dev": { - "symfony/http-client": "^5.4|^6.0" + "symfony/http-client": "^5.4|^6.0", + "symfony/webhook": "^6.4" }, "conflict": { - "symfony/mime": "<6.2" + "symfony/mime": "<6.2" }, "autoload": { - "psr-4": { "Symfony\\Component\\Mailer\\Bridge\\Brevo\\": "" }, - "exclude-from-classmap": [ - "/Tests/" - ] + "psr-4": { "Symfony\\Component\\Mailer\\Bridge\\Brevo\\": "" }, + "exclude-from-classmap": [ + "/Tests/" + ] }, "minimum-stability": "dev" }