Skip to content

Commit 6ffd173

Browse files
vicdelfantfabpot
authored andcommitted
[Mailer] Dispatch event for Postmark's "inactive recipient" API error
1 parent c71348a commit 6ffd173

File tree

7 files changed

+197
-0
lines changed

7 files changed

+197
-0
lines changed
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
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\Postmark\Event;
13+
14+
use Symfony\Component\Mime\Header\Headers;
15+
16+
class PostmarkDeliveryEvent
17+
{
18+
public const CODE_INACTIVE_RECIPIENT = 406;
19+
20+
private int $errorCode;
21+
22+
private Headers $headers;
23+
24+
private ?string $message;
25+
26+
public function __construct(string $message, int $errorCode)
27+
{
28+
$this->message = $message;
29+
$this->errorCode = $errorCode;
30+
31+
$this->headers = new Headers();
32+
}
33+
34+
public function getErrorCode(): int
35+
{
36+
return $this->errorCode;
37+
}
38+
39+
public function getHeaders(): Headers
40+
{
41+
return $this->headers;
42+
}
43+
44+
public function getMessage(): ?string
45+
{
46+
return $this->message;
47+
}
48+
49+
public function getMessageId(): ?string
50+
{
51+
if (!$this->headers->has('Message-ID')) {
52+
return null;
53+
}
54+
55+
return $this->headers->get('Message-ID')->getBodyAsString();
56+
}
57+
58+
public function setHeaders(Headers $headers): self
59+
{
60+
$this->headers = $headers;
61+
62+
return $this;
63+
}
64+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
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\Postmark\Event;
13+
14+
use Symfony\Component\Mime\Email;
15+
16+
class PostmarkDeliveryEventFactory
17+
{
18+
public function create(int $errorCode, string $message, Email $email): PostmarkDeliveryEvent
19+
{
20+
if (!$this->supports($errorCode)) {
21+
throw new \InvalidArgumentException(sprintf('Error code "%s" is not supported.', $errorCode));
22+
}
23+
24+
return (new PostmarkDeliveryEvent($message, $errorCode))
25+
->setHeaders($email->getHeaders());
26+
}
27+
28+
public function supports(int $errorCode): bool
29+
{
30+
return \in_array($errorCode, [
31+
PostmarkDeliveryEvent::CODE_INACTIVE_RECIPIENT,
32+
]);
33+
}
34+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
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\Postmark\Event;
13+
14+
class PostmarkEvents
15+
{
16+
public const DELIVERY = 'postmark.delivery';
17+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
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\Postmark\Tests\Event;
13+
14+
use PHPUnit\Framework\TestCase;
15+
use Symfony\Component\Mailer\Bridge\Postmark\Event\PostmarkDeliveryEvent;
16+
use Symfony\Component\Mailer\Bridge\Postmark\Event\PostmarkDeliveryEventFactory;
17+
18+
class PostmarkDeliveryEventFactoryTest extends TestCase
19+
{
20+
public function testFactorySupportsInactiveRecipient()
21+
{
22+
$factory = new PostmarkDeliveryEventFactory();
23+
24+
$this->assertTrue($factory->supports(PostmarkDeliveryEvent::CODE_INACTIVE_RECIPIENT));
25+
}
26+
}

src/Symfony/Component/Mailer/Bridge/Postmark/Tests/Transport/PostmarkApiTransportTest.php

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,10 @@
1212
namespace Symfony\Component\Mailer\Bridge\Postmark\Tests\Transport;
1313

1414
use PHPUnit\Framework\TestCase;
15+
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
1516
use Symfony\Component\HttpClient\MockHttpClient;
1617
use Symfony\Component\HttpClient\Response\JsonMockResponse;
18+
use Symfony\Component\Mailer\Bridge\Postmark\Event\PostmarkDeliveryEvent;
1719
use Symfony\Component\Mailer\Bridge\Postmark\Transport\MessageStreamHeader;
1820
use Symfony\Component\Mailer\Bridge\Postmark\Transport\PostmarkApiTransport;
1921
use Symfony\Component\Mailer\Envelope;
@@ -119,6 +121,38 @@ public function testSendThrowsForErrorResponse()
119121
$transport->send($mail);
120122
}
121123

124+
public function testSendDeliveryEventIsDispatched()
125+
{
126+
$client = new MockHttpClient(static fn (string $method, string $url, array $options): ResponseInterface => new JsonMockResponse(['Message' => 'Inactive recipient', 'ErrorCode' => 406], [
127+
'http_code' => 422,
128+
]));
129+
130+
$mail = new Email();
131+
$mail->subject('Hello!')
132+
->to(new Address('saif.gmati@symfony.com', 'Saif Eddin'))
133+
->from(new Address('fabpot@symfony.com', 'Fabien'))
134+
->text('Hello There!');
135+
136+
$expectedEvent = (new PostmarkDeliveryEvent('Inactive recipient', 406))
137+
->setHeaders($mail->getHeaders());
138+
139+
$dispatcher = $this->createMock(EventDispatcherInterface::class);
140+
$dispatcher
141+
->method('dispatch')
142+
->willReturnCallback(function ($event) use ($expectedEvent) {
143+
if ($event instanceof PostmarkDeliveryEvent) {
144+
$this->assertEquals($event, $expectedEvent);
145+
}
146+
147+
return $event;
148+
});
149+
150+
$transport = new PostmarkApiTransport('KEY', $client, $dispatcher);
151+
$transport->setPort(8984);
152+
153+
$transport->send($mail);
154+
}
155+
122156
public function testTagAndMetadataAndMessageStreamHeaders()
123157
{
124158
$email = new Email();

src/Symfony/Component/Mailer/Bridge/Postmark/Transport/PostmarkApiTransport.php

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

1414
use Psr\EventDispatcher\EventDispatcherInterface;
1515
use Psr\Log\LoggerInterface;
16+
use Symfony\Component\Mailer\Bridge\Postmark\Event\PostmarkDeliveryEventFactory;
17+
use Symfony\Component\Mailer\Bridge\Postmark\Event\PostmarkEvents;
1618
use Symfony\Component\Mailer\Envelope;
1719
use Symfony\Component\Mailer\Exception\HttpTransportException;
1820
use Symfony\Component\Mailer\Exception\TransportException;
@@ -33,13 +35,16 @@ class PostmarkApiTransport extends AbstractApiTransport
3335
{
3436
private const HOST = 'api.postmarkapp.com';
3537

38+
private ?EventDispatcherInterface $dispatcher;
39+
3640
private string $key;
3741

3842
private ?string $messageStream = null;
3943

4044
public function __construct(string $key, HttpClientInterface $client = null, EventDispatcherInterface $dispatcher = null, LoggerInterface $logger = null)
4145
{
4246
$this->key = $key;
47+
$this->dispatcher = $dispatcher;
4348

4449
parent::__construct($client, $dispatcher, $logger);
4550
}
@@ -69,6 +74,18 @@ protected function doSendApi(SentMessage $sentMessage, Email $email, Envelope $e
6974
}
7075

7176
if (200 !== $statusCode) {
77+
$eventFactory = new PostmarkDeliveryEventFactory();
78+
79+
// Some delivery issues can be handled silently - route those through EventDispatcher
80+
if (null !== $this->dispatcher && $eventFactory->supports($result['ErrorCode'])) {
81+
$this->dispatcher->dispatch(
82+
$eventFactory->create($result['ErrorCode'], $result['Message'], $email),
83+
PostmarkEvents::DELIVERY,
84+
);
85+
86+
return $response;
87+
}
88+
7289
throw new HttpTransportException('Unable to send an email: '.$result['Message'].sprintf(' (code %d).', $result['ErrorCode']), $response);
7390
}
7491

src/Symfony/Component/Mailer/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.1
5+
---
6+
7+
* Dispatch Postmark's "406 - Inactive recipient" API error code as a `PostmarkDeliveryEvent` instead of throwing an exception
8+
49
7.0
510
---
611

0 commit comments

Comments
 (0)