Skip to content

Commit e2e4800

Browse files
committed
feature symfony#61455 [Mailer][Sendgrid] Add suppression groups support (KiloSierraCharlie)
This PR was squashed before being merged into the 7.4 branch. Discussion ---------- [Mailer][Sendgrid] Add suppression groups support | Q | A | ------------- | --- | Branch? | 7.4 | Bug fix? | no | New feature? | yes | Deprecations? | no | Issues | Fix/Implement symfony#53830 | License | MIT This PR implements a new header which supports the SendGrid suppression group/ASM options - allowing recipients of emails to unsubscribe. Commits ------- 1c69aa2 [Mailer][Sendgrid] Add suppression groups support
2 parents e0365cc + 1c69aa2 commit e2e4800

File tree

7 files changed

+170
-0
lines changed

7 files changed

+170
-0
lines changed

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+
7.4
5+
---
6+
7+
* Add support for suppression groups via `SuppressionGroupHeader`
8+
49
7.2
510
---
611

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <[email protected]>
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\Header;
13+
14+
use Symfony\Component\Mime\Header\UnstructuredHeader;
15+
16+
/**
17+
* @author Kieran Cross
18+
*/
19+
final class SuppressionGroupHeader extends UnstructuredHeader
20+
{
21+
/**
22+
* @param int[] $groupsToDisplay
23+
*/
24+
public function __construct(
25+
private int $groupId,
26+
private array $groupsToDisplay = [],
27+
) {
28+
$this->groupsToDisplay = array_values(array_map('intval', $groupsToDisplay));
29+
30+
parent::__construct('X-Sendgrid-SuppressionGroup', json_encode([
31+
'group_id' => $groupId,
32+
'groups_to_display' => $this->groupsToDisplay,
33+
], \JSON_THROW_ON_ERROR));
34+
}
35+
36+
public function getGroupId(): int
37+
{
38+
return $this->groupId;
39+
}
40+
41+
public function getGroupsToDisplay(): array
42+
{
43+
return $this->groupsToDisplay;
44+
}
45+
}

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

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,22 @@ class SendGridConsumer implements ConsumerInterface
4444
}
4545
```
4646

47+
Suppression Groups
48+
------------------
49+
50+
Create an e-mail and add the `SuppressionGroupHeader`:
51+
52+
```php
53+
use Symfony\Component\Mailer\Bridge\Sendgrid\Header\SuppressionGroupHeader;
54+
// [...]
55+
$email = new Email();
56+
$email->getHeaders()->add(new SuppressionGroupHeader(GROUP_ID, GROUPS_TO_DISPLAY));
57+
```
58+
59+
where:
60+
- `GROUP_ID` is your Sendgrid suppression group ID
61+
- `GROUPS_TO_DISPLAY_ID` is an array of the Sendgrid suppression group IDs presented to the user
62+
4763
Resources
4864
---------
4965

src/Symfony/Component/Mailer/Bridge/Sendgrid/Tests/Transport/SendgridApiTransportTest.php

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313

1414
use PHPUnit\Framework\Attributes\DataProvider;
1515
use PHPUnit\Framework\TestCase;
16+
use Symfony\Component\Mailer\Bridge\Sendgrid\Header\SuppressionGroupHeader;
1617
use Symfony\Component\Mailer\Bridge\Sendgrid\Transport\SendgridApiTransport;
1718
use Symfony\Component\Mailer\Envelope;
1819
use Symfony\Component\Mailer\Header\MetadataHeader;
@@ -289,4 +290,23 @@ public function testInlineWithoutCustomContentId()
289290

290291
$this->assertSame('text.txt', $payload['attachments'][0]['content_id']);
291292
}
293+
294+
public function testWithSuppressionGroup()
295+
{
296+
$email = new Email();
297+
$email->getHeaders()->add(new SuppressionGroupHeader(1, [1, 2, 3, 4, 5]));
298+
$envelope = new Envelope(new Address('[email protected]'), [new Address('[email protected]')]);
299+
300+
$transport = new SendgridApiTransport('ACCESS_KEY');
301+
$method = new \ReflectionMethod(SendgridApiTransport::class, 'getPayload');
302+
$payload = $method->invoke($transport, $email, $envelope);
303+
304+
$this->assertArrayHasKey('asm', $payload);
305+
$this->assertArrayHasKey('group_id', $payload['asm']);
306+
$this->assertArrayHasKey('groups_to_display', $payload['asm']);
307+
308+
$this->assertCount(5, $payload['asm']['groups_to_display']);
309+
310+
$this->assertSame([1, 2, 3, 4, 5], $payload['asm']['groups_to_display']);
311+
}
292312
}

src/Symfony/Component/Mailer/Bridge/Sendgrid/Tests/Transport/SendgridSmtpTransportTest.php

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,10 @@
1313

1414
use PHPUnit\Framework\Attributes\DataProvider;
1515
use PHPUnit\Framework\TestCase;
16+
use Symfony\Component\Mailer\Bridge\Sendgrid\Header\SuppressionGroupHeader;
1617
use Symfony\Component\Mailer\Bridge\Sendgrid\Transport\SendgridSmtpTransport;
18+
use Symfony\Component\Mime\Address;
19+
use Symfony\Component\Mime\Email;
1720

1821
class SendgridSmtpTransportTest extends TestCase
1922
{
@@ -36,4 +39,29 @@ public static function getTransportData()
3639
],
3740
];
3841
}
42+
43+
public function testSuppressionGroupHeader()
44+
{
45+
$email = (new Email())->subject('Hello!')
46+
->to(new Address('[email protected]', 'Kevin'))
47+
->from(new Address('[email protected]', 'Fabien'))
48+
->text('Hello There!');
49+
$email->getHeaders()->add(new SuppressionGroupHeader(1, [1, 2, 3, 4, 5]));
50+
51+
$transport = new SendgridSmtpTransport('ACCESS_KEY');
52+
$method = new \ReflectionMethod(SendgridSmtpTransport::class, 'addSendgridHeaders');
53+
$method->invoke($transport, $email);
54+
55+
$this->assertFalse($email->getHeaders()->has('X-Sendgrid-SuppressionGroup'));
56+
$this->assertTrue($email->getHeaders()->has('X-SMTPAPI'));
57+
58+
$json = $email->getHeaders()->get('X-SMTPAPI')->getBodyAsString();
59+
$payload = json_decode($json, true);
60+
61+
$this->assertArrayHasKey('asm', $payload);
62+
$this->assertArrayHasKey('group_id', $payload['asm']);
63+
$this->assertArrayHasKey('groups_to_display', $payload['asm']);
64+
$this->assertCount(5, $payload['asm']['groups_to_display']);
65+
$this->assertSame([1, 2, 3, 4, 5], $payload['asm']['groups_to_display']);
66+
}
3967
}

src/Symfony/Component/Mailer/Bridge/Sendgrid/Transport/SendgridApiTransport.php

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313

1414
use Psr\EventDispatcher\EventDispatcherInterface;
1515
use Psr\Log\LoggerInterface;
16+
use Symfony\Component\Mailer\Bridge\Sendgrid\Header\SuppressionGroupHeader;
1617
use Symfony\Component\Mailer\Envelope;
1718
use Symfony\Component\Mailer\Exception\HttpTransportException;
1819
use Symfony\Component\Mailer\Exception\TransportException;
@@ -132,6 +133,13 @@ private function getPayload(Email $email, Envelope $envelope): array
132133
$categories[] = mb_substr($header->getValue(), 0, 255);
133134
} elseif ($header instanceof MetadataHeader) {
134135
$customArguments[$header->getKey()] = $header->getValue();
136+
} elseif ($header instanceof SuppressionGroupHeader) {
137+
$payload['asm'] = [
138+
'group_id' => $header->getGroupId(),
139+
];
140+
if ($groupsToDisplay = $header->getGroupsToDisplay()) {
141+
$payload['asm']['groups_to_display'] = $groupsToDisplay;
142+
}
135143
} else {
136144
$payload['headers'][$header->getName()] = $header->getBodyAsString();
137145
}

src/Symfony/Component/Mailer/Bridge/Sendgrid/Transport/SendgridSmtpTransport.php

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,12 @@
1313

1414
use Psr\EventDispatcher\EventDispatcherInterface;
1515
use Psr\Log\LoggerInterface;
16+
use Symfony\Component\Mailer\Bridge\Sendgrid\Header\SuppressionGroupHeader;
17+
use Symfony\Component\Mailer\Envelope;
18+
use Symfony\Component\Mailer\SentMessage;
1619
use Symfony\Component\Mailer\Transport\Smtp\EsmtpTransport;
20+
use Symfony\Component\Mime\Message;
21+
use Symfony\Component\Mime\RawMessage;
1722

1823
/**
1924
* @author Kevin Verschaeve
@@ -27,4 +32,47 @@ public function __construct(#[\SensitiveParameter] string $key, ?EventDispatcher
2732
$this->setUsername('apikey');
2833
$this->setPassword($key);
2934
}
35+
36+
public function send(RawMessage $message, ?Envelope $envelope = null): ?SentMessage
37+
{
38+
if ($message instanceof Message) {
39+
$this->addSendgridHeaders($message);
40+
}
41+
42+
return parent::send($message, $envelope);
43+
}
44+
45+
private function addSendgridHeaders(Message $message): void
46+
{
47+
$headers = $message->getHeaders();
48+
49+
if ($headers->has('X-SMTPAPI')) {
50+
return;
51+
}
52+
53+
$suppressionHeader = null;
54+
55+
foreach ($headers->all() as $header) {
56+
if ($header instanceof SuppressionGroupHeader) {
57+
$suppressionHeader = $header;
58+
break;
59+
}
60+
}
61+
62+
if ($suppressionHeader) {
63+
$payload = [
64+
'asm' => [
65+
'group_id' => $suppressionHeader->getGroupId(),
66+
],
67+
];
68+
69+
$groupsToDisplay = $suppressionHeader->getGroupsToDisplay();
70+
if ($groupsToDisplay) {
71+
$payload['asm']['groups_to_display'] = $groupsToDisplay;
72+
}
73+
74+
$headers->addTextHeader('X-SMTPAPI', json_encode($payload, \JSON_UNESCAPED_SLASHES));
75+
$headers->remove('X-Sendgrid-SuppressionGroup');
76+
}
77+
}
3078
}

0 commit comments

Comments
 (0)