diff --git a/src/Symfony/Component/Mailer/Bridge/Postmark/Event/PostmarkDeliveryEvent.php b/src/Symfony/Component/Mailer/Bridge/Postmark/Event/PostmarkDeliveryEvent.php new file mode 100644 index 0000000000000..e20335ad0f8b8 --- /dev/null +++ b/src/Symfony/Component/Mailer/Bridge/Postmark/Event/PostmarkDeliveryEvent.php @@ -0,0 +1,64 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Mailer\Bridge\Postmark\Event; + +use Symfony\Component\Mime\Header\Headers; + +class PostmarkDeliveryEvent +{ + public const CODE_INACTIVE_RECIPIENT = 406; + + private int $errorCode; + + private Headers $headers; + + private ?string $message; + + public function __construct(string $message, int $errorCode) + { + $this->message = $message; + $this->errorCode = $errorCode; + + $this->headers = new Headers(); + } + + public function getErrorCode(): int + { + return $this->errorCode; + } + + public function getHeaders(): Headers + { + return $this->headers; + } + + public function getMessage(): ?string + { + return $this->message; + } + + public function getMessageId(): ?string + { + if (!$this->headers->has('Message-ID')) { + return null; + } + + return $this->headers->get('Message-ID')->getBodyAsString(); + } + + public function setHeaders(Headers $headers): self + { + $this->headers = $headers; + + return $this; + } +} diff --git a/src/Symfony/Component/Mailer/Bridge/Postmark/Event/PostmarkDeliveryEventFactory.php b/src/Symfony/Component/Mailer/Bridge/Postmark/Event/PostmarkDeliveryEventFactory.php new file mode 100644 index 0000000000000..8bdb1807bcfb7 --- /dev/null +++ b/src/Symfony/Component/Mailer/Bridge/Postmark/Event/PostmarkDeliveryEventFactory.php @@ -0,0 +1,34 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Mailer\Bridge\Postmark\Event; + +use Symfony\Component\Mime\Email; + +class PostmarkDeliveryEventFactory +{ + public function create(int $errorCode, string $message, Email $email): PostmarkDeliveryEvent + { + if (!$this->supports($errorCode)) { + throw new \InvalidArgumentException(sprintf('Error code "%s" is not supported.', $errorCode)); + } + + return (new PostmarkDeliveryEvent($message, $errorCode)) + ->setHeaders($email->getHeaders()); + } + + public function supports(int $errorCode): bool + { + return \in_array($errorCode, [ + PostmarkDeliveryEvent::CODE_INACTIVE_RECIPIENT, + ]); + } +} diff --git a/src/Symfony/Component/Mailer/Bridge/Postmark/Event/PostmarkEvents.php b/src/Symfony/Component/Mailer/Bridge/Postmark/Event/PostmarkEvents.php new file mode 100644 index 0000000000000..68832e26873f9 --- /dev/null +++ b/src/Symfony/Component/Mailer/Bridge/Postmark/Event/PostmarkEvents.php @@ -0,0 +1,17 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Mailer\Bridge\Postmark\Event; + +class PostmarkEvents +{ + public const DELIVERY = 'postmark.delivery'; +} diff --git a/src/Symfony/Component/Mailer/Bridge/Postmark/Tests/Event/PostmarkDeliveryEventFactoryTest.php b/src/Symfony/Component/Mailer/Bridge/Postmark/Tests/Event/PostmarkDeliveryEventFactoryTest.php new file mode 100644 index 0000000000000..fc1f11e2cdecb --- /dev/null +++ b/src/Symfony/Component/Mailer/Bridge/Postmark/Tests/Event/PostmarkDeliveryEventFactoryTest.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Mailer\Bridge\Postmark\Tests\Event; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Mailer\Bridge\Postmark\Event\PostmarkDeliveryEvent; +use Symfony\Component\Mailer\Bridge\Postmark\Event\PostmarkDeliveryEventFactory; + +class PostmarkDeliveryEventFactoryTest extends TestCase +{ + public function testFactorySupportsInactiveRecipient() + { + $factory = new PostmarkDeliveryEventFactory(); + + $this->assertTrue($factory->supports(PostmarkDeliveryEvent::CODE_INACTIVE_RECIPIENT)); + } +} diff --git a/src/Symfony/Component/Mailer/Bridge/Postmark/Tests/Transport/PostmarkApiTransportTest.php b/src/Symfony/Component/Mailer/Bridge/Postmark/Tests/Transport/PostmarkApiTransportTest.php index 0b8b18836fc5e..bc4ad754ae7fb 100644 --- a/src/Symfony/Component/Mailer/Bridge/Postmark/Tests/Transport/PostmarkApiTransportTest.php +++ b/src/Symfony/Component/Mailer/Bridge/Postmark/Tests/Transport/PostmarkApiTransportTest.php @@ -12,8 +12,10 @@ namespace Symfony\Component\Mailer\Bridge\Postmark\Tests\Transport; use PHPUnit\Framework\TestCase; +use Symfony\Component\EventDispatcher\EventDispatcherInterface; use Symfony\Component\HttpClient\MockHttpClient; use Symfony\Component\HttpClient\Response\JsonMockResponse; +use Symfony\Component\Mailer\Bridge\Postmark\Event\PostmarkDeliveryEvent; use Symfony\Component\Mailer\Bridge\Postmark\Transport\MessageStreamHeader; use Symfony\Component\Mailer\Bridge\Postmark\Transport\PostmarkApiTransport; use Symfony\Component\Mailer\Envelope; @@ -119,6 +121,38 @@ public function testSendThrowsForErrorResponse() $transport->send($mail); } + public function testSendDeliveryEventIsDispatched() + { + $client = new MockHttpClient(static fn (string $method, string $url, array $options): ResponseInterface => new JsonMockResponse(['Message' => 'Inactive recipient', 'ErrorCode' => 406], [ + 'http_code' => 422, + ])); + + $mail = new Email(); + $mail->subject('Hello!') + ->to(new Address('saif.gmati@symfony.com', 'Saif Eddin')) + ->from(new Address('fabpot@symfony.com', 'Fabien')) + ->text('Hello There!'); + + $expectedEvent = (new PostmarkDeliveryEvent('Inactive recipient', 406)) + ->setHeaders($mail->getHeaders()); + + $dispatcher = $this->createMock(EventDispatcherInterface::class); + $dispatcher + ->method('dispatch') + ->willReturnCallback(function ($event) use ($expectedEvent) { + if ($event instanceof PostmarkDeliveryEvent) { + $this->assertEquals($event, $expectedEvent); + } + + return $event; + }); + + $transport = new PostmarkApiTransport('KEY', $client, $dispatcher); + $transport->setPort(8984); + + $transport->send($mail); + } + public function testTagAndMetadataAndMessageStreamHeaders() { $email = new Email(); diff --git a/src/Symfony/Component/Mailer/Bridge/Postmark/Transport/PostmarkApiTransport.php b/src/Symfony/Component/Mailer/Bridge/Postmark/Transport/PostmarkApiTransport.php index 22ed262924f6d..94cd6bdcd029b 100644 --- a/src/Symfony/Component/Mailer/Bridge/Postmark/Transport/PostmarkApiTransport.php +++ b/src/Symfony/Component/Mailer/Bridge/Postmark/Transport/PostmarkApiTransport.php @@ -13,6 +13,8 @@ use Psr\EventDispatcher\EventDispatcherInterface; use Psr\Log\LoggerInterface; +use Symfony\Component\Mailer\Bridge\Postmark\Event\PostmarkDeliveryEventFactory; +use Symfony\Component\Mailer\Bridge\Postmark\Event\PostmarkEvents; use Symfony\Component\Mailer\Envelope; use Symfony\Component\Mailer\Exception\HttpTransportException; use Symfony\Component\Mailer\Exception\TransportException; @@ -33,6 +35,8 @@ class PostmarkApiTransport extends AbstractApiTransport { private const HOST = 'api.postmarkapp.com'; + private ?EventDispatcherInterface $dispatcher; + private string $key; private ?string $messageStream = null; @@ -40,6 +44,7 @@ class PostmarkApiTransport extends AbstractApiTransport public function __construct(string $key, HttpClientInterface $client = null, EventDispatcherInterface $dispatcher = null, LoggerInterface $logger = null) { $this->key = $key; + $this->dispatcher = $dispatcher; parent::__construct($client, $dispatcher, $logger); } @@ -69,6 +74,18 @@ protected function doSendApi(SentMessage $sentMessage, Email $email, Envelope $e } if (200 !== $statusCode) { + $eventFactory = new PostmarkDeliveryEventFactory(); + + // Some delivery issues can be handled silently - route those through EventDispatcher + if (null !== $this->dispatcher && $eventFactory->supports($result['ErrorCode'])) { + $this->dispatcher->dispatch( + $eventFactory->create($result['ErrorCode'], $result['Message'], $email), + PostmarkEvents::DELIVERY, + ); + + return $response; + } + throw new HttpTransportException('Unable to send an email: '.$result['Message'].sprintf(' (code %d).', $result['ErrorCode']), $response); } diff --git a/src/Symfony/Component/Mailer/CHANGELOG.md b/src/Symfony/Component/Mailer/CHANGELOG.md index 0645df470b227..d58cf3d832353 100644 --- a/src/Symfony/Component/Mailer/CHANGELOG.md +++ b/src/Symfony/Component/Mailer/CHANGELOG.md @@ -1,6 +1,11 @@ CHANGELOG ========= +7.1 +--- + + * Dispatch Postmark's "406 - Inactive recipient" API error code as a `PostmarkDeliveryEvent` instead of throwing an exception + 7.0 ---