8000 [Webhook][RemoteEvent] Add Sendgrid symfony#50704 · symfony/symfony@96186e1 · GitHub
[go: up one dir, main page]

Skip to content

Commit 96186e1

Browse files
[Webhook][RemoteEvent] Add Sendgrid #50704
1 parent 52a9292 commit 96186e1

14 files changed

+453
-2
lines changed

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2615,6 +2615,7 @@ private function registerMailerConfiguration(array $config, ContainerBuilder $co
26152615
$webhookRequestParsers = [
26162616
MailerBridge\Mailgun\Webhook\MailgunRequestParser::class => 'mailer.webhook.request_parser.mailgun',
26172617
MailerBridge\Postmark\Webhook\PostmarkRequestParser::class => 'mailer.webhook.request_parser.postmark',
2618+
MailerBridge\Sendgrid\Webhook\SendgridRequestParser::class => 'mailer.webhook.request_parser.sendgrid',
26182619
];
26192620

26202621
foreach ($webhookRequestParsers as $class => $service) {

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

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@
1515
use Symfony\Component\Mailer\Bridge\Mailgun\Webhook\MailgunRequestParser;
1616
use Symfony\Component\Mailer\Bridge\Postmark\RemoteEvent\PostmarkPayloadConverter;
1717
use Symfony\Component\Mailer\Bridge\Postmark\Webhook\PostmarkRequestParser;
18+
use Symfony\Component\Mailer\Bridge\Sendgrid\RemoteEvent\SendgridPayloadConverter;
19+
use Symfony\Component\Mailer\Bridge\Sendgrid\Webhook\SendgridRequestParser;
1820

1921
return static function (ContainerConfigurator $container) {
2022
$container->services()
@@ -27,5 +29,10 @@
2729
->set('mailer.webhook.request_parser.postmark', PostmarkRequestParser::class)
2830
->args([service('mailer.payload_converter.postmark')])
2931
->alias(PostmarkRequestParser::class, 'mailer.webhook.request_parser.postmark')
32+
33+
->set('mailer.payload_converter.sendgrid', SendgridPayloadConverter::class)
34+
->set('mailer.webhook.request_parser.sendgrid', SendgridRequestParser::class)
35+
->args([service('mailer.payload_converter.sendgrid')])
36+
->alias(SendgridRequestParser::class, 'mailer.webhook.request_parser.sendgrid')
3037
;
3138
};

src/Symfony/Component/Mailer/Bridge/Sendgrid/CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
CHANGELOG
22
=========
33

4+
6.4
5+
---
6+
7+
* Add support for webhooks
8+
49
5.4
510
---
611

src/Symfony/Component/Mailer/Bridge/Sendgrid/README.md

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,30 @@ MAILER_DSN=sendgrid+api://KEY@default
1616
where:
1717
- `KEY` is your Sendgrid API Key
1818

19+
20+
Webhook:
21+
--------
22+
Create route:
23+
```yaml
24+
framework:
25+
webhook:
26+
routing:
27+
sendgrid:
28+
service: mailer.webhook.request_parser.sendgrid
29+
secret: '!SENDGRID_VALIDATION_SECRET!' #Leave blank if you dont want to use the signature validation
30+
```
31+
Create consumer:
32+
```php
33+
#[\Symfony\Component\RemoteEvent\Attribute\AsRemoteEventConsumer(name: 'sendgrid')]
34+
class SendGridConsumer implements ConsumerInterface
35+
{
36+
public function consume(RemoteEvent|MailerDeliveryEvent $event): void
37+
{
38+
//your code
39+
}
40+
}
41+
```
42+
1943
Resources
2044
---------
2145

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\Mailer\Bridge\Sendgrid\RemoteEvent;
13+
14+
use Symfony\Component\RemoteEvent\Event\Mailer\AbstractMailerEvent;
15+
use Symfony\Component\RemoteEvent\Event\Mailer\MailerDeliveryEvent;
16+
use Symfony\Component\RemoteEvent\Event\Mailer\MailerEngagementEvent;
17+
use Symfony\Component\RemoteEvent\Exception\ParseException;
18+
use Symfony\Component\RemoteEvent\PayloadConverterInterface;
19+
20+
/**
21+
* @author WoutervanderLoop.nl <info@woutervanderloop.nl>
22+
*/
23+
final class SendgridPayloadConverter implements PayloadConverterInterface
24+
{
25+
public function convert(array $payload): AbstractMailerEvent
26+
{
27+
if (\in_array($payload['event'], ['processed', 'delivered', 'bounce', 'dropped', 'deferred'], true)) {
28+
$name = match ($payload['event']) {
29+
'processed', 'delivered' => MailerDeliveryEvent::DELIVERED,
30+
'dropped' => MailerDeliveryEvent::DROPPED,
31+
'deferred' => MailerDeliveryEvent::DEFERRED,
32+
'bounce' => MailerDeliveryEvent::BOUNCE,
33+
};
34+
$event = new MailerDeliveryEvent($name, $payload['sg_message_id'], $payload);
35+
$event->setReason($payload['reason'] ?? '');
36+
} else {
37+
$name = match ($payload['event']) {
38+
'click' => MailerEngagementEvent::CLICK,
39+
'unsubscribe' => MailerEngagementEvent::UNSUBSCRIBE,
40+
'open' => MailerEngagementEvent::OPEN,
41+
'spamreport' => MailerEngagementEvent::SPAM,
42+
default => throw new ParseException(sprintf('Unsupported event "%s".', $payload['unsubscribe'])),
43+
};
44+
$event = new MailerEngagementEvent($name, $payload['sg_message_id'], $payload);
45+
}
46+
47+
if (!$date = \DateTimeImmutable::createFromFormat('U', $payload['timestamp'])) {
48+
throw new ParseException(sprintf('Invalid date "%s".', $payload['timestamp']));
49+
}
50+
51+
$event->setDate($date);
52+
$event->setRecipientEmail($payload['email']);
53+
$event->setMetadata([]);
54+
$event->setTags($payload['category'] ?? []);
55+
56+
return $event;
57+
}
58+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
[{"email":"hello@world.com","event":"dropped","reason":"Bounced Address","sg_event_id":"ZHJvcC0xMDk5NDkxOS1MUnpYbF9OSFN0T0doUTRrb2ZTbV9BLTA","sg_message_id":"LRzXl_NHStOGhQ4kofSm_A.filterdrecv-p3mdw1-756b745b58-kmzbl-18-5F5FC76C-9.0","smtp-id":"<LRzXl_NHStOGhQ4kofSm_A@ismtpd0039p1iad1.sendgrid.net>","timestamp":1600112492}]
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
< 1241 /div>
1+
<?php
2+
3+
use Symfony\Component\RemoteEvent\Event\Mailer\MailerDeliveryEvent;
4+
5+
$wh = new MailerDeliveryEvent(MailerDeliveryEvent::DROPPED, 'LRzXl_NHStOGhQ4kofSm_A.filterdrecv-p3mdw1-756b745b58-kmzbl-18-5F5FC76C-9.0', json_decode(file_get_contents(str_replace('.php', '.json', __FILE__)), true)[0]);
6+
$wh->setRecipientEmail('hello@world.com');
7+
$wh->setTags([]);
8+
$wh->setMetadata([]);
9+
$wh->setReason('Bounced Address');
10+
$wh->setDate(\DateTimeImmutable::createFromFormat('U', 1600112492));
11+
12+
return $wh;
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\Mailer\Bridge\Sendgrid\Tests\Webhook;
13+
14+
use Symfony\Component\HttpFoundation\Request;
15+
use Symfony\Component\Mailer\Bridge\Sendgrid\RemoteEvent\SendgridPayloadConverter;
16+
use Symfony\Component\Mailer\Bridge\Sendgrid\Webhook\SendgridRequestParser;
17+
use Symfony\Component\Webhook\Client\RequestParserInterface;
18+
use Symfony\Component\Webhook\Exception\RejectWebhookException;
19+
use Symfony\Component\Webhook\Test\AbstractRequestParserTestCase;
20+
21+
/**
22< 10000 span class="diff-text-marker">+
* @author WoutervanderLoop.nl <info@woutervanderloop.nl>
23+
*/
24+
class SendgridMissingSignedRequestParserTest extends AbstractRequestParserTestCase
25+
{
26+
protected function createRequestParser(): RequestParserInterface
27+
{
28+
$this->expectException(RejectWebhookException::class);
29+
$this->expectExceptionMessage('Signature is required.');
30+
31+
return new SendgridRequestParser(new SendgridPayloadConverter());
32+
}
33+
34+
/**
35+
* @see https://github.com/sendgrid/sendgrid-php/blob/9335dca98bc64456a72db73469d1dd67db72f6ea/test/unit/EventWebhookTest.php#L20
36+
*/
37+
protected function createRequest(string $payload): Request
38+
{
39+
return Request::create('/', 'POST', [], [], [], [
40+
'Content-Type' => 'application/json',
41+
], str_replace("\n", "\r\n", $payload));
42+
}
43+
44+
protected function getSecret(): string
45+
{
46+
return 'MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE83T4O/n84iotIvIW4mdBgQ/7dAfSmpqIM8kF9mN1flpVKS3GRqe62gw+2fNNRaINXvVpiglSI8eNEc6wEA3F+g==';
47+
}
48+
}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\Mailer\Bridge\Sendgrid\Tests\Webhook;
13+
14+
use Symfony\Component\HttpFoundation\Request;
15+
use Symfony\Component\Mailer\Bridge\Sendgrid\RemoteEvent\SendgridPayloadConverter;
16+
use Symfony\Component\Mailer\Bridge\Sendgrid\Webhook\SendgridRequestParser;
17+
use Symfony\Component\Webhook\Client\RequestParserInterface;
18+
use Symfony\Component\Webhook\Test\AbstractRequestParserTestCase;
19+
20+
/**
21+
* @author WoutervanderLoop.nl <info@woutervanderloop.nl>
22+
*/
23+
class SendgridSignedRequestParserTest extends AbstractRequestParserTestCase
24+
{
25+
protected function createRequestParser(): RequestParserInterface
26+
{
27+
return new SendgridRequestParser(new SendgridPayloadConverter(), true);
28+
}
29+
30+
/**
31+
* @see https://github.com/sendgrid/sendgrid-php/blob/9335dca98bc64456a72db73469d1dd67db72f6ea/test/unit/EventWebhookTest.php#L20
32+
*/
33+
protected function createRequest(string $payload): Request
34+
{
35+
return Request::create('/', 'POST', [], [], [], [
36+
'Content-Type' => 'application/json',
37+
'HTTP_X-Twilio-Email-Event-Webhook-Signature' => 'MEUCIGHQVtGj+Y3LkG9fLcxf3qfI10QysgDWmMOVmxG0u6ZUAiEAyBiXDWzM+uOe5W0JuG+luQAbPIqHh89M15TluLtEZtM=',
38+
'HTTP_X-Twilio-Email-Event-Webhook-Timestamp' => '1600112502',
39+
], str_replace("\n", "\r\n", $payload));
40+
}
41+
42+
protected function getSecret(): string
43+
{
44+
return 'MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE83T4O/n84iotIvIW4mdBgQ/7dAfSmpqIM8kF9mN1flpVKS3GRqe62gw+2fNNRaINXvVpiglSI8eNEc6wEA3F+g==';
45+
}
46+
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\Mailer\Bridge\Sendgrid\Tests\Webhook;
13+
14+
use Symfony\Component\HttpFoundation\Request;
15+
use Symfony\Component\Mailer\Bridge\Sendgrid\RemoteEvent\SendgridPayloadConverter;
16+
use Symfony\Component\Mailer\Bridge\Sendgrid\Webhook\SendgridRequestParser;
17+
use Symfony\Component\Webhook\Client\RequestParserInterface;
18+
use Symfony\Component\Webhook\Test\AbstractRequestParserTestCase;
19+
20+
/**
21+
* @author WoutervanderLoop.nl <info@woutervanderloop.nl>
22+
*/
23+
class SendgridUnsignedRequestParserTest extends AbstractRequestParserTestCase
24+
{
25+
protected function createRequestParser(): RequestParserInterface
26+
{
27+
return new SendgridRequestParser(new SendgridPayloadConverter());
28+
}
29+
30+
/**
31+
* @see https://github 10000 .com/sendgrid/sendgrid-php/blob/9335dca98bc64456a72db73469d1dd67db72f6ea/test/unit/EventWebhookTest.php#L20
32+
*/
33+
protected function createRequest(string $payload): Request
34+
{
35+
return Request::create('/', 'POST', [], [], [], [
36+
'Content-Type' => 'application/json',
37+
], str_replace("\n", "\r\n", $payload));
38+
}
39+
}
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\Mailer\Bridge\Sendgrid\Tests\Webhook;
13+
14+
use Symfony\Component\HttpFoundation\Request;
15+
use Symfony\Component\Mailer\Bridge\Sendgrid\RemoteEvent\SendgridPayloadConverter;
16+
use Symfony\Component\Mailer\Bridge\Sendgrid\Webhook\SendgridRequestParser;
17+
use Symfony\Component\Webhook\Client\RequestParserInterface;
18+
use Symfony\Component\Webhook\Exception\RejectWebhookException;
19+
use Symfony\Component\Webhook\Test\AbstractRequestParserTestCase;
20+
21+
/**
22+
* @author WoutervanderLoop.nl <info@woutervanderloop.nl>
23+
*/
24+
class SendgridWrongSecretRequestParserTest extends AbstractRequestParserTestCase
25+
{
26+
protected function createRequestParser(): RequestParserInterface
27+
{
28+
$this->expectException(RejectWebhookException::class);
29+
$this->expectExceptionMessage('Public key is wrong.');
30+
31+
return new SendgridRequestParser(new SendgridPayloadConverter());
32+
}
33+
34+
/**
35+
* @see https://github.com/sendgrid/sendgrid-php/blob/9335dca98bc64456a72db73469d1dd67db72f6ea/test/unit/EventWebhookTest.php#L20
36+
*/
37+
protected function createRequest(string $payload): Request
38+
{
39+
return Request::create('/', 'POST', [], [], [], [
40+
'Content-Type' => 'application/json',
41+
'HTTP_X-Twilio-Email-Event-Webhook-Signature' => 'MEUCIGHQVtGj+Y3LkG9fLcxf3qfI10QysgDWmMOVmxG0u6ZUAiEAyBiXDWzM+uOe5W0JuG+luQAbPIqHh89M15TluLtEZtM=',
42+
'HTTP_X-Twilio-Email-Event-Webhook-Timestamp' => '1600112502',
43+
], str_replace("\n", "\r\n", $payload));
44+
}
45+
46+
protected function getSecret(): string
47+
{
48+
return 'incorrect';
49+
}
50+
}
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\Mailer\Bridge\Sendgrid\Tests\Webhook;
13+
14+
use Symfony\Component\HttpFoundation\Request;
15+
use Symfony\Component\Mailer\Bridge\Sendgrid\RemoteEvent\SendgridPayloadConverter;
16+
use Symfony\Component\Mailer\Bridge\Sendgrid\Webhook\SendgridRequestParser;
17+
use Symfony\Component\Webhook\Client\RequestParserInterface;
18+
use Symfony\Component\Webhook\Exception\RejectWebhookException;
19+
use Symfony\Component\Webhook\Test\AbstractRequestParserTestCase;
20+
21+
/**
22+
* @author WoutervanderLoop.nl <info@woutervanderloop.nl>
23+
*/
24+
class SendgridWrongSignatureRequestParserTest extends AbstractRequestParserTestCase
25+
{
26+
protected function createRequestParser(): RequestParserInterface
27+
{
28+
$this->expectException(RejectWebhookException::class);
29+
$this->expectExceptionMessage('Signature is wrong.');
30+
31+
return new SendgridRequestParser(new SendgridPayloadConverter());
32+
}
33+
34+
/**
35+
* @see https://github.com/sendgrid/sendgrid-php/blob/9335dca98bc64456a72db73469d1dd67db72f6ea/test/unit/EventWebhookTest.php#L20
36+
*/
37+
protected function createRequest(string $payload): Request
38+
{
39+
return Request::create('/', 'POST', [], [], [], [
40+
'Content-Type' => 'application/json',
41+
'HTTP_X-Twilio-Email-Event-Webhook-Signature' => 'incorrect',
42+
'HTTP_X-Twilio-Email-Event-Webhook-Timestamp' => '1600112502',
43+
], str_replace("\n", "\r\n", $payload));
44+
}
45+
46+
protected function getSecret(): string
47+
{
48+
return 'MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE83T4O/n84iotIvIW4mdBgQ/7dAfSmpqIM8kF9mN1flpVKS3GRqe62gw+2fNNRaINXvVpiglSI8eNEc6wEA3F+g==';
49+
}
50+
}

0 commit comments

Comments
 (0)
0