Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit 37b1ba8

Browse filesBrowse files
[Webhook][RemoteEvent] Add Sendgrid #50704
1 parent 52a9292 commit 37b1ba8
Copy full SHA for 37b1ba8

File tree

13 files changed

+366
-2
lines changed
Filter options

13 files changed

+366
-2
lines changed

‎composer.json

Copy file name to clipboardExpand all lines: composer.json
+1Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,7 @@
153153
"symfony/phpunit-bridge": "^5.4|^6.0|^7.0",
154154
"symfony/runtime": "self.version",
155155
"symfony/security-acl": "~2.8|~3.0",
156+
"starkbank/ecdsa": "^2.0",
156157
"twig/cssinliner-extra": "^2.12|^3",
157158
"twig/inky-extra": "^2.12|^3",
158159
"twig/markdown-extra": "^2.12|^3",

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

Copy file name to clipboardExpand all lines: src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php
+5Lines changed: 5 additions & 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) {
@@ -2624,6 +2625,10 @@ private function registerMailerConfiguration(array $config, ContainerBuilder $co
26242625
$container->removeDefinition($service);
26252626
}
26262627
}
2628+
2629+
if (ContainerBuilder::willBeAvailable('symfony/sendgrid-mailer', MailerBridge\Sendgrid\Webhook\SendgridRequestParser::class, ['symfony/framework-bundle', 'symfony/mailer'])) {
2630+
$container->setParameter('mailer.webhook.sendgrid.validate_signature', false);
2631+
}
26272632
}
26282633

26292634
$envelopeListener = $container->getDefinition('mailer.envelope_listener');

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

Copy file name to clipboardExpand all lines: src/Symfony/Bundle/FrameworkBundle/Resources/config/mailer_webhook.php
+10Lines changed: 10 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,13 @@
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([
36+
service('mailer.payload_converter.sendgrid'),
37+
param('mailer.webhook.sendgrid.validate_signature'),
38+
])
39+
->alias(SendgridRequestParser::class, 'mailer.webhook.request_parser.sendgrid')
3040
;
3141
};

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

Copy file name to clipboardExpand all lines: src/Symfony/Component/Mailer/Bridge/Sendgrid/CHANGELOG.md
+5Lines 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

Copy file name to clipboardExpand all lines: src/Symfony/Component/Mailer/Bridge/Sendgrid/README.md
+30Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,36 @@ 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!'
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+
43+
Default sendgrid enforces to validate the signature, to disable the signature validation:
44+
```yaml
45+
parameters:
46+
mailer.webhook.sendgrid.validate_signature: true
47+
```
48+
1949
Resources
2050
---------
2151
+58Lines 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+
}
+1Lines 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}]
+12Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
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;
+43Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
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 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(), true);
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+
}
+46Lines 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+
}
+39Lines 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(), false);
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+
], str_replace("\n", "\r\n", $payload));
38+
}
39+
}

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.