diff --git a/src/Symfony/Component/Mailer/Bridge/Mailjet/CHANGELOG.md b/src/Symfony/Component/Mailer/Bridge/Mailjet/CHANGELOG.md index 8795f1e1008da..bd1d2d82779bd 100644 --- a/src/Symfony/Component/Mailer/Bridge/Mailjet/CHANGELOG.md +++ b/src/Symfony/Component/Mailer/Bridge/Mailjet/CHANGELOG.md @@ -1,6 +1,11 @@ CHANGELOG ========= +6.4 +--- + + * Add `RemoteEvent` and `Webhook` support + 6.3 --- diff --git a/src/Symfony/Component/Mailer/Bridge/Mailjet/README.md b/src/Symfony/Component/Mailer/Bridge/Mailjet/README.md index 3c56a3d7d5b94..afa1d9fcdfa37 100644 --- a/src/Symfony/Component/Mailer/Bridge/Mailjet/README.md +++ b/src/Symfony/Component/Mailer/Bridge/Mailjet/README.md @@ -18,6 +18,11 @@ where: - `ACCESS_KEY` is your Mailjet access key - `SECRET_KEY` is your Mailjet secret key +Webhook +------- + +When you [setup your webhook URL](https://app.mailjet.com/account/triggers) on Mailjet you must not group events by unchecking the checkboxes. + Resources --------- diff --git a/src/Symfony/Component/Mailer/Bridge/Mailjet/RemoteEvent/MailjetPayloadConverter.php b/src/Symfony/Component/Mailer/Bridge/Mailjet/RemoteEvent/MailjetPayloadConverter.php new file mode 100644 index 0000000000000..c940e1ba4b097 --- /dev/null +++ b/src/Symfony/Component/Mailer/Bridge/Mailjet/RemoteEvent/MailjetPayloadConverter.php @@ -0,0 +1,66 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Mailer\Bridge\Mailjet\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 MailjetPayloadConverter implements PayloadConverterInterface +{ + public function convert(array $payload): AbstractMailerEvent + { + if (\in_array($payload['event'], ['bounce', 'sent', 'blocked'], true)) { + $name = match ($payload['event']) { + 'bounce' => MailerDeliveryEvent::BOUNCE, + 'sent' => MailerDeliveryEvent::DELIVERED, + 'blocked' => MailerDeliveryEvent::DROPPED, + }; + + $event = new MailerDeliveryEvent($name, $payload['MessageID'], $payload); + $event->setReason($this->getReason($payload)); + } else { + $name = match ($payload['event']) { + 'click' => MailerEngagementEvent::CLICK, + 'open' => MailerEngagementEvent::OPEN, + 'spam' => MailerEngagementEvent::SPAM, + 'unsub' => MailerEngagementEvent::UNSUBSCRIBE, + default => throw new ParseException(sprintf('Unsupported event "%s".', $payload['event'])), + }; + $event = new MailerEngagementEvent($name, $payload['MessageID'], $payload); + } + + if (!$date = \DateTimeImmutable::createFromFormat('U', $payload['time'])) { + throw new ParseException(sprintf('Invalid date "%s".', $payload['time'])); + } + + $event->setDate($date); + $event->setRecipientEmail($payload['email']); + + if (isset($payload['CustomID'])) { + $event->setTags([$payload['CustomID']]); + } + + if (isset($payload['Payload'])) { + $event->setMetadata(['Payload' => $payload['Payload']]); + } + + return $event; + } + + private function getReason(array $payload): string + { + return $payload['smtp_reply'] ?? $payload['error_related_to'] ?? ''; + } +} diff --git a/src/Symfony/Component/Mailer/Bridge/Mailjet/Tests/Webhook/Fixtures/blocked.json b/src/Symfony/Component/Mailer/Bridge/Mailjet/Tests/Webhook/Fixtures/blocked.json new file mode 100644 index 0000000000000..b632620f96516 --- /dev/null +++ b/src/Symfony/Component/Mailer/Bridge/Mailjet/Tests/Webhook/Fixtures/blocked.json @@ -0,0 +1,14 @@ +{ + "event": "blocked", + "time": 1430812195, + "MessageID": 13792286917004336, + "Message_GUID": "1ab23cd4-e567-8901-2345-6789f0gh1i2j", + "email": "bounce@mailjet.com", + "mj_campaign_id": 0, + "mj_contact_id": 1000, + "customcampaign": "", + "CustomID": "helloworld", + "Payload": "", + "error_related_to": "mailjet", + "error": "preblocked" +} diff --git a/src/Symfony/Component/Mailer/Bridge/Mailjet/Tests/Webhook/Fixtures/blocked.php b/src/Symfony/Component/Mailer/Bridge/Mailjet/Tests/Webhook/Fixtures/blocked.php new file mode 100644 index 0000000000000..f8afa7dad0835 --- /dev/null +++ b/src/Symfony/Component/Mailer/Bridge/Mailjet/Tests/Webhook/Fixtures/blocked.php @@ -0,0 +1,12 @@ +setRecipientEmail('bounce@mailjet.com'); +$wh->setDate(\DateTimeImmutable::createFromFormat('U', 1430812195)); +$wh->setReason('mailjet'); +$wh->setTags(['helloworld']); +$wh->setMetadata(['Payload' => '']); + +return $wh; diff --git a/src/Symfony/Component/Mailer/Bridge/Mailjet/Tests/Webhook/Fixtures/bounce.json b/src/Symfony/Component/Mailer/Bridge/Mailjet/Tests/Webhook/Fixtures/bounce.json new file mode 100644 index 0000000000000..d40b22d08655a --- /dev/null +++ b/src/Symfony/Component/Mailer/Bridge/Mailjet/Tests/Webhook/Fixtures/bounce.json @@ -0,0 +1,17 @@ +{ + "event": "bounce", + "time": 1685525050, + "MessageID": 104427216766056450, + "Message_GUID": "5577705c-024a-472d-8918-29ef81a64738", + "email": "event-bounce@yahoo.fr", + "mj_campaign_id": 0, + "mj_contact_id": 1000, + "customcampaign": "", + "blocked": false, + "hard_bounce": false, + "error_related_to": "policy issue", + "error": "", + "comment": "421 4.7.0 [TSS04] Messages from 87.253.233.123 temporarily deferred due to unexpected volume or user complaints - 4.16.55.1; see https://postmaster.yahooinc.com/error-codes", + "CustomID": "helloworld", + "Payload": "" +} diff --git a/src/Symfony/Component/Mailer/Bridge/Mailjet/Tests/Webhook/Fixtures/bounce.php b/src/Symfony/Component/Mailer/Bridge/Mailjet/Tests/Webhook/Fixtures/bounce.php new file mode 100644 index 0000000000000..d9b21e0e45b70 --- /dev/null +++ b/src/Symfony/Component/Mailer/Bridge/Mailjet/Tests/Webhook/Fixtures/bounce.php @@ -0,0 +1,12 @@ +setRecipientEmail('event-bounce@yahoo.fr'); +$wh->setDate(\DateTimeImmutable::createFromFormat('U', 1685525050)); +$wh->setReason('policy issue'); +$wh->setTags(['helloworld']); +$wh->setMetadata(['Payload' => '']); + +return $wh; diff --git a/src/Symfony/Component/Mailer/Bridge/Mailjet/Tests/Webhook/Fixtures/click.json b/src/Symfony/Component/Mailer/Bridge/Mailjet/Tests/Webhook/Fixtures/click.json new file mode 100644 index 0000000000000..20c01b0c0a5bd --- /dev/null +++ b/src/Symfony/Component/Mailer/Bridge/Mailjet/Tests/Webhook/Fixtures/click.json @@ -0,0 +1,16 @@ +{ + "event": "click", + "time": 1685519224, + "MessageID": 93449692684977140, + "Message_GUID": "245e4120-9d53-41b7-91f5-9aac8fda3cb0", + "email": "event-click@hotmail.com", + "mj_campaign_id": 0, + "mj_contact_id": 1000, + "customcampaign": "", + "url": "https://mailjet.com", + "ip": "127.0.0.1", + "geo": "FR", + "agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/113.0.0.0 Safari/537.36 CCleaner/113.0.21244.129", + "CustomID": "helloworld", + "Payload": "" +} diff --git a/src/Symfony/Component/Mailer/Bridge/Mailjet/Tests/Webhook/Fixtures/click.php b/src/Symfony/Component/Mailer/Bridge/Mailjet/Tests/Webhook/Fixtures/click.php new file mode 100644 index 0000000000000..3872b4980dad6 --- /dev/null +++ b/src/Symfony/Component/Mailer/Bridge/Mailjet/Tests/Webhook/Fixtures/click.php @@ -0,0 +1,11 @@ +setRecipientEmail('event-click@hotmail.com'); +$wh->setDate(\DateTimeImmutable::createFromFormat('U', 1685519224)); +$wh->setTags(['helloworld']); +$wh->setMetadata(['Payload' => '']); + +return $wh; diff --git a/src/Symfony/Component/Mailer/Bridge/Mailjet/Tests/Webhook/Fixtures/open.json b/src/Symfony/Component/Mailer/Bridge/Mailjet/Tests/Webhook/Fixtures/open.json new file mode 100644 index 0000000000000..de34d63f72a89 --- /dev/null +++ b/src/Symfony/Component/Mailer/Bridge/Mailjet/Tests/Webhook/Fixtures/open.json @@ -0,0 +1,15 @@ +{ + "event": "open", + "time": 1685519055, + "MessageID": 102175416994919440, + "Message_GUID": "982f91f1-4417-4ab1-9777-a2410e1bde36", + "email": "event-open@gmail.com", + "mj_campaign_id": 0, + "mj_contact_id": 1000, + "customcampaign": "", + "ip": "127.0.0.1", + "geo": "EU", + "agent": "Mozilla/5.0 (Windows NT 5.1; rv:11.0) Gecko Firefox/11.0 (via ggpht.com GoogleImageProxy)", + "CustomID": "helloworld", + "Payload": "" +} diff --git a/src/Symfony/Component/Mailer/Bridge/Mailjet/Tests/Webhook/Fixtures/open.php b/src/Symfony/Component/Mailer/Bridge/Mailjet/Tests/Webhook/Fixtures/open.php new file mode 100644 index 0000000000000..ea41b2be202b5 --- /dev/null +++ b/src/Symfony/Component/Mailer/Bridge/Mailjet/Tests/Webhook/Fixtures/open.php @@ -0,0 +1,11 @@ +setRecipientEmail('event-open@gmail.com'); +$wh->setDate(\DateTimeImmutable::createFromFormat('U', 1685519055)); +$wh->setTags(['helloworld']); +$wh->setMetadata(['Payload' => '']); + +return $wh; diff --git a/src/Symfony/Component/Mailer/Bridge/Mailjet/Tests/Webhook/Fixtures/sent.json b/src/Symfony/Component/Mailer/Bridge/Mailjet/Tests/Webhook/Fixtures/sent.json new file mode 100644 index 0000000000000..9937c466cfb52 --- /dev/null +++ b/src/Symfony/Component/Mailer/Bridge/Mailjet/Tests/Webhook/Fixtures/sent.json @@ -0,0 +1,13 @@ +{ + "event": "sent", + "time": 1685518742, + "MessageID": 92042317804662640, + "Message_GUID": "5b4de5f5-63d0-44f3-b4bd-a34f222cb8af", + "email": "event-sent@gmail.com", + "mj_campaign_id": 0, + "mj_contact_id": 1000, + "customcampaign": "", + "CustomID": "helloworld", + "Payload": "", + "smtp_reply": "250 2.0.0 OK 1685518742 k22-20020a05600c0b5600b003f6020d9976si8376621wmr.181 - gsmtp" +} diff --git a/src/Symfony/Component/Mailer/Bridge/Mailjet/Tests/Webhook/Fixtures/sent.php b/src/Symfony/Component/Mailer/Bridge/Mailjet/Tests/Webhook/Fixtures/sent.php new file mode 100644 index 0000000000000..76eb06b79032d --- /dev/null +++ b/src/Symfony/Component/Mailer/Bridge/Mailjet/Tests/Webhook/Fixtures/sent.php @@ -0,0 +1,12 @@ +setRecipientEmail('event-sent@gmail.com'); +$wh->setDate(\DateTimeImmutable::createFromFormat('U', 1685518742)); +$wh->setReason('250 2.0.0 OK 1685518742 k22-20020a05600c0b5600b003f6020d9976si8376621wmr.181 - gsmtp'); +$wh->setTags(['helloworld']); +$wh->setMetadata(['Payload' => '']); + +return $wh; diff --git a/src/Symfony/Component/Mailer/Bridge/Mailjet/Tests/Webhook/Fixtures/spam.json b/src/Symfony/Component/Mailer/Bridge/Mailjet/Tests/Webhook/Fixtures/spam.json new file mode 100644 index 0000000000000..944877eafb690 --- /dev/null +++ b/src/Symfony/Component/Mailer/Bridge/Mailjet/Tests/Webhook/Fixtures/spam.json @@ -0,0 +1,13 @@ +{ + "event": "spam", + "time": 1430812195, + "MessageID": 13792286917004336, + "Message_GUID": "1ab23cd4-e567-8901-2345-6789f0gh1i2j", + "email": "bounce@mailjet.com", + "mj_campaign_id": 0, + "mj_contact_id": 1000, + "customcampaign": "", + "CustomID": "helloworld", + "Payload": "", + "source": "JMRPP" +} diff --git a/src/Symfony/Component/Mailer/Bridge/Mailjet/Tests/Webhook/Fixtures/spam.php b/src/Symfony/Component/Mailer/Bridge/Mailjet/Tests/Webhook/Fixtures/spam.php new file mode 100644 index 0000000000000..23221094a3d88 --- /dev/null +++ b/src/Symfony/Component/Mailer/Bridge/Mailjet/Tests/Webhook/Fixtures/spam.php @@ -0,0 +1,11 @@ +setRecipientEmail('bounce@mailjet.com'); +$wh->setDate(\DateTimeImmutable::createFromFormat('U', 1430812195)); +$wh->setTags(['helloworld']); +$wh->setMetadata(['Payload' => '']); + +return $wh; diff --git a/src/Symfony/Component/Mailer/Bridge/Mailjet/Tests/Webhook/Fixtures/unsub.json b/src/Symfony/Component/Mailer/Bridge/Mailjet/Tests/Webhook/Fixtures/unsub.json new file mode 100644 index 0000000000000..7b632d62ee905 --- /dev/null +++ b/src/Symfony/Component/Mailer/Bridge/Mailjet/Tests/Webhook/Fixtures/unsub.json @@ -0,0 +1,17 @@ +{ + "event": "unsub", + "time": 1433334941, + "MessageID": 20547674933128000, + "Message_GUID": "1ab23cd4-e567-8901-2345-6789f0gh1i2j", + "email": "api@mailjet.com", + "mj_campaign_id": 0, + "mj_contact_id": 1000, + "customcampaign": "", + "CustomID": "helloworld", + "Payload": "", + "mj_list_id": 1, + "ip": "127.0.0.1", + "geo": "FR", + "agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/42.0.2311.135 Safari/537.36" +} + diff --git a/src/Symfony/Component/Mailer/Bridge/Mailjet/Tests/Webhook/Fixtures/unsub.php b/src/Symfony/Component/Mailer/Bridge/Mailjet/Tests/Webhook/Fixtures/unsub.php new file mode 100644 index 0000000000000..bba2b27cface8 --- /dev/null +++ b/src/Symfony/Component/Mailer/Bridge/Mailjet/Tests/Webhook/Fixtures/unsub.php @@ -0,0 +1,11 @@ +setRecipientEmail('api@mailjet.com'); +$wh->setDate(\DateTimeImmutable::createFromFormat('U', 1433334941)); +$wh->setTags(['helloworld']); +$wh->setMetadata(['Payload' => '']); + +return $wh; diff --git a/src/Symfony/Component/Mailer/Bridge/Mailjet/Tests/Webhook/MailjetRequestParserTest.php b/src/Symfony/Component/Mailer/Bridge/Mailjet/Tests/Webhook/MailjetRequestParserTest.php new file mode 100644 index 0000000000000..6001db4e90e9e --- /dev/null +++ b/src/Symfony/Component/Mailer/Bridge/Mailjet/Tests/Webhook/MailjetRequestParserTest.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\Mailjet\Tests\Webhook; + +use Symfony\Component\Mailer\Bridge\Mailjet\RemoteEvent\MailjetPayloadConverter; +use Symfony\Component\Mailer\Bridge\Mailjet\Webhook\MailjetRequestParser; +use Symfony\Component\Webhook\Client\RequestParserInterface; +use Symfony\Component\Webhook\Test\AbstractRequestParserTestCase; + +class MailjetRequestParserTest extends AbstractRequestParserTestCase +{ + protected function createRequestParser(): RequestParserInterface + { + return new MailjetRequestParser(new MailjetPayloadConverter()); + } +} diff --git a/src/Symfony/Component/Mailer/Bridge/Mailjet/Webhook/MailjetRequestParser.php b/src/Symfony/Component/Mailer/Bridge/Mailjet/Webhook/MailjetRequestParser.php new file mode 100644 index 0000000000000..d3f28ea461104 --- /dev/null +++ b/src/Symfony/Component/Mailer/Bridge/Mailjet/Webhook/MailjetRequestParser.php @@ -0,0 +1,48 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Mailer\Bridge\Mailjet\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\Mailjet\RemoteEvent\MailjetPayloadConverter; +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 MailjetRequestParser extends AbstractRequestParser +{ + public function __construct( + private readonly MailjetPayloadConverter $converter, + ) { + } + + protected function getRequestMatcher(): RequestMatcherInterface + { + return new ChainRequestMatcher([ + new MethodRequestMatcher('POST'), + new IsJsonRequestMatcher(), + ]); + } + + protected function doParse(Request $request, string $secret): ?AbstractMailerEvent + { + try { + return $this->converter->convert($request->toArray()); + } catch (ParseException $e) { + throw new RejectWebhookException(406, $e->getMessage(), $e); + } + } +} diff --git a/src/Symfony/Component/Mailer/Bridge/Mailjet/composer.json b/src/Symfony/Component/Mailer/Bridge/Mailjet/composer.json index 245d62810f8da..66cd40f9b58f5 100644 --- a/src/Symfony/Component/Mailer/Bridge/Mailjet/composer.json +++ b/src/Symfony/Component/Mailer/Bridge/Mailjet/composer.json @@ -20,7 +20,8 @@ "symfony/mailer": "^5.4.21|^6.2.7|^7.0" }, "require-dev": { - "symfony/http-client": "^5.4|^6.0|^7.0" + "symfony/http-client": "^5.4|^6.0|^7.0", + "symfony/webhook": "^6.4" }, "autoload": { "psr-4": { "Symfony\\Component\\Mailer\\Bridge\\Mailjet\\": "" }, pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: 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:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy