diff --git a/CHANGELOG.md b/CHANGELOG.md index 183b91e..df8c368 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +## [1.8.0] - 2024-04-19 + +- Support new functionality [Bulk Stream](https://help.mailtrap.io/article/113-sending-streams) + ## [1.7.4] - 2024-03-20 - Add PHP 8.3 support (GitHub Actions) diff --git a/README.md b/README.md index 29d2158..c3cacb4 100644 --- a/README.md +++ b/README.md @@ -112,6 +112,7 @@ try { You can find more examples [here](examples). * [General examples](examples/general) * [Sending examples](examples/sending) +* [Bulk Sending examples](examples/bulkSending) * [Sandbox examples](examples/sandbox) diff --git a/examples/bulkSending/emails.php b/examples/bulkSending/emails.php new file mode 100644 index 0000000..97ca514 --- /dev/null +++ b/examples/bulkSending/emails.php @@ -0,0 +1,113 @@ +from(new Address('example@YOUR-DOMAIN-HERE.com', 'Mailtrap Test')) // <--- you should use your domain here that you installed in the mailtrap.io admin area (otherwise you will get 401) + ->replyTo(new Address('reply@YOUR-DOMAIN-HERE.com')) + ->to(new Address('email@example.com', 'Jon')) + ->priority(Email::PRIORITY_HIGH) + ->cc('mailtrapqa@example.com') + ->addCc('staging@example.com') + ->bcc('mailtrapdev@example.com') + ->subject('Best practices of building HTML emails') + ->text('Hey! Learn the best practices of building HTML emails and play with ready-to-go templates. Mailtrap’s Guide on How to Build HTML Email is live on our blog') + ->html( + ' + +


Hey
+ Learn the best practices of building HTML emails and play with ready-to-go templates.

+

Mailtrap’s Guide on How to Build HTML Email is live on our blog

+ + + ' + ) + ->embed(fopen('https://mailtrap.io/wp-content/uploads/2021/04/mailtrap-new-logo.svg', 'r'), 'logo', 'image/svg+xml') + ->attachFromPath('README.md') + ; + + // Headers + $email->getHeaders() + ->addTextHeader('X-Message-Source', '1alf.com') + ->add(new UnstructuredHeader('X-Mailer', 'Mailtrap PHP Client')) + ; + + // Custom Variables + $email->getHeaders() + ->add(new CustomVariableHeader('user_id', '45982')) + ->add(new CustomVariableHeader('batch_id', 'PSJ-12')) + ; + + // Category (should be only one) + $email->getHeaders() + ->add(new CategoryHeader('Integration Test')) + ; + + $response = $mailtrap->bulkSending()->emails()->send($email); + + var_dump(ResponseHelper::toArray($response)); // body (array) +} catch (Exception $e) { + echo 'Caught exception: ', $e->getMessage(), "\n"; +} + + +/** + * Email Bulk Sending WITH TEMPLATE + * + * WARNING! If a template is provided, then subject, text, html, category and other params are forbidden. + * + * UUID of email template. Subject, text and html will be generated from template using optional template_variables. + * Optional template variables that will be used to generate actual subject, text and html from email template + */ +try { + // your API token from here https://mailtrap.io/api-tokens + $apiKey = getenv('MAILTRAP_API_KEY'); + $mailtrap = new MailtrapClient(new Config($apiKey)); + + $email = (new Email()) + ->from(new Address('example@YOUR-DOMAIN-HERE.com', 'Mailtrap Test')) // <--- you should use your domain here that you installed in the mailtrap.io admin area (otherwise you will get 401) + ->replyTo(new Address('reply@YOUR-DOMAIN-HERE.com')) + ->to(new Address('example@gmail.com', 'Jon')) + ; + + // Template UUID and Variables + $email->getHeaders() + ->add(new TemplateUuidHeader('bfa432fd-0000-0000-0000-8493da283a69')) + ->add(new TemplateVariableHeader('user_name', 'Jon Bush')) + ->add(new TemplateVariableHeader('next_step_link', 'https://mailtrap.io/')) + ->add(new TemplateVariableHeader('get_started_link', 'https://mailtrap.io/')) + ->add(new TemplateVariableHeader('onboarding_video_link', 'some_video_link')) + ; + + $response = $mailtrap->bulkSending()->emails()->send($email); + + var_dump(ResponseHelper::toArray($response)); // body (array) +} catch (Exception $e) { + echo 'Caught exception: ', $e->getMessage(), "\n"; +} diff --git a/src/Api/AbstractApi.php b/src/Api/AbstractApi.php index 2640c90..3768f8e 100644 --- a/src/Api/AbstractApi.php +++ b/src/Api/AbstractApi.php @@ -18,8 +18,9 @@ abstract class AbstractApi { public const DEFAULT_HOST = 'mailtrap.io'; - public const SENDMAIL_HOST = 'send.api.mailtrap.io'; + public const SENDMAIL_TRANSACTIONAL_HOST = 'send.api.mailtrap.io'; public const SENDMAIL_SANDBOX_HOST = 'sandbox.api.mailtrap.io'; + public const SENDMAIL_BULK_HOST = 'bulk.api.mailtrap.io'; protected ConfigInterface $config; protected ClientInterface $httpClient; diff --git a/src/Api/BulkSending/BulkSendingInterface.php b/src/Api/BulkSending/BulkSendingInterface.php new file mode 100644 index 0000000..51a5df1 --- /dev/null +++ b/src/Api/BulkSending/BulkSendingInterface.php @@ -0,0 +1,7 @@ +handleResponse( + $this->httpPost($this->getHost() . '/api/send', [], $this->getPayload($email)) + ); + } + + protected function getHost(): string + { + return $this->config->getHost() ?: self::SENDMAIL_BULK_HOST; + } +} diff --git a/src/Api/Sending/Emails.php b/src/Api/Sending/Emails.php index c6cd41f..0bba8c1 100644 --- a/src/Api/Sending/Emails.php +++ b/src/Api/Sending/Emails.php @@ -22,6 +22,6 @@ public function send(Email $email): ResponseInterface protected function getHost(): string { - return $this->config->getHost() ?: self::SENDMAIL_HOST; + return $this->config->getHost() ?: self::SENDMAIL_TRANSACTIONAL_HOST; } } diff --git a/src/Bridge/Laravel/README.md b/src/Bridge/Laravel/README.md index a916967..0d645d0 100644 --- a/src/Bridge/Laravel/README.md +++ b/src/Bridge/Laravel/README.md @@ -48,8 +48,20 @@ MAIL_MAILER="mailtrap" MAILTRAP_HOST="send.api.mailtrap.io" MAILTRAP_API_KEY="YOUR_API_KEY_HERE" ``` +### Bulk Sending +You need to set the API key to the `MAILTRAP_API_KEY` variable. + +More info about bulk sending -> https://help.mailtrap.io/article/113-sending-streams +```bash +MAIL_MAILER="mailtrap" + +MAILTRAP_HOST="bulk.api.mailtrap.io" +MAILTRAP_API_KEY="YOUR_API_KEY_HERE" +``` ### Sandbox You need to set the API key to the `MAILTRAP_API_KEY` variable and set your inboxId to the `MAILTRAP_INBOX_ID`. + +More info sandbox -> https://help.mailtrap.io/article/109-getting-started-with-mailtrap-email-testing ```bash MAIL_MAILER="mailtrap" diff --git a/src/Bridge/Symfony/README.md b/src/Bridge/Symfony/README.md index 729f6d9..caf8769 100644 --- a/src/Bridge/Symfony/README.md +++ b/src/Bridge/Symfony/README.md @@ -29,8 +29,19 @@ MAILER_DSN=mailtrap+api://YOUR_API_KEY_HERE@default # or MAILER_DSN=mailtrap+api://YOUR_API_KEY_HERE@send.api.mailtrap.io ``` + +### Bulk Sending +Add or change MAILER_DSN variable inside your `.env` file. Also, you need to change the `YOUR_API_KEY_HERE` placeholder. + +More info about bulk sending -> https://help.mailtrap.io/article/113-sending-streams +```bash +MAILER_DSN=mailtrap+api://YOUR_API_KEY_HERE@bulk.api.mailtrap.io +``` + ### Sandbox Add or change MAILER_DSN variable inside your `.env` file. Also, you need to change the `YOUR_API_KEY_HERE` placeholder and put correct `inboxId`. + +More info sandbox -> https://help.mailtrap.io/article/109-getting-started-with-mailtrap-email-testing ```bash MAILER_DSN=mailtrap+api://YOUR_API_KEY_HERE@sandbox.api.mailtrap.io?inboxId=1000001 ``` diff --git a/src/Bridge/Transport/MailtrapTransportFactory.php b/src/Bridge/Transport/MailtrapTransportFactory.php index 79622ae..1c970b2 100644 --- a/src/Bridge/Transport/MailtrapTransportFactory.php +++ b/src/Bridge/Transport/MailtrapTransportFactory.php @@ -8,8 +8,8 @@ use Mailtrap\Config; use Mailtrap\Exception\RuntimeException; use Mailtrap\MailtrapClient; +use Mailtrap\MailtrapClientInterface; use Mailtrap\MailtrapSandboxClient; -use Mailtrap\MailtrapSendingClient; use Symfony\Component\HttpClient\Psr18Client; use Symfony\Component\Mailer\Exception\UnsupportedSchemeException; use Symfony\Component\Mailer\Transport\AbstractTransportFactory; @@ -29,13 +29,11 @@ public function create(Dsn $dsn): TransportInterface $inboxId = !empty($dsn->getOption('inboxId')) ? (int) $dsn->getOption('inboxId') : null; $config = (new Config($this->getUser($dsn))) - ->setHost('default' === $dsn->getHost() ? AbstractApi::SENDMAIL_HOST : $dsn->getHost()) + ->setHost('default' === $dsn->getHost() ? AbstractApi::SENDMAIL_TRANSACTIONAL_HOST : $dsn->getHost()) ->setHttpClient(null === $this->client ? null : new Psr18Client($this->client)) ; - $mailtrapClient = stripos($config->getHost(), AbstractApi::SENDMAIL_HOST) !== false - ? (new MailtrapClient($config))->sending() - : (new MailtrapClient($config))->sandbox(); + $mailtrapClient = $this->getMailtrapClient($config); if ($mailtrapClient instanceof MailtrapSandboxClient && null === $inboxId) { throw new RuntimeException( 'You cannot send email to the sandbox with empty "inboxId" param. Example -> "MAILER_DSN=mailtrap+api://APIKEY@sandbox.api.mailtrap.io?inboxId=1234"' @@ -49,4 +47,24 @@ protected function getSupportedSchemes(): array { return ['mailtrap', 'mailtrap+api']; } + + private function getMailtrapClient(Config $config): MailtrapClientInterface + { + $layer = $this->determineLayerByHost($config->getHost()); + + return (new MailtrapClient($config))->{$layer}(); + } + + private function determineLayerByHost(string $host): string + { + if (stripos($host, AbstractApi::SENDMAIL_TRANSACTIONAL_HOST) !== false) { + return MailtrapClient::LAYER_TRANSACTIONAL_SENDING; + } + + if (stripos($host, AbstractApi::SENDMAIL_BULK_HOST) !== false) { + return MailtrapClient::LAYER_BULK_SENDING; + } + + return MailtrapClient::LAYER_SANDBOX; + } } diff --git a/src/MailtrapBulkSendingClient.php b/src/MailtrapBulkSendingClient.php new file mode 100644 index 0000000..f2ea025 --- /dev/null +++ b/src/MailtrapBulkSendingClient.php @@ -0,0 +1,19 @@ + Api\BulkSending\Emails::class, + ]; +} diff --git a/src/MailtrapClient.php b/src/MailtrapClient.php index 4f30bdb..dc3f7fd 100644 --- a/src/MailtrapClient.php +++ b/src/MailtrapClient.php @@ -7,17 +7,24 @@ /** * The main entry point to use all possible API layers * - * @method MailtrapGeneralClient general - * @method MailtrapSandboxClient sandbox - * @method MailtrapSendingClient sending + * @method MailtrapGeneralClient general + * @method MailtrapSandboxClient sandbox + * @method MailtrapSendingClient sending + * @method MailtrapBulkSendingClient bulkSending * * Class MailtrapClient */ class MailtrapClient extends AbstractMailtrapClient { + public const LAYER_GENERAL = 'general'; + public const LAYER_SANDBOX = 'sandbox'; + public const LAYER_TRANSACTIONAL_SENDING = 'sending'; + public const LAYER_BULK_SENDING = 'bulkSending'; + public const API_MAPPING = [ - 'general' => MailtrapGeneralClient::class, - 'sandbox' => MailtrapSandboxClient::class, - 'sending' => MailtrapSendingClient::class, + self::LAYER_GENERAL => MailtrapGeneralClient::class, + self::LAYER_SANDBOX => MailtrapSandboxClient::class, + self::LAYER_TRANSACTIONAL_SENDING => MailtrapSendingClient::class, + self::LAYER_BULK_SENDING => MailtrapBulkSendingClient::class, ]; } diff --git a/tests/Api/AbstractEmailsTest.php b/tests/Api/AbstractEmailsTest.php new file mode 100644 index 0000000..a8aa5e8 --- /dev/null +++ b/tests/Api/AbstractEmailsTest.php @@ -0,0 +1,375 @@ + true, + "message_ids" => [ + "0c7fd939-02cf-11ed-88c2-0a58a9feac02" + ] + ]; + + $email = new Email(); + $email->from(new Address('foo@example.com', 'Ms. Foo Bar')) + ->replyTo(new Address('reply@example.com')) + ->to(new Address('bar@example.com', 'Mr. Recipient')) + ->priority(Email::PRIORITY_HIGH) + ->bcc('baz@example.com') + ->subject('Best practices of building HTML emails') + ->text('Some text') + ->html('

Some text

') + ; + $email->getHeaders() + ->addTextHeader('X-Message-Source', 'dev.mydomain.com'); + + $this->email + ->expects($this->once()) + ->method('httpPost') + ->with($this->getHost() . '/api/send', [], [ + 'from' => [ + 'email' => 'foo@example.com', + 'name' => 'Ms. Foo Bar', + ], + 'to' => [[ + 'email' => 'bar@example.com', + 'name' => 'Mr. Recipient', + ]], + 'subject' => 'Best practices of building HTML emails', + 'bcc' => [[ + 'email' => 'baz@example.com' + ]], + 'text' => 'Some text', + 'html' => '

Some text

', + 'headers' => [ + 'X-Message-Source' => 'dev.mydomain.com', + 'Reply-To' => 'reply@example.com', + 'X-Priority' => '2 (High)', + ] + ]) + ->willReturn(new Response(200, ['Content-Type' => 'application/json'], json_encode($expectedData))); + + $response = $this->email->send($email); + $responseData = ResponseHelper::toArray($response); + + $this->assertInstanceOf(Response::class, $response); + $this->assertCount(2, $responseData); + $this->assertArrayHasKey('success', $responseData); + $this->assertArrayHasKey('message_ids', $responseData); + } + + public function testInValidSend(): void + { + $expectedData = [ + "errors" => [ + "'to' address is required", + "'subject' is required", + ] + ]; + + $this->expectException(HttpClientException::class); + $this->expectExceptionMessage( + "Bad request. Fix errors listed in response before retrying. Errors: 'to' address is required. 'subject' is required." + ); + + $email = new Email(); + $email->from(new Address('foo@example.com', 'Ms. Foo Bar')) + ->text('Some text') + ->html('

Some text

') + ; + $email->getHeaders() + ->addTextHeader('X-Message-Source', 'dev.mydomain.com'); + + $this->email + ->expects($this->once()) + ->method('httpPost') + ->with($this->getHost() . '/api/send', [], [ + 'from' => [ + 'email' => 'foo@example.com', + 'name' => 'Ms. Foo Bar', + ], + 'to' => [], + 'text' => 'Some text', + 'html' => '

Some text

', + 'headers' => [ + 'X-Message-Source' => 'dev.mydomain.com' + ] + ]) + ->willReturn(new Response(400, ['Content-Type' => 'application/json'], json_encode($expectedData))); + + $this->email->send($email); + } + + public function testValidSendTemplate(): void + { + $expectedData = [ + "success" => true, + "message_ids" => [ + "0c7fd939-02cf-11ed-88c2-0a58a9feac02" + ] + ]; + + $email = (new Email()) + ->from(new Address('foo@example.com', 'Ms. Foo Bar')) + ->to(new Address('bar@example.com', 'Mr. Recipient')) + ; + + $email->getHeaders() + ->add(new TemplateUuidHeader('bfa432fd-0000-0000-0000-8493da283a69')) + ->add(new TemplateVariableHeader('user_name', 'Jon Bush')) + ->add(new TemplateVariableHeader('next_step_link', 'https://mailtrap.io/')) + ->add(new TemplateVariableHeader('get_started_link', 'https://mailtrap.io/')) + ->add(new TemplateVariableHeader('onboarding_video_link', 'some_video_link')) + ; + + $this->email + ->expects($this->once()) + ->method('httpPost') + ->with($this->getHost() . '/api/send', [], [ + 'from' => [ + 'email' => 'foo@example.com', + 'name' => 'Ms. Foo Bar', + ], + 'to' => [[ + 'email' => 'bar@example.com', + 'name' => 'Mr. Recipient', + ]], + 'template_uuid' => 'bfa432fd-0000-0000-0000-8493da283a69', + 'template_variables' => [ + 'user_name' => 'Jon Bush', + 'next_step_link' => 'https://mailtrap.io/', + 'get_started_link' => 'https://mailtrap.io/', + 'onboarding_video_link' => 'some_video_link', + ] + ]) + ->willReturn(new Response(200, ['Content-Type' => 'application/json'], json_encode($expectedData))); + + $response = $this->email->send($email); + $responseData = ResponseHelper::toArray($response); + + $this->assertInstanceOf(Response::class, $response); + $this->assertCount(2, $responseData); + $this->assertArrayHasKey('success', $responseData); + $this->assertArrayHasKey('message_ids', $responseData); + } + + public function testAttachments(): void + { + $email = (new Email()) + ->from(new Address('foo@example.com', 'Ms. Foo Bar')) + ->attach('fake_body', 'fakeFile.jpg', 'image/jpg') + ; + + $method = new \ReflectionMethod(Emails::class, 'getPayload'); + $method->setAccessible(true); + $payload = $method->invoke(new Emails($this->getConfigMock()), $email); + + $this->assertArrayHasKey('attachments', $payload); + $this->assertArrayHasKey('content', $payload['attachments'][0]); + $this->assertArrayHasKey('type', $payload['attachments'][0]); + $this->assertArrayHasKey('filename', $payload['attachments'][0]); + $this->assertEquals('ZmFrZV9ib2R5', $payload['attachments'][0]['content']); + $this->assertEquals('image/jpg', $payload['attachments'][0]['type']); + $this->assertEquals('fakeFile.jpg', $payload['attachments'][0]['filename']); + } + + /** + * @dataProvider validHeadersDataProvider + */ + public function testHeaders($name, $value): void + { + $email = (new Email())->from(new Address('foo@example.com', 'Ms. Foo Bar')); + $email->getHeaders()->addTextHeader($name, $value); + + $method = new \ReflectionMethod(Emails::class, 'getPayload'); + $method->setAccessible(true); + $payload = $method->invoke(new Emails($this->getConfigMock()), $email); + + $this->assertArrayHasKey('headers', $payload); + $this->assertArrayHasKey($name, $payload['headers']); + $this->assertEquals($value, $payload['headers'][$name]); + } + + /** + * @dataProvider validCustomVariablesDataProvider + */ + public function testCustomVariables($name, $value): void + { + $email = (new Email())->from(new Address('foo@example.com', 'Ms. Foo Bar')); + $email->getHeaders() + ->add(new CustomVariableHeader($name, $value)); + + $method = new \ReflectionMethod(Emails::class, 'getPayload'); + $method->setAccessible(true); + $payload = $method->invoke(new Emails($this->getConfigMock()), $email); + + $this->assertArrayHasKey(CustomVariableHeader::VAR_NAME, $payload); + $this->assertArrayHasKey($name, $payload[CustomVariableHeader::VAR_NAME]); + $this->assertEquals($value, $payload[CustomVariableHeader::VAR_NAME][$name]); + } + + /** + * @dataProvider validEmailCategoryDataProvider + */ + public function testEmailCategory($value): void + { + $email = (new Email())->from(new Address('foo@example.com', 'Ms. Foo Bar')); + $email->getHeaders() + ->add(new CategoryHeader($value)); + + $method = new \ReflectionMethod(Emails::class, 'getPayload'); + $method->setAccessible(true); + $payload = $method->invoke(new Emails($this->getConfigMock()), $email); + + $this->assertArrayHasKey(CategoryHeader::VAR_NAME, $payload); + $this->assertEquals($value, $payload[CategoryHeader::VAR_NAME]); + } + + public function testInvalidCountOfEmailCategory(): void + { + $email = (new Email())->from(new Address('foo@example.com', 'Ms. Foo Bar')); + $email->getHeaders() + ->add(new CategoryHeader('category 1')) + ->add(new CategoryHeader('category 2')) + ; + + $this->expectExceptionObject( + new RuntimeException( + sprintf('Too many "%s" instances present in the email headers. Mailtrap does not accept more than 1 category in the email.', CategoryHeader::class) + ) + ); + + $method = new \ReflectionMethod(Emails::class, 'getPayload'); + $method->setAccessible(true); + $method->invoke(new Emails($this->getConfigMock()), $email); + } + + /** + * @dataProvider validTemplateUuidDataProvider + */ + public function testTemplateUuid($value): void + { + $email = (new Email())->from(new Address('foo@example.com', 'Ms. Foo Bar')); + $email->getHeaders() + ->add(new TemplateUuidHeader($value)); + + $method = new \ReflectionMethod(Emails::class, 'getPayload'); + $method->setAccessible(true); + $payload = $method->invoke(new Emails($this->getConfigMock()), $email); + + $this->assertArrayHasKey(TemplateUuidHeader::VAR_NAME, $payload); + $this->assertEquals($value, $payload[TemplateUuidHeader::VAR_NAME]); + } + + public function testInvalidCountOfTemplateUuid(): void + { + $email = (new Email())->from(new Address('foo@example.com', 'Ms. Foo Bar')); + $email->getHeaders() + ->add(new TemplateUuidHeader('11111111-0000-0000-0000-8493da283a69')) + ->add(new TemplateUuidHeader('22222222-0000-0000-0000-8493da283a69')) + ; + + $this->expectExceptionObject( + new RuntimeException( + sprintf('Too many "%s" instances present in the email headers. Mailtrap does not accept more than 1 template UUID in the email.', TemplateUuidHeader::class) + ) + ); + + $method = new \ReflectionMethod(Emails::class, 'getPayload'); + $method->setAccessible(true); + $method->invoke(new Emails($this->getConfigMock()), $email); + } + + /** + * @dataProvider validTemplateVariablesDataProvider + */ + public function testTemplateVariables($name, $value): void + { + $email = (new Email())->from(new Address('foo@example.com', 'Ms. Foo Bar')); + $email->getHeaders() + ->add(new TemplateVariableHeader($name, $value)); + + $method = new \ReflectionMethod(Emails::class, 'getPayload'); + $method->setAccessible(true); + $payload = $method->invoke(new Emails($this->getConfigMock()), $email); + + $this->assertArrayHasKey(TemplateVariableHeader::VAR_NAME, $payload); + $this->assertArrayHasKey($name, $payload[TemplateVariableHeader::VAR_NAME]); + $this->assertEquals($value, $payload[TemplateVariableHeader::VAR_NAME][$name]); + } + + // + + public function validHeadersDataProvider(): array + { + return [ + ['X-Message-Source', 'dev.mydomain.com'], + ['X-Mailer', 'Mailtrap PHP Client'], + ]; + } + + public function validCustomVariablesDataProvider(): array + { + return [ + ['user_id', '45982'], + ['batch_id', 'PSJ-12'], + ]; + } + + public function validEmailCategoryDataProvider(): array + { + return [ + ['Integration Test'], + ['Some other category'], + ]; + } + + public function validTemplateUuidDataProvider(): array + { + return [ + ['11111111-0000-0000-0000-8493da283a69'], + ['22222222-0000-0000-0000-8493da283a69'], + ]; + } + + public function validTemplateVariablesDataProvider(): array + { + return [ + ['user_name', 'Jon Bush'], + ['next_step_link', 'https://mailtrap.io/'], + ['onboarding_video_link', 'some_video_link'], + ]; + } + + // +} diff --git a/tests/Api/BulkSending/BulkEmailsTest.php b/tests/Api/BulkSending/BulkEmailsTest.php new file mode 100644 index 0000000..6458b29 --- /dev/null +++ b/tests/Api/BulkSending/BulkEmailsTest.php @@ -0,0 +1,40 @@ +email = $this->getMockBuilder(Emails::class) + ->onlyMethods(['httpPost']) + ->setConstructorArgs([$this->getConfigMock()]) + ->getMock() + ; + } + + protected function tearDown(): void + { + $this->email = null; + + parent::tearDown(); + } + + protected function getHost(): string + { + return AbstractApi::SENDMAIL_BULK_HOST; + } +} diff --git a/tests/Api/Sending/EmailsTest.php b/tests/Api/Sending/EmailsTest.php index 352e380..9b36390 100644 --- a/tests/Api/Sending/EmailsTest.php +++ b/tests/Api/Sending/EmailsTest.php @@ -6,30 +6,15 @@ use Mailtrap\Api\AbstractApi; use Mailtrap\Api\Sending\Emails; -use Mailtrap\EmailHeader\CategoryHeader; -use Mailtrap\EmailHeader\CustomVariableHeader; -use Mailtrap\EmailHeader\Template\TemplateUuidHeader; -use Mailtrap\EmailHeader\Template\TemplateVariableHeader; -use Mailtrap\Exception\HttpClientException; -use Mailtrap\Exception\RuntimeException; -use Mailtrap\Helper\ResponseHelper; -use Mailtrap\Tests\MailtrapTestCase; -use Nyholm\Psr7\Response; -use Symfony\Component\Mime\Address; -use Symfony\Component\Mime\Email; +use Mailtrap\Tests\Api\AbstractEmailsTest; /** * @covers Emails * * Class EmailsTest */ -final class EmailsTest extends MailtrapTestCase +final class EmailsTest extends AbstractEmailsTest { - /** - * @var Emails - */ - private $email; - protected function setUp(): void { parent::setUp(); @@ -48,345 +33,8 @@ protected function tearDown(): void parent::tearDown(); } - public function testValidSend(): void - { - $expectedData = [ - "success" => true, - "message_ids" => [ - "0c7fd939-02cf-11ed-88c2-0a58a9feac02" - ] - ]; - - $email = new Email(); - $email->from(new Address('foo@example.com', 'Ms. Foo Bar')) - ->replyTo(new Address('reply@example.com')) - ->to(new Address('bar@example.com', 'Mr. Recipient')) - ->priority(Email::PRIORITY_HIGH) - ->bcc('baz@example.com') - ->subject('Best practices of building HTML emails') - ->text('Some text') - ->html('

Some text

') - ; - $email->getHeaders() - ->addTextHeader('X-Message-Source', 'dev.mydomain.com'); - - $this->email - ->expects($this->once()) - ->method('httpPost') - ->with(AbstractApi::SENDMAIL_HOST . '/api/send', [], [ - 'from' => [ - 'email' => 'foo@example.com', - 'name' => 'Ms. Foo Bar', - ], - 'to' => [[ - 'email' => 'bar@example.com', - 'name' => 'Mr. Recipient', - ]], - 'subject' => 'Best practices of building HTML emails', - 'bcc' => [[ - 'email' => 'baz@example.com' - ]], - 'text' => 'Some text', - 'html' => '

Some text

', - 'headers' => [ - 'X-Message-Source' => 'dev.mydomain.com', - 'Reply-To' => 'reply@example.com', - 'X-Priority' => '2 (High)', - ] - ]) - ->willReturn(new Response(200, ['Content-Type' => 'application/json'], json_encode($expectedData))); - - $response = $this->email->send($email); - $responseData = ResponseHelper::toArray($response); - - $this->assertInstanceOf(Response::class, $response); - $this->assertCount(2, $responseData); - $this->assertArrayHasKey('success', $responseData); - $this->assertArrayHasKey('message_ids', $responseData); - } - - public function testInValidSend(): void - { - $expectedData = [ - "errors" => [ - "'to' address is required", - "'subject' is required", - ] - ]; - - $this->expectException(HttpClientException::class); - $this->expectExceptionMessage( - "Bad request. Fix errors listed in response before retrying. Errors: 'to' address is required. 'subject' is required." - ); - - $email = new Email(); - $email->from(new Address('foo@example.com', 'Ms. Foo Bar')) - ->text('Some text') - ->html('

Some text

') - ; - $email->getHeaders() - ->addTextHeader('X-Message-Source', 'dev.mydomain.com'); - - $this->email - ->expects($this->once()) - ->method('httpPost') - ->with(AbstractApi::SENDMAIL_HOST . '/api/send', [], [ - 'from' => [ - 'email' => 'foo@example.com', - 'name' => 'Ms. Foo Bar', - ], - 'to' => [], - 'text' => 'Some text', - 'html' => '

Some text

', - 'headers' => [ - 'X-Message-Source' => 'dev.mydomain.com' - ] - ]) - ->willReturn(new Response(400, ['Content-Type' => 'application/json'], json_encode($expectedData))); - - $this->email->send($email); - } - - public function testValidSendTemplate(): void - { - $expectedData = [ - "success" => true, - "message_ids" => [ - "0c7fd939-02cf-11ed-88c2-0a58a9feac02" - ] - ]; - - $email = (new Email()) - ->from(new Address('foo@example.com', 'Ms. Foo Bar')) - ->to(new Address('bar@example.com', 'Mr. Recipient')) - ; - - $email->getHeaders() - ->add(new TemplateUuidHeader('bfa432fd-0000-0000-0000-8493da283a69')) - ->add(new TemplateVariableHeader('user_name', 'Jon Bush')) - ->add(new TemplateVariableHeader('next_step_link', 'https://mailtrap.io/')) - ->add(new TemplateVariableHeader('get_started_link', 'https://mailtrap.io/')) - ->add(new TemplateVariableHeader('onboarding_video_link', 'some_video_link')) - ; - - $this->email - ->expects($this->once()) - ->method('httpPost') - ->with(AbstractApi::SENDMAIL_HOST . '/api/send', [], [ - 'from' => [ - 'email' => 'foo@example.com', - 'name' => 'Ms. Foo Bar', - ], - 'to' => [[ - 'email' => 'bar@example.com', - 'name' => 'Mr. Recipient', - ]], - 'template_uuid' => 'bfa432fd-0000-0000-0000-8493da283a69', - 'template_variables' => [ - 'user_name' => 'Jon Bush', - 'next_step_link' => 'https://mailtrap.io/', - 'get_started_link' => 'https://mailtrap.io/', - 'onboarding_video_link' => 'some_video_link', - ] - ]) - ->willReturn(new Response(200, ['Content-Type' => 'application/json'], json_encode($expectedData))); - - $response = $this->email->send($email); - $responseData = ResponseHelper::toArray($response); - - $this->assertInstanceOf(Response::class, $response); - $this->assertCount(2, $responseData); - $this->assertArrayHasKey('success', $responseData); - $this->assertArrayHasKey('message_ids', $responseData); - } - - public function testAttachments(): void - { - $email = (new Email()) - ->from(new Address('foo@example.com', 'Ms. Foo Bar')) - ->attach('fake_body', 'fakeFile.jpg', 'image/jpg') - ; - - $method = new \ReflectionMethod(Emails::class, 'getPayload'); - $method->setAccessible(true); - $payload = $method->invoke(new Emails($this->getConfigMock()), $email); - - $this->assertArrayHasKey('attachments', $payload); - $this->assertArrayHasKey('content', $payload['attachments'][0]); - $this->assertArrayHasKey('type', $payload['attachments'][0]); - $this->assertArrayHasKey('filename', $payload['attachments'][0]); - $this->assertEquals('ZmFrZV9ib2R5', $payload['attachments'][0]['content']); - $this->assertEquals('image/jpg', $payload['attachments'][0]['type']); - $this->assertEquals('fakeFile.jpg', $payload['attachments'][0]['filename']); - } - - /** - * @dataProvider validHeadersDataProvider - */ - public function testHeaders($name, $value): void - { - $email = (new Email())->from(new Address('foo@example.com', 'Ms. Foo Bar')); - $email->getHeaders()->addTextHeader($name, $value); - - $method = new \ReflectionMethod(Emails::class, 'getPayload'); - $method->setAccessible(true); - $payload = $method->invoke(new Emails($this->getConfigMock()), $email); - - $this->assertArrayHasKey('headers', $payload); - $this->assertArrayHasKey($name, $payload['headers']); - $this->assertEquals($value, $payload['headers'][$name]); - } - - /** - * @dataProvider validCustomVariablesDataProvider - */ - public function testCustomVariables($name, $value): void - { - $email = (new Email())->from(new Address('foo@example.com', 'Ms. Foo Bar')); - $email->getHeaders() - ->add(new CustomVariableHeader($name, $value)); - - $method = new \ReflectionMethod(Emails::class, 'getPayload'); - $method->setAccessible(true); - $payload = $method->invoke(new Emails($this->getConfigMock()), $email); - - $this->assertArrayHasKey(CustomVariableHeader::VAR_NAME, $payload); - $this->assertArrayHasKey($name, $payload[CustomVariableHeader::VAR_NAME]); - $this->assertEquals($value, $payload[CustomVariableHeader::VAR_NAME][$name]); - } - - /** - * @dataProvider validEmailCategoryDataProvider - */ - public function testEmailCategory($value): void - { - $email = (new Email())->from(new Address('foo@example.com', 'Ms. Foo Bar')); - $email->getHeaders() - ->add(new CategoryHeader($value)); - - $method = new \ReflectionMethod(Emails::class, 'getPayload'); - $method->setAccessible(true); - $payload = $method->invoke(new Emails($this->getConfigMock()), $email); - - $this->assertArrayHasKey(CategoryHeader::VAR_NAME, $payload); - $this->assertEquals($value, $payload[CategoryHeader::VAR_NAME]); - } - - public function testInvalidCountOfEmailCategory(): void - { - $email = (new Email())->from(new Address('foo@example.com', 'Ms. Foo Bar')); - $email->getHeaders() - ->add(new CategoryHeader('category 1')) - ->add(new CategoryHeader('category 2')) - ; - - $this->expectExceptionObject( - new RuntimeException( - sprintf('Too many "%s" instances present in the email headers. Mailtrap does not accept more than 1 category in the email.', CategoryHeader::class) - ) - ); - - $method = new \ReflectionMethod(Emails::class, 'getPayload'); - $method->setAccessible(true); - $method->invoke(new Emails($this->getConfigMock()), $email); - } - - /** - * @dataProvider validTemplateUuidDataProvider - */ - public function testTemplateUuid($value): void - { - $email = (new Email())->from(new Address('foo@example.com', 'Ms. Foo Bar')); - $email->getHeaders() - ->add(new TemplateUuidHeader($value)); - - $method = new \ReflectionMethod(Emails::class, 'getPayload'); - $method->setAccessible(true); - $payload = $method->invoke(new Emails($this->getConfigMock()), $email); - - $this->assertArrayHasKey(TemplateUuidHeader::VAR_NAME, $payload); - $this->assertEquals($value, $payload[TemplateUuidHeader::VAR_NAME]); - } - - public function testInvalidCountOfTemplateUuid(): void - { - $email = (new Email())->from(new Address('foo@example.com', 'Ms. Foo Bar')); - $email->getHeaders() - ->add(new TemplateUuidHeader('11111111-0000-0000-0000-8493da283a69')) - ->add(new TemplateUuidHeader('22222222-0000-0000-0000-8493da283a69')) - ; - - $this->expectExceptionObject( - new RuntimeException( - sprintf('Too many "%s" instances present in the email headers. Mailtrap does not accept more than 1 template UUID in the email.', TemplateUuidHeader::class) - ) - ); - - $method = new \ReflectionMethod(Emails::class, 'getPayload'); - $method->setAccessible(true); - $method->invoke(new Emails($this->getConfigMock()), $email); - } - - /** - * @dataProvider validTemplateVariablesDataProvider - */ - public function testTemplateVariables($name, $value): void - { - $email = (new Email())->from(new Address('foo@example.com', 'Ms. Foo Bar')); - $email->getHeaders() - ->add(new TemplateVariableHeader($name, $value)); - - $method = new \ReflectionMethod(Emails::class, 'getPayload'); - $method->setAccessible(true); - $payload = $method->invoke(new Emails($this->getConfigMock()), $email); - - $this->assertArrayHasKey(TemplateVariableHeader::VAR_NAME, $payload); - $this->assertArrayHasKey($name, $payload[TemplateVariableHeader::VAR_NAME]); - $this->assertEquals($value, $payload[TemplateVariableHeader::VAR_NAME][$name]); - } - - // - - public function validHeadersDataProvider(): array - { - return [ - ['X-Message-Source', 'dev.mydomain.com'], - ['X-Mailer', 'Mailtrap PHP Client'], - ]; - } - - public function validCustomVariablesDataProvider(): array + protected function getHost(): string { - return [ - ['user_id', '45982'], - ['batch_id', 'PSJ-12'], - ]; + return AbstractApi::SENDMAIL_TRANSACTIONAL_HOST; } - - public function validEmailCategoryDataProvider(): array - { - return [ - ['Integration Test'], - ['Some other category'], - ]; - } - - public function validTemplateUuidDataProvider(): array - { - return [ - ['11111111-0000-0000-0000-8493da283a69'], - ['22222222-0000-0000-0000-8493da283a69'], - ]; - } - - public function validTemplateVariablesDataProvider(): array - { - return [ - ['user_name', 'Jon Bush'], - ['next_step_link', 'https://mailtrap.io/'], - ['onboarding_video_link', 'some_video_link'], - ]; - } - - // } diff --git a/tests/Bridge/Transport/MailtrapApiTransportTest.php b/tests/Bridge/Transport/MailtrapApiTransportTest.php index a1ad2fc..47f41c8 100644 --- a/tests/Bridge/Transport/MailtrapApiTransportTest.php +++ b/tests/Bridge/Transport/MailtrapApiTransportTest.php @@ -41,7 +41,7 @@ public static function getTransportData(): array [ new MailtrapApiTransport( new MailtrapSendingClient( - (new Config('key'))->setHost(AbstractApi::SENDMAIL_HOST) + (new Config('key'))->setHost(AbstractApi::SENDMAIL_TRANSACTIONAL_HOST) ) ), 'mailtrap+api://send.api.mailtrap.io', diff --git a/tests/Bridge/Transport/MailtrapTransportFactoryTest.php b/tests/Bridge/Transport/MailtrapTransportFactoryTest.php index dd73515..b88d403 100644 --- a/tests/Bridge/Transport/MailtrapTransportFactoryTest.php +++ b/tests/Bridge/Transport/MailtrapTransportFactoryTest.php @@ -60,7 +60,7 @@ public function createProvider(): iterable (new MailtrapClient( (new Config(self::USER)) ->setHttpClient(new Psr18Client($this->getClient())) - ->setHost(AbstractApi::SENDMAIL_HOST) + ->setHost(AbstractApi::SENDMAIL_TRANSACTIONAL_HOST) ))->sending(), null, $dispatcher, @@ -69,12 +69,12 @@ public function createProvider(): iterable ]; yield [ - new Dsn('mailtrap', AbstractApi::SENDMAIL_HOST, self::USER), + new Dsn('mailtrap', AbstractApi::SENDMAIL_TRANSACTIONAL_HOST, self::USER), new MailtrapApiTransport( (new MailtrapClient( (new Config(self::USER)) ->setHttpClient(new Psr18Client($this->getClient())) - ->setHost(AbstractApi::SENDMAIL_HOST) + ->setHost(AbstractApi::SENDMAIL_TRANSACTIONAL_HOST) ))->sending(), null, $dispatcher, @@ -83,6 +83,20 @@ public function createProvider(): iterable ]; // sandbox + yield [ + new Dsn('mailtrap+api', AbstractApi::SENDMAIL_SANDBOX_HOST, self::USER, null, null, ['inboxId' => 1234]), + new MailtrapApiTransport( + (new MailtrapClient( + (new Config(self::USER)) + ->setHttpClient(new Psr18Client($this->getClient())) + ->setHost(AbstractApi::SENDMAIL_SANDBOX_HOST) + ))->sandbox(), + 1234, + $dispatcher, + $logger + ), + ]; + yield [ new Dsn('mailtrap', AbstractApi::SENDMAIL_SANDBOX_HOST, self::USER, null, null, ['inboxId' => 1234]), new MailtrapApiTransport( @@ -96,6 +110,35 @@ public function createProvider(): iterable $logger ), ]; + + // bulk sending + yield [ + new Dsn('mailtrap+api', AbstractApi::SENDMAIL_BULK_HOST, self::USER), + new MailtrapApiTransport( + (new MailtrapClient( + (new Config(self::USER)) + ->setHttpClient(new Psr18Client($this->getClient())) + ->setHost(AbstractApi::SENDMAIL_BULK_HOST) + ))->bulkSending(), + null, + $dispatcher, + $logger + ), + ]; + + yield [ + new Dsn('mailtrap', AbstractApi::SENDMAIL_BULK_HOST, self::USER), + new MailtrapApiTransport( + (new MailtrapClient( + (new Config(self::USER)) + ->setHttpClient(new Psr18Client($this->getClient())) + ->setHost(AbstractApi::SENDMAIL_BULK_HOST) + ))->bulkSending(), + null, + $dispatcher, + $logger + ), + ]; } public function unsupportedSchemeProvider(): iterable diff --git a/tests/MailtrapBulkSendingClientTest.php b/tests/MailtrapBulkSendingClientTest.php new file mode 100644 index 0000000..e9d566c --- /dev/null +++ b/tests/MailtrapBulkSendingClientTest.php @@ -0,0 +1,33 @@ +getConfigMock())]; + } + } +}