├── .github ├── pull_request_template.md └── workflows │ ├── ci-phpunit.yml │ └── ci-psalm.yaml ├── CHANGELOG.md ├── CODE_OF_CONDUCT.md ├── LICENSE.txt ├── README.md ├── UPGRADE-2.0.md ├── UPGRADE-3.0.md ├── composer.json ├── examples ├── general │ ├── accounts.php │ ├── contacts.php │ ├── permissions.php │ └── users.php ├── sending │ └── emails.php └── testing │ ├── attachments.php │ ├── emails.php │ ├── inboxes.php │ ├── messages.php │ └── projects.php ├── psalm.xml ├── src ├── AbstractMailtrapClient.php ├── Api │ ├── AbstractApi.php │ ├── AbstractEmails.php │ ├── BulkSending │ │ ├── BulkSendingInterface.php │ │ └── Emails.php │ ├── EmailsSendApiInterface.php │ ├── General │ │ ├── Account.php │ │ ├── Contact.php │ │ ├── GeneralInterface.php │ │ ├── Permission.php │ │ └── User.php │ ├── Sandbox │ │ ├── Attachment.php │ │ ├── Emails.php │ │ ├── Inbox.php │ │ ├── Message.php │ │ ├── Project.php │ │ └── SandboxInterface.php │ └── Sending │ │ ├── Emails.php │ │ └── SendingInterface.php ├── Bridge │ ├── Laravel │ │ ├── MailtrapSdkProvider.php │ │ ├── README.md │ │ └── config │ │ │ └── mailtrap-sdk.php │ ├── Symfony │ │ └── README.md │ └── Transport │ │ ├── MailtrapSdkTransport.php │ │ └── MailtrapSdkTransportFactory.php ├── Config.php ├── ConfigInterface.php ├── DTO │ └── Request │ │ ├── Contact │ │ ├── ContactInterface.php │ │ ├── CreateContact.php │ │ └── UpdateContact.php │ │ ├── Inbox.php │ │ ├── Permission │ │ ├── CreateOrUpdatePermission.php │ │ ├── DestroyPermission.php │ │ ├── PermissionInterface.php │ │ └── Permissions.php │ │ └── RequestInterface.php ├── EmailHeader │ ├── CategoryHeader.php │ ├── CustomHeaderInterface.php │ ├── CustomVariableHeader.php │ └── Template │ │ ├── TemplateUuidHeader.php │ │ └── TemplateVariableHeader.php ├── EmailsSendMailtrapClientInterface.php ├── Exception │ ├── BadMethodCallException.php │ ├── HttpClientException.php │ ├── HttpException.php │ ├── HttpServerException.php │ ├── InvalidArgumentException.php │ ├── InvalidTypeException.php │ ├── LogicException.php │ ├── MailtrapExceptionInterface.php │ ├── RuntimeException.php │ └── Transport │ │ └── UnsupportedHostException.php ├── Helper │ └── ResponseHelper.php ├── HttpClient │ ├── HttpClientBuilder.php │ └── HttpClientBuilderInterface.php ├── MailtrapBulkSendingClient.php ├── MailtrapClient.php ├── MailtrapClientInterface.php ├── MailtrapGeneralClient.php ├── MailtrapSandboxClient.php ├── MailtrapSendingClient.php └── Mime │ └── MailtrapEmail.php └── tests ├── Api ├── AbstractEmailsTest.php ├── BulkSending │ └── BulkEmailsTest.php ├── General │ ├── AccountTest.php │ ├── ContactTest.php │ ├── PermissionTest.php │ └── UserTest.php ├── Sandbox │ ├── AttachmentTest.php │ ├── EmailsTest.php │ ├── InboxTest.php │ ├── MessageTest.php │ └── ProjectTest.php └── Sending │ └── EmailsTest.php ├── Bridge └── Transport │ ├── MailtrapSdkTransportFactoryTest.php │ ├── MailtrapSdkTransportTest.php │ └── TransportFactoryTestCase.php ├── MailtrapBulkSendingClientTest.php ├── MailtrapClientTestCase.php ├── MailtrapGeneralClientTest.php ├── MailtrapSandboxClientTest.php ├── MailtrapSendingClientTest.php └── MailtrapTestCase.php /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | ## Motivation 2 | 3 | 4 | ## Changes 5 | 6 | - Change 1 7 | - Change 2 8 | 9 | ## How to test 10 | 11 | - [ ] Test 1 12 | - [ ] Test 2 13 | 14 | ## Images and GIFs 15 | 16 | | Before | After | 17 | |--------|--------| 18 | | link1 | link2 | 19 | | link3 | link4 | 20 | | link5 | link6 | 21 | | link7 | link8 | 22 | | link9 | link10 | 23 | -------------------------------------------------------------------------------- /.github/workflows/ci-phpunit.yml: -------------------------------------------------------------------------------- 1 | name: Unit Tests 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | pull_request: 7 | branches: 8 | - 'release/**' 9 | - 'feature/**' 10 | - 'main' 11 | schedule: 12 | - cron: '0 0 * * *' # Runs every day at midnight UTC 13 | 14 | jobs: 15 | phpunit-tests: 16 | name: PHP ${{ matrix.php-version }} Latest 17 | runs-on: ${{ matrix.os }} 18 | strategy: 19 | matrix: 20 | os: [ ubuntu-latest ] 21 | php-version: [ '8.0','8.1','8.2','8.3','8.4' ] 22 | 23 | steps: 24 | - name: Checkout 25 | uses: actions/checkout@v3 26 | 27 | - name: Setup PHP 28 | id: setup-php 29 | uses: shivammathur/setup-php@v2 30 | with: 31 | php-version: ${{ matrix.php-version }} 32 | extensions: mbstring, intl, curl 33 | 34 | - name: Print PHP version 35 | run: echo ${{ steps.setup-php.outputs.php-version }} 36 | 37 | - name: Get composer cache directory 38 | id: composer-cache 39 | run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT 40 | 41 | - name: Cache dependencies 42 | uses: actions/cache@v3 43 | with: 44 | path: ${{ steps.composer-cache.outputs.dir }} 45 | key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }} 46 | restore-keys: ${{ runner.os }}-composer 47 | 48 | - name: Validate composer.json 49 | run: composer validate 50 | 51 | - name: Install Composer dependencies 52 | run: composer install --prefer-dist --no-progress 53 | 54 | - name: Run phpunit tests 55 | run: composer test 56 | 57 | symfony: 58 | name: Symfony ${{ matrix.symfony }} LTS and PHP ${{ matrix.php-version }} 59 | runs-on: ubuntu-latest 60 | strategy: 61 | matrix: 62 | include: 63 | - symfony: '6' 64 | php-version: '8.2' 65 | - symfony: '7' 66 | php-version: '8.3' 67 | - symfony: '7' 68 | php-version: '8.4' 69 | 70 | steps: 71 | - name: Checkout 72 | uses: actions/checkout@v3 73 | 74 | - name: Setup PHP 75 | uses: shivammathur/setup-php@v2 76 | with: 77 | php-version: ${{ matrix.php-version }} 78 | tools: composer:v2 79 | coverage: none 80 | 81 | - name: Install dependencies 82 | run: | 83 | composer config --no-plugins allow-plugins.symfony/flex true 84 | composer require --no-update --no-interaction --no-progress symfony/flex 85 | composer config extra.symfony.require ${{ matrix.symfony}} 86 | composer update --prefer-dist --no-interaction --prefer-stable --prefer-lowest --no-progress 87 | 88 | - name: Execute tests 89 | run: composer test 90 | 91 | laravel: 92 | name: Laravel ${{ matrix.laravel }} LTS and PHP ${{ matrix.php-version }} 93 | runs-on: ubuntu-latest 94 | strategy: 95 | matrix: 96 | include: 97 | - laravel: '9' 98 | php-version: '8.1' 99 | - laravel: '10' 100 | php-version: '8.2' 101 | - laravel: '11' 102 | php-version: '8.3' 103 | - laravel: '11' 104 | php-version: '8.4' 105 | 106 | steps: 107 | - name: Checkout 108 | uses: actions/checkout@v3 109 | 110 | - name: Setup PHP 111 | uses: shivammathur/setup-php@v2 112 | with: 113 | php-version: ${{ matrix.php-version }} 114 | tools: composer:v2 115 | coverage: none 116 | 117 | - name: Install dependencies 118 | run: | 119 | composer config --no-plugins allow-plugins.laravel/installer true 120 | composer require --no-update --no-interaction --no-progress laravel/framework:${{ matrix.laravel }} 121 | composer update --prefer-dist --no-interaction --prefer-stable --prefer-lowest --no-progress 122 | 123 | - name: Execute tests 124 | run: composer test 125 | -------------------------------------------------------------------------------- /.github/workflows/ci-psalm.yaml: -------------------------------------------------------------------------------- 1 | name: Psalm Analyze 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | pull_request: 7 | branches: 8 | - 'release/**' 9 | - 'feature/**' 10 | - 'main' 11 | 12 | jobs: 13 | psalm-analyze: 14 | runs-on: ${{ matrix.os }} 15 | strategy: 16 | matrix: 17 | os: [ ubuntu-latest ] 18 | php-versions: [ '8.0','8.1','8.2','8.3','8.4' ] 19 | 20 | steps: 21 | - name: Checkout 22 | uses: actions/checkout@v3 23 | 24 | - name: Setup PHP 25 | id: setup-php 26 | uses: shivammathur/setup-php@v2 27 | with: 28 | php-version: ${{ matrix.php-versions }} 29 | extensions: mbstring, intl, curl 30 | 31 | - name: Print PHP version 32 | run: echo ${{ steps.setup-php.outputs.php-version }} 33 | 34 | - name: Get composer cache directory 35 | id: composer-cache 36 | run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT 37 | 38 | - name: Cache dependencies 39 | uses: actions/cache@v3 40 | with: 41 | path: ${{ steps.composer-cache.outputs.dir }} 42 | key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }} 43 | restore-keys: ${{ runner.os }}-composer 44 | 45 | - name: Install Composer dependencies 46 | run: composer install --prefer-dist --no-progress 47 | 48 | - name: Run Psalm analyze 49 | run: vendor/bin/psalm 50 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## [3.1.0] - 2025-05-27 2 | - Add Contacts API functionality 3 | 4 | ## [3.0.0] - 2025-05-15 5 | 6 | - [BC BREAK] Change Symfony&Laravel integration naming from `mailtrap+api` to `mailtrap+sdk` ([reason](https://symfony.com/packages/MailtrapMailer)) 7 | - Rollback Psalm library because now they support PHP 8.4 8 | 9 | ## [2.1.0] - 2025-01-28 10 | 11 | - Use psr/http-factory instead of php-http/message-factory 12 | - Remove a Psalm library from dependencies which can break installation on PHP 8.4 13 | 14 | ## [2.0.4] - 2025-01-22 15 | 16 | - Add name prefix into custom EmailHeaders to avoid conflicts with reserved names Symfony\Component\Mime\Header\Headers::HEADER_CLASS_MAP 17 | 18 | ## [2.0.3] - 2024-11-01 19 | 20 | - Add more template examples in Laravel/Symfony docs 21 | 22 | ## [2.0.2] - 2024-10-04 23 | 24 | - Remove an expected message from the `testUnsupportedSchemeException` method ([reason](https://github.com/symfony/mailer/commit/a098a3fe7f42a30235b862162090900cbf787ff6)) 25 | 26 | 27 | ## [2.0.1] - 2024-08-16 28 | 29 | - Support mixed types in template_variables (array, string, int, float, bool) 30 | 31 | ## [2.0.0] - 2024-06-12 32 | - [BC BREAK] PHP 7.4 will no longer be supported (PHP 8.0+). 33 | - [BC BREAK] Rebuild `Emails` layers to use the `inboxId` at the client level ([examples](examples/testing/emails.php)) 34 | - [BC BREAK] Rebuild SANDBOX `Projects` & `Messages` & `Attachments` & `Inboxes` layers ([examples](examples/testing)) 35 | - [BC BREAK] Rebuild GENERAL `Accounts` & `Permissions` & `Users` layers ([examples](examples/general)) 36 | - Added a short method to get the Email layer depending on config params `MailtrapClient::initSendingEmails()` 37 | - Added MailtrapEmail wrapper (MIME) for easy add category, custom variables, template uuid, etc. 38 | 39 | ## [1.9.0] - 2024-05-06 40 | 41 | - Support templates in testing 42 | - Refactoring of examples 43 | - sandbox -> [testing](examples/testing) 44 | - bulkSending -> [sending](examples/sending) 45 | 46 | ## [1.8.1] - 2024-04-25 47 | 48 | - Use real value for template headers (should not be encoded as it is not a real header) 49 | 50 | ## [1.8.0] - 2024-04-19 51 | 52 | - Support new functionality [Bulk Stream](https://help.mailtrap.io/article/113-sending-streams) 53 | 54 | ## [1.7.4] - 2024-03-20 55 | 56 | - Add PHP 8.3 support (GitHub Actions) 57 | - Support new Symfony packages v7 (mime, http-client, etc) 58 | 59 | ## [1.7.3] - 2024-01-30 60 | 61 | - Use Psr18ClientDiscovery instead of deprecated HttpClientDiscovery 62 | 63 | ## [1.7.0] - 2023-05-16 64 | 65 | - Support sandbox message endpoints. Examples [here](examples/sandbox/messages.php) 66 | 67 | 68 | ## [1.6.0] - 2023-05-05 69 | 70 | - Support sandbox attachment endpoints. Examples [here](examples/sandbox/attachments.php) 71 | 72 | ## [1.5.0] - 2023-05-04 73 | 74 | - Support sandbox inbox endpoints. Examples [here](examples/sandbox/inboxes.php) 75 | 76 | 77 | ## [1.4.0] - 2023-04-20 78 | 79 | - Support general permission endpoints. Examples [here](examples/general/permissions.php) 80 | 81 | ## [1.3.0] - 2023-04-13 82 | 83 | - Support sandbox project endpoints. Examples [here](examples/sandbox/projects.php) 84 | 85 | ## [1.2.0] - 2023-04-10 86 | 87 | - Support general account users endpoints. Examples [here](examples/general/users.php) 88 | 89 | ## [1.1.0] - 2023-04-07 90 | 91 | - Breaking changes: 92 | - move `accounts()` functions from the `sandbox & sending` layers to the `general` 93 | - Add the new `general` layer to mailtrapClient 94 | 95 | ## [1.0.0] - 2023-03-28 96 | 97 | - The initial release of the official mailtrap.io PHP client 98 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | We as members, contributors, and leaders pledge to make participation in our community a harassment-free experience for everyone, regardless of age, body size, visible or invisible disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, religion, or sexual identity and orientation. 6 | 7 | We pledge to act and interact in ways that contribute to an open, welcoming, diverse, inclusive, and healthy community. 8 | 9 | ## Our Standards 10 | 11 | Examples of behavior that contributes to a positive environment for our community include: 12 | 13 | * Demonstrating empathy and kindness toward other people 14 | * Being respectful of differing opinions, viewpoints, and experiences 15 | * Giving and gracefully accepting constructive feedback 16 | * Accepting responsibility and apologizing to those affected by our mistakes, and learning from the experience 17 | * Focusing on what is best not just for us as individuals, but for the overall community 18 | 19 | Examples of unacceptable behavior include: 20 | 21 | * The use of sexualized language or imagery, and sexual attention or advances of any kind 22 | * Trolling, insulting or derogatory comments, and personal or political attacks 23 | * Public or private harassment 24 | * Publishing others' private information, such as a physical or email address, without their explicit permission 25 | * Other conduct which could reasonably be considered inappropriate in a professional setting 26 | 27 | ## Enforcement Responsibilities 28 | 29 | Community leaders are responsible for clarifying and enforcing our standards of acceptable behavior and will take appropriate and fair corrective action in response to any behavior that they deem inappropriate, threatening, offensive, or harmful. 30 | 31 | Community leaders have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, and will communicate reasons for moderation decisions when appropriate. 32 | 33 | ## Scope 34 | 35 | This Code of Conduct applies within all community spaces, and also applies when an individual is officially representing the community in public spaces. Examples of representing our community include using an official e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. 36 | 37 | ## Enforcement 38 | 39 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported to the community leaders responsible for enforcement at support@mailtrap.io. All complaints will be reviewed and investigated promptly and fairly. 40 | 41 | All community leaders are obligated to respect the privacy and security of the reporter of any incident. 42 | 43 | ## Enforcement Guidelines 44 | 45 | Community leaders will follow these Community Impact Guidelines in determining the consequences for any action they deem in violation of this Code of Conduct: 46 | 47 | ### 1. Correction 48 | 49 | **Community Impact**: Use of inappropriate language or other behavior deemed unprofessional or unwelcome in the community. 50 | 51 | **Consequence**: A private, written warning from community leaders, providing clarity around the nature of the violation and an explanation of why the behavior was inappropriate. A public apology may be requested. 52 | 53 | ### 2. Warning 54 | 55 | **Community Impact**: A violation through a single incident or series of actions. 56 | 57 | **Consequence**: A warning with consequences for continued behavior. No interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, for a specified period of time. This includes avoiding interactions in community spaces as well as external channels like social media. Violating these terms may lead to a temporary or permanent ban. 58 | 59 | ### 3. Temporary Ban 60 | 61 | **Community Impact**: A serious violation of community standards, including sustained inappropriate behavior. 62 | 63 | **Consequence**: A temporary ban from any sort of interaction or public communication with the community for a specified period of time. No public or private interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, is allowed during this period. Violating these terms may lead to a permanent ban. 64 | 65 | ### 4. Permanent Ban 66 | 67 | **Community Impact**: Demonstrating a pattern of violation of community standards, including sustained inappropriate behavior, harassment of an individual, or aggression toward or disparagement of classes of individuals. 68 | 69 | **Consequence**: A permanent ban from any sort of public interaction within the community. 70 | 71 | ## Attribution 72 | 73 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 2.0, 74 | available at https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. 75 | 76 | Community Impact Guidelines were inspired by [Mozilla's code of conduct enforcement ladder](https://github.com/mozilla/diversity). 77 | 78 | [homepage]: https://www.contributor-covenant.org 79 | 80 | For answers to common questions about this code of conduct, see the FAQ at 81 | https://www.contributor-covenant.org/faq. Translations are available at https://www.contributor-covenant.org/translations. 82 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2023 Railsware Products Studio LLC 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Official Mailtrap PHP client 2 | =============== 3 | ![GitHub Actions](https://github.com/railsware/mailtrap-php/actions/workflows/ci-phpunit.yml/badge.svg) 4 | ![GitHub Actions](https://github.com/railsware/mailtrap-php/actions/workflows/ci-psalm.yaml/badge.svg) 5 | 6 | [![PHP version support](https://img.shields.io/packagist/dependency-v/railsware/mailtrap-php/php?style=flat)](https://packagist.org/packages/railsware/mailtrap-php) 7 | [![Latest Version on Packagist](https://img.shields.io/packagist/v/railsware/mailtrap-php.svg?style=flat)](https://packagist.org/packages/railsware/mailtrap-php) 8 | [![Total Downloads](https://img.shields.io/packagist/dt/railsware/mailtrap-php.svg?style=flat)](https://packagist.org/packages/railsware/mailtrap-php) 9 | 10 | 11 | ## Installation 12 | You can install the package via [composer](http://getcomposer.org/) 13 | 14 | The Mailtrap API Client is not hard coupled to Guzzle, React, Zend, Symfony HTTP or any other library that sends 15 | HTTP messages. Instead, it uses the [PSR-18](https://www.php-fig.org/psr/psr-18/) client abstraction. 16 | 17 | This will give you the flexibility to choose what [HTTP client](https://docs.php-http.org/en/latest/clients.html) you want to use. 18 | 19 | If you just want to get started quickly you should run one of the following command (depends on which HTTP client you want to use): 20 | ```bash 21 | # With symfony http client (recommend) 22 | composer require railsware/mailtrap-php symfony/http-client nyholm/psr7 23 | 24 | # Or with guzzle http client 25 | composer require railsware/mailtrap-php guzzlehttp/guzzle php-http/guzzle7-adapter 26 | ``` 27 | 28 | ## Usage 29 | You should use Composer autoloader in your application to automatically load your dependencies. 30 | 31 | Here's how to send a message using the SDK: 32 | 33 | ```php 34 | from(new Address('example@your-domain-here.com', 'Mailtrap Test')) 52 | ->replyTo(new Address('reply@your-domain-here.com')) 53 | ->to(new Address('email@example.com', 'Jon')) 54 | ->priority(Email::PRIORITY_HIGH) 55 | ->cc('mailtrapqa@example.com') 56 | ->addCc('staging@example.com') 57 | ->bcc('mailtrapdev@example.com') 58 | ->subject('Best practices of building HTML emails') 59 | ->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') 60 | ->html( 61 | ' 62 | 63 |


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

65 |

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

66 | 67 | 68 | ' 69 | ) 70 | ->embed(fopen('https://mailtrap.io/wp-content/uploads/2021/04/mailtrap-new-logo.svg', 'r'), 'logo', 'image/svg+xml') 71 | ->category('Integration Test') 72 | ->customVariables([ 73 | 'user_id' => '45982', 74 | 'batch_id' => 'PSJ-12' 75 | ]) 76 | ; 77 | 78 | // Custom email headers (optional) 79 | $email->getHeaders() 80 | ->addTextHeader('X-Message-Source', 'domain.com') 81 | ->add(new UnstructuredHeader('X-Mailer', 'Mailtrap PHP Client')) // the same as addTextHeader 82 | ; 83 | 84 | try { 85 | $response = $mailtrap->send($email); 86 | 87 | var_dump(ResponseHelper::toArray($response)); // body (array) 88 | } catch (Exception $e) { 89 | echo 'Caught exception: ', $e->getMessage(), "\n"; 90 | } 91 | 92 | 93 | // OR -> Mailtrap BULK SENDING client (real) 94 | try { 95 | $mailtrapBulkSending = MailtrapClient::initSendingEmails( 96 | apiKey: getenv('MAILTRAP_API_KEY'), # your API token from here https://mailtrap.io/api-tokens 97 | isBulk: true # Bulk sending (@see https://help.mailtrap.io/article/113-sending-streams) 98 | ); 99 | 100 | $response = $mailtrapBulkSending->send($email); 101 | 102 | var_dump(ResponseHelper::toArray($response)); // body (array) 103 | } catch (Exception $e) { 104 | echo 'Caught exception: ', $e->getMessage(), "\n"; 105 | } 106 | 107 | // OR -> Mailtrap Testing client (sandbox) 108 | try { 109 | $mailtrapTesting = MailtrapClient::initSendingEmails( 110 | apiKey: getenv('MAILTRAP_API_KEY'), # your API token from here https://mailtrap.io/api-tokens 111 | isSandbox: true, # Sandbox sending (@see https://help.mailtrap.io/article/109-getting-started-with-mailtrap-email-testing) 112 | inboxId: getenv('MAILTRAP_INBOX_ID') # required param for sandbox sending 113 | ); 114 | 115 | $response = $mailtrapTesting->send($email); 116 | 117 | var_dump(ResponseHelper::toArray($response)); // body (array) 118 | } catch (Exception $e) { 119 | echo 'Caught exception: ', $e->getMessage(), "\n"; 120 | } 121 | 122 | ``` 123 | 124 | ### All usage examples 125 | 126 | You can find more examples [here](examples). 127 | * [General examples](examples/general) 128 | * [Testing examples](examples/testing) 129 | * [Sending examples](examples/sending) 130 | 131 | 132 | ## Framework integration 133 | 134 | If you are using a framework you might consider these composer packages to make the framework integration easier. 135 | 136 | * [Symfony](src/Bridge/Symfony) 137 | * [Laravel](src/Bridge/Laravel) 138 | 139 | ## Contributing 140 | 141 | Bug reports and pull requests are welcome on [GitHub](https://github.com/railsware/mailtrap-php). This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [code of conduct](CODE_OF_CONDUCT.md). 142 | 143 | ## License 144 | 145 | The package is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT). 146 | 147 | ## Code of Conduct 148 | 149 | Everyone interacting in the Mailtrap project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](CODE_OF_CONDUCT.md). 150 | -------------------------------------------------------------------------------- /UPGRADE-2.0.md: -------------------------------------------------------------------------------- 1 | UPGRADE FROM 1.x to 2.0 2 | ======================= 3 | 4 | ### Email Layers 5 | * Added a short method to get one of the Email layers (Sending/Bulk/Sandbox) depending on config params `MailtrapClient::initSendingEmails()` 6 | * string $apiKey 7 | * bool $isBulk = false 8 | * bool $isSandbox = false 9 | * int $inboxId = null 10 | * **BC BREAK**: In Sandbox layer `inboxId` should be passed at the client level. 11 | 12 | __Before__: 13 | ```php 14 | $mailtrap = new MailtrapClient(new Config(getenv('MAILTRAP_API_KEY'))); 15 | 16 | $response = $mailtrap 17 | ->sandbox() 18 | ->emails() 19 | ->send($email, 1000001); # <--- inboxId here 20 | ``` 21 | __After__: 22 | ```php 23 | # short method using `initSendingEmails` 24 | $mailtrap = MailtrapClient::initSendingEmails( 25 | apiKey: getenv('MAILTRAP_API_KEY'), #your API token from here https://mailtrap.io/api-tokens 26 | isSandbox: true, # required param for sandbox sending 27 | inboxId: getenv('MAILTRAP_INBOX_ID') # <--- inboxId here 28 | ); 29 | 30 | # or using the client directly (old variant) 31 | $mailtrap = (new MailtrapClient(new Config(getenv('MAILTRAP_API_KEY')))) 32 | ->sandbox() 33 | ->emails(getenv('MAILTRAP_INBOX_ID')); # <--- inboxId here 34 | 35 | $response = $mailtrap->send($email); 36 | ``` 37 | 38 | ### General API 39 | * **BC BREAK**: Rebuild `Accounts` & `Permissions` & `Users` layers ([examples](examples/general)) 40 | 41 | __Before__: 42 | ```php 43 | $mailtrap = new MailtrapClient(new Config(getenv('MAILTRAP_API_KEY'))); # no changes here 44 | 45 | $response = $mailtrap 46 | ->general() 47 | ->permissions() 48 | ->getResources(getenv('MAILTRAP_ACCOUNT_ID')); # <--- accountId here 49 | 50 | $response = $mailtrap 51 | ->general() 52 | ->users() 53 | ->getList(getenv('MAILTRAP_ACCOUNT_ID')); # <--- accountId here 54 | ``` 55 | __After__: 56 | ```php 57 | // all permissions endpoints 58 | $response = $mailtrap 59 | ->general() 60 | ->permissions(getenv('MAILTRAP_ACCOUNT_ID')) # <--- accountId here 61 | ->getResources(); 62 | 63 | // all users endpoints 64 | $response = $mailtrap 65 | ->general() 66 | ->users(getenv('MAILTRAP_ACCOUNT_ID')) # <--- accountId here 67 | ->getList(); 68 | ``` 69 | 70 | ### Sandbox API 71 | * **BC BREAK**: Rebuild `Projects` & `Messages` & `Attachments` & `Inboxes` layers ([examples](examples/testing)) 72 | 73 | __Before__: 74 | ```php 75 | $mailtrap = new MailtrapClient(new Config(getenv('MAILTRAP_API_KEY'))); # no changes here 76 | 77 | $response = $mailtrap 78 | ->sandbox() 79 | ->inboxes() 80 | ->getList(getenv('MAILTRAP_ACCOUNT_ID')); # <--- accountId here 81 | ``` 82 | __After__: 83 | ```php 84 | // all sandbox(testing) endpoints: projects, messages, attachments, inboxes 85 | $response = $mailtrap 86 | ->sandbox() 87 | ->inboxes(getenv('MAILTRAP_ACCOUNT_ID')) # <--- accountId here 88 | ->getList(); 89 | ``` 90 | 91 | ### Email Template class 92 | * Added `MailtrapEmail` wrapper (MIME) for easy use category, custom variables, template uuid, etc. 93 | 94 | __Before__: 95 | ```php 96 | $email = (new Email()) 97 | ->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) 98 | ->replyTo(new Address('reply@YOUR-DOMAIN-HERE.com')) 99 | ->to(new Address('example@gmail.com', 'Jon')) 100 | ; 101 | 102 | // Template UUID and Variables 103 | $email->getHeaders() 104 | ->add(new TemplateUuidHeader('bfa432fd-0000-0000-0000-8493da283a69')) 105 | ->add(new TemplateVariableHeader('user_name', 'Jon Bush')) 106 | ->add(new TemplateVariableHeader('next_step_link', 'https://mailtrap.io/')) 107 | ->add(new TemplateVariableHeader('get_started_link', 'https://mailtrap.io/')) 108 | ->add(new TemplateVariableHeader('onboarding_video_link', 'some_video_link')) 109 | ; 110 | ``` 111 | 112 | __After__: 113 | ```php 114 | use Mailtrap\Mime\MailtrapEmail; 115 | 116 | $email = (new MailtrapEmail()) # <--- new MIME class with template support 117 | ->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) 118 | ->replyTo(new Address('reply@YOUR-DOMAIN-HERE.com')) 119 | ->to(new Address('example@gmail.com', 'Jon')) 120 | ->templateUuid('bfa432fd-0000-0000-0000-8493da283a69') 121 | ->templateVariables([ 122 | 'user_name' => 'Jon Bush', 123 | 'next_step_link' => 'https://mailtrap.io/', 124 | 'get_started_link' => 'https://mailtrap.io/', 125 | 'onboarding_video_link' => 'some_video_link' 126 | ]) 127 | ; 128 | ``` 129 | -------------------------------------------------------------------------------- /UPGRADE-3.0.md: -------------------------------------------------------------------------------- 1 | UPGRADE FROM 2.x to 3.0 2 | ======================= 3 | 4 | Version 3.0 introduces **breaking changes** for `Laravel` and `Symfony` frameworks. 5 | This guide helps you upgrade your application accordingly. 6 | 7 | 8 | ### Laravel Integration 9 | 10 | 1. Change mailtrap transport inside your `config/mail.php` file. 11 | 12 | __Before__: 13 | ```php 14 | [ 23 | 24 | // start mailtrap transport 25 | 'mailtrap' => [ 26 | 'transport' => 'mailtrap' 27 | ], 28 | // end mailtrap transport 29 | 30 | ] 31 | ]; 32 | ``` 33 | __After__: 34 | ```php 35 | [ 44 | 45 | // start mailtrap transport 46 | 'mailtrap-sdk' => [ 47 | 'transport' => 'mailtrap-sdk' 48 | ], 49 | // end mailtrap transport 50 | 51 | ] 52 | ]; 53 | ``` 54 | 55 | 2. Set `mailtrap-sdk` transport as a default instead `mailtrap` inside your `.env` file. 56 | 57 | ```bash 58 | MAIL_MAILER="mailtrap-sdk" 59 | ``` 60 | 61 | 3. Rename mailtrap config file and variables 62 | 63 | __Before__: 64 | ```php 65 | # /config/mailtrap.php 66 | 67 | [ 71 | 'host' => env('MAILTRAP_HOST', 'send.api.mailtrap.io'), 72 | 'apiKey' => env('MAILTRAP_API_KEY'), 73 | 'inboxId' => env('MAILTRAP_INBOX_ID'), 74 | ], 75 | ]; 76 | ``` 77 | __After__: 78 | ```php 79 | # /config/mailtrap-sdk.php 80 | 81 | [ 85 | 'host' => env('MAILTRAP_HOST', 'send.api.mailtrap.io'), 86 | 'apiKey' => env('MAILTRAP_API_KEY'), 87 | 'inboxId' => env('MAILTRAP_INBOX_ID'), 88 | ], 89 | ]; 90 | ``` 91 | 92 | ### Symfony Integration 93 | 1. Change configuration inside your `config/services.yaml` file 94 | 95 | __Before__: 96 | ```yaml 97 | ... 98 | # add more service definitions when explicit configuration is needed 99 | # please note that last definitions always *replace* previous ones 100 | 101 | Mailtrap\Bridge\Transport\MailtrapTransportFactory: 102 | tags: 103 | - { name: 'mailer.transport_factory' } 104 | ``` 105 | __After__: 106 | ```yaml 107 | ... 108 | # add more service definitions when explicit configuration is needed 109 | # please note that last definitions always *replace* previous ones 110 | 111 | Mailtrap\Bridge\Transport\MailtrapSdkTransportFactory: 112 | tags: 113 | - { name: 'mailer.transport_factory' } 114 | ``` 115 | 116 | 2. Change MAILER_DSN variable inside your `.env` 117 | 118 | __Before__: 119 | ```bash 120 | MAILER_DSN=mailtrap://YOUR_API_KEY_HERE@default 121 | # or 122 | MAILER_DSN=mailtrap+api://YOUR_API_KEY_HERE@send.api.mailtrap.io 123 | ``` 124 | __After__: 125 | ```bash 126 | MAILER_DSN=mailtrap+sdk://YOUR_API_KEY_HERE@default 127 | # or 128 | MAILER_DSN=mailtrap+sdk://YOUR_API_KEY_HERE@send.api.mailtrap.io 129 | ``` 130 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "railsware/mailtrap-php", 3 | "description": "The Mailtrap SDK provides methods for all API functions.", 4 | "keywords": ["mailtrap", "mailtrap-io", "php", "mail", "email", "sdk", "plugin", "symfony", "laravel"], 5 | "homepage": "https://github.com/railsware/mailtrap-php", 6 | "license": "MIT", 7 | "require": { 8 | "php": "^8.0", 9 | "ext-curl": "*", 10 | "ext-json": "*", 11 | "psr/http-message": "^1.0 || ^2.0", 12 | "psr/http-client-implementation": "^1.0", 13 | "php-http/client-common": "^2.0", 14 | "php-http/httplug": "^2.0", 15 | "php-http/discovery": "^1.0", 16 | "symfony/mime": "^6.0|^7.0", 17 | "egulias/email-validator": "^2.1.10|^3.1|^4", 18 | "psr/http-factory": "^1.1" 19 | }, 20 | "require-dev": { 21 | "symfony/http-client": "^6.0|^7.0", 22 | "symfony/mailer": "^6.0|^7.0", 23 | "phpunit/phpunit": "^9", 24 | "nyholm/psr7": "^1.5", 25 | "vimeo/psalm": "^5.0|^6.0" 26 | }, 27 | "suggest": { 28 | "nyholm/psr7": "PSR-7 message implementation", 29 | "symfony/http-client": "HTTP client" 30 | }, 31 | "scripts": { 32 | "test": "vendor/bin/phpunit" 33 | }, 34 | "autoload": { 35 | "psr-4": { 36 | "Mailtrap\\": "src/" 37 | } 38 | }, 39 | "autoload-dev": { 40 | "psr-4": { 41 | "Mailtrap\\Tests\\": "tests/" 42 | } 43 | }, 44 | "extra": { 45 | "laravel": { 46 | "providers": [ 47 | "Mailtrap\\Bridge\\Laravel\\MailtrapSdkProvider" 48 | ] 49 | } 50 | }, 51 | "minimum-stability": "dev", 52 | "config": { 53 | "allow-plugins": { 54 | "php-http/discovery": true 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /examples/general/accounts.php: -------------------------------------------------------------------------------- 1 | accounts(); 11 | 12 | /** 13 | * Get a list of your Mailtrap accounts. 14 | * 15 | * GET https://mailtrap.io/api/accounts 16 | */ 17 | try { 18 | $response = $generalAccounts->getList(); 19 | 20 | var_dump(ResponseHelper::toArray($response)); // body (array) 21 | } catch (Exception $e) { 22 | echo 'Caught exception: ', $e->getMessage(), "\n"; 23 | } 24 | -------------------------------------------------------------------------------- /examples/general/contacts.php: -------------------------------------------------------------------------------- 1 | contacts($accountId); 14 | 15 | /** 16 | * Get all Contact Lists. 17 | * 18 | * GET https://mailtrap.io/api/accounts/{account_id}/contacts/lists 19 | */ 20 | try { 21 | $response = $contacts->getAllContactLists(); 22 | 23 | // print the response body (array) 24 | var_dump(ResponseHelper::toArray($response)); 25 | } catch (Exception $e) { 26 | echo 'Caught exception: ', $e->getMessage(), PHP_EOL; 27 | } 28 | 29 | 30 | /** 31 | * Create a new Contact 32 | * 33 | * POST https://mailtrap.io/api/accounts/{account_id}/contacts 34 | */ 35 | try { 36 | $response = $contacts->createContact( 37 | CreateContact::init( 38 | 'john.smith@example.com', 39 | ['first_name' => 'John', 'last_name' => 'Smith'], // Fields 40 | [1, 2] // List IDs to which the contact will be added 41 | ) 42 | ); 43 | 44 | // print the response body (array) 45 | var_dump(ResponseHelper::toArray($response)); 46 | } catch (Exception $e) { 47 | echo 'Caught exception: ', $e->getMessage(), PHP_EOL; 48 | } 49 | 50 | 51 | /** 52 | * Update contact by ID or Email. 53 | * 54 | * PATCH https://mailtrap.io/api/accounts/{account_id}/contacts/{id_or_email} 55 | */ 56 | try { 57 | // Update contact by ID 58 | $response = $contacts->updateContactById( 59 | '019706a8-0000-0000-0000-4f26816b467a', 60 | UpdateContact::init( 61 | 'john.smith@example.com', 62 | ['first_name' => 'John', 'last_name' => 'Smith'], // Fields 63 | [3], // List IDs to which the contact will be added 64 | [1, 2], // List IDs from which the contact will be removed 65 | true // Unsubscribe the contact 66 | ) 67 | ); 68 | 69 | // OR update contact by email 70 | $response = $contacts->updateContactByEmail( 71 | 'john.smith@example.com', 72 | UpdateContact::init( 73 | 'john.smith@example.com', 74 | ['first_name' => 'John', 'last_name' => 'Smith'], // Fields 75 | [3], // List IDs to which the contact will be added 76 | [1, 2], // List IDs from which the contact will be removed 77 | true // Unsubscribe the contact 78 | ) 79 | ); 80 | 81 | // print the response body (array) 82 | var_dump(ResponseHelper::toArray($response)); 83 | } catch (Exception $e) { 84 | echo 'Caught exception: ', $e->getMessage(), PHP_EOL; 85 | } 86 | 87 | 88 | /** 89 | * Delete contact 90 | * 91 | * Delete https://mailtrap.io/api/accounts/{account_id}/contacts/{id_or_email} 92 | */ 93 | try { 94 | // Delete contact by ID 95 | $response = $contacts->deleteContactById('019706a8-0000-0000-0000-4f26816b467a'); 96 | 97 | // OR delete contact by email 98 | $response = $contacts->deleteContactByEmail('john.smith@example.com'); 99 | 100 | // print the response body (array) 101 | var_dump(ResponseHelper::toArray($response)); 102 | } catch (Exception $e) { 103 | echo 'Caught exception: ', $e->getMessage(), PHP_EOL; 104 | } 105 | -------------------------------------------------------------------------------- /examples/general/permissions.php: -------------------------------------------------------------------------------- 1 | permissions($accountId); 16 | 17 | /** 18 | * Get resources 19 | * 20 | * GET https://mailtrap.io/api/accounts/{account_id}/permissions/resources 21 | */ 22 | try { 23 | $response = $generalPermissions->getResources(); 24 | 25 | // print the response body (array) 26 | var_dump(ResponseHelper::toArray($response)); 27 | } catch (Exception $e) { 28 | echo 'Caught exception: ', $e->getMessage(), "\n"; 29 | } 30 | 31 | 32 | /** 33 | * Manage user or token permissions 34 | * 35 | * If you send a combination of resource_type and resource_id that already exists, the permission is updated. 36 | * If the combination doesn’t exist, the permission is created. 37 | * 38 | * PUT https://mailtrap.io/api/accounts/{account_id}/account_accesses/{account_access_id}/permissions/bulk 39 | */ 40 | try { 41 | $accountAccessId = getenv('MAILTRAP_ACCOUNT_ACCESS_ID'); 42 | 43 | // resource IDs 44 | $projectResourceId = getenv('MAILTRAP_NEW_PROJECT_RESOURCE_ID'); 45 | $inboxResourceId = getenv('MAILTRAP_INBOX_RESOURCE_ID'); 46 | $destroyProjectResourceId = getenv('MAILTRAP_OLD_PROJECT_RESOURCE_ID'); 47 | 48 | $permissions = new Permissions( 49 | new CreateOrUpdatePermission($projectResourceId, PermissionInterface::TYPE_PROJECT, 10), // viewer = 10 50 | new CreateOrUpdatePermission($inboxResourceId, PermissionInterface::TYPE_INBOX, 100), // admin = 100 51 | new DestroyPermission($destroyProjectResourceId, PermissionInterface::TYPE_PROJECT), 52 | ); 53 | 54 | $response = $generalPermissions->update($accountAccessId, $permissions); 55 | 56 | // print the response body (array) 57 | var_dump(ResponseHelper::toArray($response)); 58 | } catch (Exception $e) { 59 | echo 'Caught exception: ', $e->getMessage(), "\n"; 60 | } 61 | -------------------------------------------------------------------------------- /examples/general/users.php: -------------------------------------------------------------------------------- 1 | users($accountId); 12 | 13 | /** 14 | * List all users in account 15 | * 16 | * GET https://mailtrap.io/api/accounts/{account_id}/account_accesses 17 | */ 18 | try { 19 | $response = $generalUsers->getList(); 20 | 21 | // OR with query parameters (not required) 22 | $inboxIds = [2000005, 2000006]; 23 | $projectIds = [1005001]; 24 | $response = $generalUsers->getList($inboxIds, $projectIds); 25 | 26 | // print the response body (array) 27 | var_dump(ResponseHelper::toArray($response)); 28 | } catch (Exception $e) { 29 | echo 'Caught exception: ', $e->getMessage(), "\n"; 30 | } 31 | 32 | /** 33 | * Remove user from the account 34 | * 35 | * DELETE https://mailtrap.io/api/accounts/{account_id}/account_accesses/{account_access_id} 36 | */ 37 | try { 38 | $accountAccessId = 10000009; 39 | 40 | $response = $generalUsers->delete($accountAccessId); 41 | 42 | // print the response body (array) 43 | var_dump(ResponseHelper::toArray($response)); 44 | } catch (Exception $e) { 45 | echo 'Caught exception: ', $e->getMessage(), "\n"; 46 | } 47 | -------------------------------------------------------------------------------- /examples/sending/emails.php: -------------------------------------------------------------------------------- 1 | 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) 30 | ->replyTo(new Address('reply@YOUR-DOMAIN-HERE.com')) 31 | ->to(new Address('email@example.com', 'Jon')) 32 | ->priority(Email::PRIORITY_HIGH) 33 | ->cc('mailtrapqa@example.com') 34 | ->addCc('staging@example.com') 35 | ->bcc('mailtrapdev@example.com') 36 | ->subject('Best practices of building HTML emails') 37 | ->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') 38 | ->html( 39 | ' 40 | 41 |


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

43 |

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

44 | 45 | 46 | ' 47 | ) 48 | ->embed(fopen('https://mailtrap.io/wp-content/uploads/2021/04/mailtrap-new-logo.svg', 'r'), 'logo', 'image/svg+xml') 49 | ->attachFromPath('README.md') 50 | ->customVariables([ 51 | 'user_id' => '45982', 52 | 'batch_id' => 'PSJ-12' 53 | ]) 54 | ->category('Integration Test') 55 | ; 56 | 57 | // Custom email headers (optional) 58 | $email->getHeaders() 59 | ->addTextHeader('X-Message-Source', 'test.com') 60 | ->add(new UnstructuredHeader('X-Mailer', 'Mailtrap PHP Client')) 61 | ; 62 | 63 | $response = $mailtrap->send($email); 64 | 65 | var_dump(ResponseHelper::toArray($response)); // body (array) 66 | } catch (Exception $e) { 67 | echo 'Caught exception: ', $e->getMessage(), "\n"; 68 | } 69 | 70 | 71 | /** 72 | * Email Sending WITH TEMPLATE 73 | * 74 | * WARNING! If template is provided then subject, text, html, category and other params are forbidden. 75 | * 76 | * UUID of email template. Subject, text and html will be generated from template using optional template_variables. 77 | * Optional template variables that will be used to generate actual subject, text and html from email template 78 | */ 79 | try { 80 | $mailtrap = MailtrapClient::initSendingEmails( 81 | apiKey: getenv('MAILTRAP_API_KEY') #your API token from here https://mailtrap.io/api-tokens 82 | ); 83 | 84 | $email = (new MailtrapEmail()) 85 | ->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) 86 | ->replyTo(new Address('reply@YOUR-DOMAIN-HERE.com')) 87 | ->to(new Address('example@gmail.com', 'Jon')) 88 | ->templateUuid('bfa432fd-0000-0000-0000-8493da283a69') 89 | ->templateVariables([ 90 | 'user_name' => 'Jon Bush', 91 | 'next_step_link' => 'https://mailtrap.io/', 92 | 'get_started_link' => 'https://mailtrap.io/', 93 | 'onboarding_video_link' => 'some_video_link', 94 | 'company' => [ 95 | 'name' => 'Best Company', 96 | 'address' => 'Its Address', 97 | ], 98 | 'products' => [ 99 | [ 100 | 'name' => 'Product 1', 101 | 'price' => 100, 102 | ], 103 | [ 104 | 'name' => 'Product 2', 105 | 'price' => 200, 106 | ], 107 | ], 108 | 'isBool' => true, 109 | 'int' => 123 110 | ]) 111 | ; 112 | 113 | $response = $mailtrap->send($email); 114 | 115 | var_dump(ResponseHelper::toArray($response)); // body (array) 116 | } catch (Exception $e) { 117 | echo 'Caught exception: ', $e->getMessage(), "\n"; 118 | } 119 | 120 | 121 | /********************************************************************************************************************** 122 | ******************************************* EMAIL BULK SENDING ******************************************************* 123 | ********************************************************************************************************************** 124 | */ 125 | 126 | /** 127 | * Email Bulk Sending API 128 | * 129 | * POST https://bulk.api.mailtrap.io/api/send 130 | */ 131 | try { 132 | $bulkMailtrap = MailtrapClient::initSendingEmails( 133 | apiKey: getenv('MAILTRAP_API_KEY'), #your API token from here https://mailtrap.io/api-tokens 134 | isBulk: true # Bulk sending (@see https://help.mailtrap.io/article/113-sending-streams) 135 | ); 136 | 137 | $email = (new MailtrapEmail()) 138 | ->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) 139 | ->replyTo(new Address('reply@YOUR-DOMAIN-HERE.com')) 140 | ->to(new Address('email@example.com', 'Jon')) 141 | ->priority(Email::PRIORITY_HIGH) 142 | ->cc('mailtrapqa@example.com') 143 | ->addCc('staging@example.com') 144 | ->bcc('mailtrapdev@example.com') 145 | ->subject('Best practices of building HTML emails') 146 | ->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') 147 | ->html( 148 | ' 149 | 150 |


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

152 |

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

153 | 154 | 155 | ' 156 | ) 157 | ->embed(fopen('https://mailtrap.io/wp-content/uploads/2021/04/mailtrap-new-logo.svg', 'r'), 'logo', 'image/svg+xml') 158 | ->attachFromPath('README.md') 159 | ->customVariables([ 160 | 'user_id' => '45982', 161 | 'batch_id' => 'PSJ-12' 162 | ]) 163 | ->category('Integration Test') 164 | ; 165 | 166 | // Custom email headers (optional) 167 | $email->getHeaders() 168 | ->addTextHeader('X-Message-Source', 'test.com') 169 | ->add(new UnstructuredHeader('X-Mailer', 'Mailtrap PHP Client')) 170 | ; 171 | 172 | $response = $bulkMailtrap->send($email); 173 | 174 | var_dump(ResponseHelper::toArray($response)); // body (array) 175 | } catch (Exception $e) { 176 | echo 'Caught exception: ', $e->getMessage(), "\n"; 177 | } 178 | 179 | 180 | /** 181 | * Email Bulk Sending WITH TEMPLATE 182 | * 183 | * WARNING! If a template is provided, then subject, text, html, category and other params are forbidden. 184 | * 185 | * UUID of email template. Subject, text and html will be generated from template using optional template_variables. 186 | * Optional template variables that will be used to generate actual subject, text and html from email template 187 | */ 188 | try { 189 | $bulkMailtrap = MailtrapClient::initSendingEmails( 190 | apiKey: getenv('MAILTRAP_API_KEY'), #your API token from here https://mailtrap.io/api-tokens 191 | isBulk: true # Bulk sending (@see https://help.mailtrap.io/article/113-sending-streams) 192 | ); 193 | 194 | $email = (new MailtrapEmail()) 195 | ->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) 196 | ->replyTo(new Address('reply@YOUR-DOMAIN-HERE.com')) 197 | ->to(new Address('example@gmail.com', 'Jon')) 198 | ->templateUuid('bfa432fd-0000-0000-0000-8493da283a69') 199 | ->templateVariables([ 200 | 'user_name' => 'Jon Bush', 201 | 'next_step_link' => 'https://mailtrap.io/', 202 | 'get_started_link' => 'https://mailtrap.io/', 203 | 'onboarding_video_link' => 'some_video_link', 204 | 'company' => [ 205 | 'name' => 'Best Company', 206 | 'address' => 'Its Address', 207 | ], 208 | 'products' => [ 209 | [ 210 | 'name' => 'Product 1', 211 | 'price' => 100, 212 | ], 213 | [ 214 | 'name' => 'Product 2', 215 | 'price' => 200, 216 | ], 217 | ], 218 | 'isBool' => true, 219 | 'int' => 123 220 | ]) 221 | ; 222 | 223 | $response = $bulkMailtrap->send($email); 224 | 225 | var_dump(ResponseHelper::toArray($response)); // body (array) 226 | } catch (Exception $e) { 227 | echo 'Caught exception: ', $e->getMessage(), "\n"; 228 | } 229 | -------------------------------------------------------------------------------- /examples/testing/attachments.php: -------------------------------------------------------------------------------- 1 | attachments($accountId, $inboxId); #required parameters are accountId amd inboxId 14 | 15 | 16 | /** 17 | * Get attachments 18 | * 19 | * GET https://mailtrap.io/api/accounts/{account_id}/inboxes/{inbox_id}/messages/{message_id}/attachments 20 | */ 21 | try { 22 | $messageId = getenv('MAILTRAP_MESSAGE_ID'); 23 | $attachmentType = 'inline'; # optional (null|string) 24 | 25 | $response = $sandboxAttachments->getMessageAttachments($messageId, $attachmentType); 26 | 27 | // print the response body (array) 28 | var_dump(ResponseHelper::toArray($response)); 29 | } catch (Exception $e) { 30 | echo 'Caught exception: ', $e->getMessage(), "\n"; 31 | } 32 | 33 | /** 34 | * Get single attachment 35 | * 36 | * GET https://mailtrap.io/api/accounts/{account_id}/inboxes/{inbox_id}/messages/{message_id}/attachments/{attachment_id} 37 | */ 38 | try { 39 | $messageId = getenv('MAILTRAP_MESSAGE_ID'); 40 | $attachmentId = getenv('MAILTRAP_MESSAGE_ATTACHMENT_ID'); 41 | 42 | $response = $sandboxAttachments->getMessageAttachment($messageId, $attachmentId); 43 | 44 | // print the response body (array) 45 | var_dump(ResponseHelper::toArray($response)); 46 | } catch (Exception $e) { 47 | echo 'Caught exception: ', $e->getMessage(), "\n"; 48 | } 49 | -------------------------------------------------------------------------------- /examples/testing/emails.php: -------------------------------------------------------------------------------- 1 | from(new Address('mailtrap@example.com', 'Mailtrap Test')) // <--- you should use the domain which is linked to template UUID (otherwise you will get 401) 31 | ->replyTo(new Address('reply@example.com')) 32 | ->to(new Address('email@example.com', 'Jon')) 33 | ->cc('mailtrapqa@example.com') 34 | ->addCc('staging@example.com') 35 | ->bcc('mailtrapdev@example.com') 36 | ->subject('Best practices of building HTML emails') 37 | ->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') 38 | ->html( 39 | ' 40 | 41 |


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

43 |

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

44 | 45 | 46 | ' 47 | ) 48 | ->embed(fopen('https://mailtrap.io/wp-content/uploads/2021/04/mailtrap-new-logo.svg', 'r'), 'logo', 'image/svg+xml') 49 | ->attachFromPath('README.md') 50 | ->customVariables([ 51 | 'user_id' => '45982', 52 | 'batch_id' => 'PSJ-12' 53 | ]) 54 | ->category('Integration Test') 55 | ; 56 | 57 | // Custom email headers (optional) 58 | $email->getHeaders() 59 | ->addTextHeader('X-Message-Source', 'test.com') 60 | ->add(new UnstructuredHeader('X-Mailer', 'Mailtrap PHP Client')) 61 | ; 62 | 63 | $response = $mailtrap->send($email); 64 | 65 | // print all possible information from the response 66 | var_dump($response->getHeaders()); //headers (array) 67 | var_dump($response->getStatusCode()); //status code (int) 68 | var_dump(ResponseHelper::toArray($response)); // body (array) 69 | } catch (Exception $e) { 70 | echo 'Caught exception: ', $e->getMessage(), "\n"; 71 | } 72 | 73 | 74 | /** 75 | * Test Email WITH TEMPLATE 76 | * 77 | * WARNING! If template is provided then subject, text, html, category and other params are forbidden. 78 | * 79 | * UUID of email template. Subject, text and html will be generated from template using optional template_variables. 80 | * Optional template variables that will be used to generate actual subject, text and html from email template 81 | */ 82 | try { 83 | $mailtrap = MailtrapClient::initSendingEmails( 84 | apiKey: getenv('MAILTRAP_API_KEY'), #your API token from here https://mailtrap.io/api-tokens 85 | isSandbox: true, # Sandbox sending (@see https://help.mailtrap.io/article/109-getting-started-with-mailtrap-email-testing) 86 | inboxId: getenv('MAILTRAP_INBOX_ID') # required param for sandbox sending 87 | ); 88 | 89 | $email = (new MailtrapEmail()) 90 | ->from(new Address('example@YOUR-DOMAIN-HERE.com', 'Mailtrap Test')) // <--- you should use the domain which is linked to template UUID (otherwise you will get 401) 91 | ->replyTo(new Address('reply@YOUR-DOMAIN-HERE.com')) 92 | ->to(new Address('example@gmail.com', 'Jon')) 93 | ->templateUuid('bfa432fd-0000-0000-0000-8493da283a69') 94 | ->templateVariables([ 95 | 'user_name' => 'Jon Bush', 96 | 'next_step_link' => 'https://mailtrap.io/', 97 | 'get_started_link' => 'https://mailtrap.io/', 98 | 'onboarding_video_link' => 'some_video_link', 99 | 'company' => [ 100 | 'name' => 'Best Company', 101 | 'address' => 'Its Address', 102 | ], 103 | 'products' => [ 104 | [ 105 | 'name' => 'Product 1', 106 | 'price' => 100, 107 | ], 108 | [ 109 | 'name' => 'Product 2', 110 | 'price' => 200, 111 | ], 112 | ], 113 | 'isBool' => true, 114 | 'int' => 123 115 | ]) 116 | ; 117 | 118 | $response = $mailtrap->send($email); 119 | 120 | // print all possible information from the response 121 | var_dump($response->getHeaders()); //headers (array) 122 | var_dump($response->getStatusCode()); //status code (int) 123 | var_dump(ResponseHelper::toArray($response)); // body (array) 124 | } catch (Exception $e) { 125 | echo 'Caught exception: ', $e->getMessage(), "\n"; 126 | } 127 | -------------------------------------------------------------------------------- /examples/testing/inboxes.php: -------------------------------------------------------------------------------- 1 | inboxes($accountId); #required parameter is accountId 14 | 15 | /** 16 | * Get a list of inboxes. 17 | * 18 | * GET https://mailtrap.io/api/accounts/{account_id}/inboxes 19 | */ 20 | try { 21 | $response = $sandboxInboxes->getList(); 22 | 23 | // print the response body (array) 24 | var_dump(ResponseHelper::toArray($response)); 25 | } catch (Exception $e) { 26 | echo 'Caught exception: ', $e->getMessage(), "\n"; 27 | } 28 | 29 | 30 | /** 31 | * Create an inbox 32 | * 33 | * POST https://mailtrap.io/api/accounts/{account_id}/projects/{project_id}/inboxes 34 | */ 35 | try { 36 | $projectId = getenv('MAILTRAP_PROJECT_ID'); 37 | $inboxName = 'First inbox'; 38 | 39 | $response = $sandboxInboxes->create($projectId, $inboxName); 40 | 41 | // print the response body (array) 42 | var_dump(ResponseHelper::toArray($response)); 43 | } catch (Exception $e) { 44 | echo 'Caught exception: ', $e->getMessage(), "\n"; 45 | } 46 | 47 | 48 | /** 49 | * Get inbox attributes 50 | * 51 | * GET https://mailtrap.io/api/accounts/{account_id}/inboxes/{inbox_id} 52 | */ 53 | try { 54 | $inboxId = getenv('MAILTRAP_INBOX_ID'); 55 | 56 | $response = $sandboxInboxes->getInboxAttributes($inboxId); 57 | 58 | // print the response body (array) 59 | var_dump(ResponseHelper::toArray($response)); 60 | } catch (Exception $e) { 61 | echo 'Caught exception: ', $e->getMessage(), "\n"; 62 | } 63 | 64 | 65 | /** 66 | * Delete an inbox 67 | * 68 | * DELETE https://mailtrap.io/api/accounts/{account_id}/inboxes/{inbox_id} 69 | */ 70 | try { 71 | $inboxId = getenv('MAILTRAP_INBOX_ID'); 72 | 73 | $response = $sandboxInboxes->delete($inboxId); 74 | 75 | // print the response body (array) 76 | var_dump(ResponseHelper::toArray($response)); 77 | } catch (Exception $e) { 78 | echo 'Caught exception: ', $e->getMessage(), "\n"; 79 | } 80 | 81 | 82 | /** 83 | * Reset email address 84 | * 85 | * PATCH https://mailtrap.io/api/accounts/{account_id}/inboxes/{inbox_id}/reset_email_username 86 | */ 87 | try { 88 | $inboxId = getenv('MAILTRAP_INBOX_ID'); 89 | 90 | $response = $sandboxInboxes->resetEmailAddress($inboxId); 91 | 92 | // print the response body (array) 93 | var_dump(ResponseHelper::toArray($response)); 94 | } catch (Exception $e) { 95 | echo 'Caught exception: ', $e->getMessage(), "\n"; 96 | } 97 | 98 | 99 | /** 100 | * Enable/Disable email address 101 | * 102 | * PATCH https://mailtrap.io/api/accounts/{account_id}/inboxes/{inbox_id}/toggle_email_username 103 | */ 104 | try { 105 | $inboxId = getenv('MAILTRAP_INBOX_ID'); 106 | 107 | $response = $sandboxInboxes->toggleEmailAddress($inboxId); 108 | 109 | // print the response body (array) 110 | var_dump(ResponseHelper::toArray($response)); 111 | } catch (Exception $e) { 112 | echo 'Caught exception: ', $e->getMessage(), "\n"; 113 | } 114 | 115 | 116 | /** 117 | * Reset credentials 118 | * 119 | * PATCH https://mailtrap.io/api/accounts/{account_id}/inboxes/{inbox_id}/reset_credentials 120 | */ 121 | try { 122 | $inboxId = getenv('MAILTRAP_INBOX_ID'); 123 | 124 | $response = $sandboxInboxes->resetSmtpCredentials($inboxId); 125 | 126 | // print the response body (array) 127 | var_dump(ResponseHelper::toArray($response)); 128 | } catch (Exception $e) { 129 | echo 'Caught exception: ', $e->getMessage(), "\n"; 130 | } 131 | 132 | 133 | /** 134 | * Mark as read 135 | * 136 | * PATCH https://mailtrap.io/api/accounts/{account_id}/inboxes/{inbox_id}/all_read 137 | */ 138 | try { 139 | $inboxId = getenv('MAILTRAP_INBOX_ID'); 140 | 141 | $response = $sandboxInboxes->markAsRead($inboxId); 142 | 143 | // print the response body (array) 144 | var_dump(ResponseHelper::toArray($response)); 145 | } catch (Exception $e) { 146 | echo 'Caught exception: ', $e->getMessage(), "\n"; 147 | } 148 | 149 | 150 | /** 151 | * Clean inbox 152 | * 153 | * PATCH https://mailtrap.io/api/accounts/{account_id}/inboxes/{inbox_id}/clean 154 | */ 155 | try { 156 | $inboxId = getenv('MAILTRAP_INBOX_ID'); 157 | 158 | $response = $sandboxInboxes->clean($inboxId); 159 | 160 | // print the response body (array) 161 | var_dump(ResponseHelper::toArray($response)); 162 | } catch (Exception $e) { 163 | echo 'Caught exception: ', $e->getMessage(), "\n"; 164 | } 165 | 166 | 167 | /** 168 | * Update an inbox 169 | * 170 | * PATCH https://mailtrap.io/api/accounts/{account_id}/inboxes/{inbox_id} 171 | */ 172 | try { 173 | $inboxId = getenv('MAILTRAP_INBOX_ID'); 174 | $newInboxName = 'New inbox name'; 175 | $newEmailUsername = 'new-email-username'; 176 | 177 | $response = $sandboxInboxes->update( 178 | $inboxId, 179 | new Inbox($newInboxName, $newEmailUsername) 180 | ); 181 | 182 | // print the response body (array) 183 | var_dump(ResponseHelper::toArray($response)); 184 | } catch (Exception $e) { 185 | echo 'Caught exception: ', $e->getMessage(), "\n"; 186 | } 187 | -------------------------------------------------------------------------------- /examples/testing/messages.php: -------------------------------------------------------------------------------- 1 | messages($accountId, $inboxId); #required parameters are accountId and inboxId 15 | 16 | /** 17 | * Get messages 18 | * Note: if you want to get all messages you need to use "page" param (by default will return only 30 messages per page) 19 | * 20 | * GET https://mailtrap.io/api/accounts/{account_id}/inboxes/{inbox_id}/messages 21 | */ 22 | try { 23 | // not required parameters 24 | $page = 1; // by default 30 messages per page 25 | $search = 'hello'; // it works like case insensitive pattern matching by subject, to_email, to_name 26 | $lastMessageId = 3000000003; // get emails, where primary key is less then this param (does not work with page param) 27 | 28 | $response = $sandboxMessages->getList(); 29 | 30 | // print the response body (array) 31 | var_dump(ResponseHelper::toArray($response)); 32 | } catch (Exception $e) { 33 | echo 'Caught exception: ', $e->getMessage(), "\n"; 34 | } 35 | 36 | 37 | /** 38 | * Show email message 39 | * 40 | * GET https://mailtrap.io/api/accounts/{account_id}/inboxes/{inbox_id}/messages/{message_id} 41 | */ 42 | try { 43 | $messageId = getenv('MAILTRAP_INBOX_MESSAGE_ID'); 44 | 45 | $response = $sandboxMessages->getById($messageId); 46 | 47 | // print the response body (array) 48 | var_dump(ResponseHelper::toArray($response)); 49 | } catch (Exception $e) { 50 | echo 'Caught exception: ', $e->getMessage(), "\n"; 51 | } 52 | 53 | 54 | /** 55 | * Get message source 56 | * 57 | * GET https://mailtrap.io/api/accounts/{account_id}/inboxes/{inbox_id}/messages/{message_id}/body.htmlsource 58 | */ 59 | try { 60 | $messageId = getenv('MAILTRAP_INBOX_MESSAGE_ID'); 61 | 62 | $response = $sandboxMessages->getSource($messageId); 63 | 64 | // print the response body (string) 65 | var_dump(ResponseHelper::toString($response)); 66 | } catch (Exception $e) { 67 | echo 'Caught exception: ', $e->getMessage(), "\n"; 68 | } 69 | 70 | 71 | /** 72 | * Get message as .eml 73 | * 74 | * GET https://mailtrap.io/api/accounts/{account_id}/inboxes/{inbox_id}/messages/{message_id}/body.eml 75 | */ 76 | try { 77 | $messageId = getenv('MAILTRAP_INBOX_MESSAGE_ID'); 78 | 79 | $response = $sandboxMessages->getEml($messageId); 80 | 81 | // print the response body (string) 82 | var_dump(ResponseHelper::toString($response)); 83 | } catch (Exception $e) { 84 | echo 'Caught exception: ', $e->getMessage(), "\n"; 85 | } 86 | 87 | 88 | /** 89 | * Get HTML message 90 | * 91 | * GET https://mailtrap.io/api/accounts/{account_id}/inboxes/{inbox_id}/messages/{message_id}/body.html 92 | */ 93 | try { 94 | $messageId = getenv('MAILTRAP_INBOX_MESSAGE_ID'); 95 | 96 | $response = $sandboxMessages->getHtml($messageId); 97 | 98 | // print the response body (string) 99 | var_dump(ResponseHelper::toString($response)); 100 | } catch (Exception $e) { 101 | echo 'Caught exception: ', $e->getMessage(), "\n"; 102 | } 103 | 104 | 105 | /** 106 | * Get raw message 107 | * 108 | * GET https://mailtrap.io/api/accounts/{account_id}/inboxes/{inbox_id}/messages/{message_id}/body.raw 109 | */ 110 | try { 111 | $messageId = getenv('MAILTRAP_INBOX_MESSAGE_ID'); 112 | 113 | $response = $sandboxMessages->getRaw($messageId); 114 | 115 | // print the response body (string) 116 | var_dump(ResponseHelper::toString($response)); 117 | } catch (Exception $e) { 118 | echo 'Caught exception: ', $e->getMessage(), "\n"; 119 | } 120 | 121 | 122 | /** 123 | * Get text message 124 | * 125 | * GET https://mailtrap.io/api/accounts/{account_id}/inboxes/{inbox_id}/messages/{message_id}/body.txt 126 | */ 127 | try { 128 | $messageId = getenv('MAILTRAP_INBOX_MESSAGE_ID'); 129 | 130 | $response = $sandboxMessages->getText($messageId); 131 | 132 | // print the response body (string) 133 | var_dump(ResponseHelper::toString($response)); 134 | } catch (Exception $e) { 135 | echo 'Caught exception: ', $e->getMessage(), "\n"; 136 | } 137 | 138 | 139 | /** 140 | * Get message HTML analysis 141 | * 142 | * GET https://mailtrap.io/api/accounts/{account_id}/inboxes/{inbox_id}/messages/{message_id}/analyze 143 | */ 144 | try { 145 | $messageId = getenv('MAILTRAP_INBOX_MESSAGE_ID'); 146 | 147 | $response = $sandboxMessages->getHtmlAnalysis($messageId); 148 | 149 | // print the response body (array) 150 | var_dump(ResponseHelper::toArray($response)); 151 | } catch (Exception $e) { 152 | echo 'Caught exception: ', $e->getMessage(), "\n"; 153 | } 154 | 155 | 156 | /** 157 | * Get message spam score 158 | * 159 | * GET https://mailtrap.io/api/accounts/{account_id}/inboxes/{inbox_id}/messages/{message_id}/spam_report 160 | */ 161 | try { 162 | $messageId = getenv('MAILTRAP_INBOX_MESSAGE_ID'); 163 | 164 | $response = $sandboxMessages->getSpamScore($messageId); 165 | 166 | // print the response body (array) 167 | var_dump(ResponseHelper::toArray($response)); 168 | } catch (Exception $e) { 169 | echo 'Caught exception: ', $e->getMessage(), "\n"; 170 | } 171 | 172 | 173 | /** 174 | * Update message (mark as read) 175 | * 176 | * PATCH https://mailtrap.io/api/accounts/{account_id}/inboxes/{inbox_id}/messages/{message_id} 177 | */ 178 | try { 179 | $messageId = getenv('MAILTRAP_INBOX_MESSAGE_ID'); 180 | 181 | $response = $sandboxMessages->markAsRead($messageId); 182 | 183 | // print the response body (array) 184 | var_dump(ResponseHelper::toArray($response)); 185 | } catch (Exception $e) { 186 | echo 'Caught exception: ', $e->getMessage(), "\n"; 187 | } 188 | 189 | 190 | /** 191 | * Delete message 192 | * 193 | * DELETE https://mailtrap.io/api/accounts/{account_id}/inboxes/{inbox_id}/messages/{message_id} 194 | */ 195 | try { 196 | $messageId = getenv('MAILTRAP_INBOX_MESSAGE_ID'); 197 | 198 | $response = $sandboxMessages->delete($messageId); 199 | 200 | // print the response body (array) 201 | var_dump(ResponseHelper::toArray($response)); 202 | } catch (Exception $e) { 203 | echo 'Caught exception: ', $e->getMessage(), "\n"; 204 | } 205 | -------------------------------------------------------------------------------- /examples/testing/projects.php: -------------------------------------------------------------------------------- 1 | projects($accountId); #required parameter is accountId 13 | 14 | /** 15 | * List projects and their inboxes to which the API token has access. 16 | * 17 | * GET https://mailtrap.io/api/accounts/{account_id}/projects 18 | */ 19 | try { 20 | $response = $sandboxProjects->getList(); 21 | 22 | // print the response body (array) 23 | var_dump(ResponseHelper::toArray($response)); 24 | } catch (Exception $e) { 25 | echo 'Caught exception: ', $e->getMessage(), "\n"; 26 | } 27 | 28 | 29 | /** 30 | * Get the project and its inboxes. 31 | * 32 | * GET https://mailtrap.io/api/accounts/{account_id}/projects/{project_id} 33 | */ 34 | try { 35 | $projectId = getenv('MAILTRAP_PROJECT_ID'); 36 | 37 | $response = $sandboxProjects->getById($projectId); 38 | 39 | // print the response body (array) 40 | var_dump(ResponseHelper::toArray($response)); 41 | } catch (Exception $e) { 42 | echo 'Caught exception: ', $e->getMessage(), "\n"; 43 | } 44 | 45 | /** 46 | * Create project 47 | * The project name is min 2 characters and max 100 characters long. 48 | * 49 | * POST https://mailtrap.io/api/accounts/{account_id}/projects 50 | */ 51 | try { 52 | $projectName = 'Some project name'; 53 | 54 | $response = $sandboxProjects->create($projectName); 55 | 56 | // print the response body (array) 57 | var_dump(ResponseHelper::toArray($response)); 58 | } catch (Exception $e) { 59 | echo 'Caught exception: ', $e->getMessage(), "\n"; 60 | } 61 | 62 | 63 | /** 64 | * Update project 65 | * The project name is min 2 characters and max 100 characters long. 66 | * 67 | * PATCH https://mailtrap.io/api/accounts/{account_id}/projects/{project_id} 68 | */ 69 | try { 70 | $projectId = getenv('MAILTRAP_PROJECT_ID'); 71 | $newProjectName = 'New project name'; 72 | 73 | $response = $sandboxProjects->updateName($projectId, $newProjectName); 74 | 75 | // print the response body (array) 76 | var_dump(ResponseHelper::toArray($response)); 77 | } catch (Exception $e) { 78 | echo 'Caught exception: ', $e->getMessage(), "\n"; 79 | } 80 | 81 | 82 | /** 83 | * Delete project and its inboxes. 84 | * 85 | * DELETE https://mailtrap.io/api/accounts/{account_id}/projects/{project_id} 86 | */ 87 | try { 88 | $projectId = getenv('MAILTRAP_PROJECT_ID'); 89 | 90 | $response = $sandboxProjects->delete($projectId); 91 | 92 | // print the response body (array) 93 | var_dump(ResponseHelper::toArray($response)); 94 | } catch (Exception $e) { 95 | echo 'Caught exception: ', $e->getMessage(), "\n"; 96 | } 97 | 98 | 99 | -------------------------------------------------------------------------------- /psalm.xml: -------------------------------------------------------------------------------- 1 | 2 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /src/AbstractMailtrapClient.php: -------------------------------------------------------------------------------- 1 | initByName($name, $arguments); 23 | } catch (InvalidArgumentException) { 24 | throw new BadMethodCallException(sprintf('%s -> undefined method called: "%s"', static::class, $name)); 25 | } 26 | } 27 | 28 | public function getConfig(): ConfigInterface 29 | { 30 | return $this->config; 31 | } 32 | 33 | private function getClassByName(string $name): ?string 34 | { 35 | /** @psalm-suppress UndefinedConstant */ 36 | return !empty(static::API_MAPPING[$name]) ? static::API_MAPPING[$name] : null; 37 | } 38 | 39 | private function initByName(string $name, $arguments) 40 | { 41 | $className = $this->getClassByName($name); 42 | if (null === $className) { 43 | throw new InvalidArgumentException(sprintf('%s -> undefined api instance called: "%s"', static::class, $name)); 44 | } 45 | 46 | /** @psalm-suppress LessSpecificReturnStatement */ 47 | return new $className($this->getConfig(), ...$arguments); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/Api/AbstractApi.php: -------------------------------------------------------------------------------- 1 | config = $config; 31 | $this->httpClient = $this->config->getHttpClientBuilder()->getHttpClient(); 32 | } 33 | 34 | protected function httpGet(string $path, array $parameters = [], array $requestHeaders = []): ResponseInterface 35 | { 36 | if (count($parameters) > 0) { 37 | $path .= '?' . $this->normalizeArrayParams($parameters); 38 | } 39 | 40 | return $this->httpClient->get($this->addDefaultScheme($path), $requestHeaders); 41 | } 42 | 43 | protected function httpPost(string $path, array $requestHeaders = [], ?array $body = null): ResponseInterface 44 | { 45 | return $this->httpClient->post( 46 | $this->addDefaultScheme($path), 47 | $requestHeaders, 48 | !empty($body) ? $this->jsonEncode($body) : null 49 | ); 50 | } 51 | 52 | protected function httpPut(string $path, array $requestHeaders = [], ?array $body = null): ResponseInterface 53 | { 54 | return $this->httpClient->put( 55 | $this->addDefaultScheme($path), 56 | $requestHeaders, 57 | !empty($body) ? $this->jsonEncode($body) : null 58 | ); 59 | } 60 | 61 | protected function httpPatch(string $path, array $requestHeaders = [], ?array $body = null): ResponseInterface 62 | { 63 | return $this->httpClient->patch( 64 | $this->addDefaultScheme($path), 65 | $requestHeaders, 66 | !empty($body) ? $this->jsonEncode($body) : null 67 | ); 68 | } 69 | 70 | protected function httpDelete(string $path, array $requestHeaders = [], ?array $body = null): ResponseInterface 71 | { 72 | return $this->httpClient->delete( 73 | $this->addDefaultScheme($path), 74 | $requestHeaders, 75 | !empty($body) ? $this->jsonEncode($body) : null 76 | ); 77 | } 78 | 79 | protected function getHost(): string 80 | { 81 | return $this->config->getHost() ?: self::DEFAULT_HOST; 82 | } 83 | 84 | protected function handleResponse(ResponseInterface $response): ResponseInterface 85 | { 86 | if (!$this->config->isResponseThrowOnError()) { 87 | return $response; 88 | } 89 | 90 | $statusCode = $response->getStatusCode(); 91 | switch (true) { 92 | case $statusCode >= 200 && $statusCode < 300: 93 | // Everything fine 94 | break; 95 | case $statusCode >= 400 && $statusCode < 500: 96 | throw HttpClientException::createFromResponse($response); 97 | case $statusCode >= 500: 98 | throw new HttpServerException( 99 | sprintf('Internal Server Error. HTTP response code ("%d") received from the API server. Retry later or contact support.', $statusCode), 100 | $statusCode 101 | ); 102 | default: 103 | throw new HttpException( 104 | sprintf('An unexpected error occurred. HTTP response code ("%d") received.', $statusCode), 105 | $statusCode 106 | ); 107 | 108 | } 109 | 110 | return $response; 111 | } 112 | 113 | /** 114 | * @param mixed $value 115 | * @param int|null $flags 116 | * @param int $maxDepth 117 | * 118 | * @return string 119 | */ 120 | private function jsonEncode(mixed $value, ?int $flags = null, int $maxDepth = 512): string 121 | { 122 | $flags ??= \JSON_HEX_TAG | \JSON_HEX_APOS | \JSON_HEX_AMP | \JSON_HEX_QUOT | \JSON_PRESERVE_ZERO_FRACTION; 123 | 124 | try { 125 | $value = json_encode($value, $flags | \JSON_THROW_ON_ERROR, $maxDepth); 126 | } catch (\JsonException $e) { 127 | throw new InvalidArgumentException('Invalid value for "json" option: ' . $e->getMessage()); 128 | } 129 | 130 | return $value; 131 | } 132 | 133 | private function addDefaultScheme(string $path): string 134 | { 135 | return empty(parse_url($path, PHP_URL_SCHEME)) ? 'https://' . $path : $path; 136 | } 137 | 138 | /** 139 | * Mailtrap API doesn't support array numeric values in GET params like that - inbox_ids[0]=1001&inbox_ids[1]=2002 140 | * that's why we need to do some normalization to use without numbers inbox_ids[]=1001&inbox_ids[]=2002 141 | * 142 | * @param array $parameters 143 | * 144 | * @return string 145 | */ 146 | private function normalizeArrayParams(array $parameters): string 147 | { 148 | return preg_replace('/%5B\d+%5D/imU', '%5B%5D', http_build_query($parameters, '', '&')); 149 | } 150 | } 151 | -------------------------------------------------------------------------------- /src/Api/AbstractEmails.php: -------------------------------------------------------------------------------- 1 | $this->getStringifierAddress($this->getSender($email->getHeaders())), 26 | 'to' => array_map([$this, 'getStringifierAddress'], $this->getRecipients($email->getHeaders(), $email)), 27 | ]; 28 | 29 | if (null !== $email->getSubject()) { 30 | $payload['subject'] = $email->getSubject(); 31 | } 32 | 33 | if (null !== $email->getTextBody()) { 34 | $payload['text'] = $email->getTextBody(); 35 | } 36 | 37 | if (null !== $email->getHtmlBody()) { 38 | $payload['html'] = $email->getHtmlBody(); 39 | } 40 | 41 | if ($ccEmails = array_map([$this, 'getStringifierAddress'], $email->getCc())) { 42 | $payload['cc'] = $ccEmails; 43 | } 44 | 45 | if ($bccEmails = array_map([$this, 'getStringifierAddress'], $email->getBcc())) { 46 | $payload['bcc'] = $bccEmails; 47 | } 48 | 49 | if ($email->getAttachments()) { 50 | $payload['attachments'] = $this->getAttachments($email); 51 | } 52 | 53 | $headersToBypass = ['received', 'from', 'to', 'cc', 'bcc', 'subject', 'content-type']; 54 | foreach ($email->getHeaders()->all() as $name => $header) { 55 | if (in_array($name, $headersToBypass, true)) { 56 | continue; 57 | } 58 | 59 | switch(true) { 60 | case $header instanceof CustomVariableHeader: 61 | $payload[CustomVariableHeader::VAR_NAME][$header->getNameWithoutPrefix()] = $header->getValue(); 62 | break; 63 | case $header instanceof TemplateVariableHeader: 64 | $payload[TemplateVariableHeader::VAR_NAME][$header->getNameWithoutPrefix()] = $header->getValue(); 65 | break; 66 | case $header instanceof CategoryHeader: 67 | if (!empty($payload[CategoryHeader::VAR_NAME])) { 68 | throw new RuntimeException( 69 | sprintf('Too many "%s" instances present in the email headers. Mailtrap does not accept more than 1 category in the email.', CategoryHeader::class) 70 | ); 71 | } 72 | 73 | $payload[CategoryHeader::VAR_NAME] = $header->getValue(); 74 | break; 75 | case $header instanceof TemplateUuidHeader: 76 | if (!empty($payload[TemplateUuidHeader::VAR_NAME])) { 77 | throw new RuntimeException( 78 | sprintf('Too many "%s" instances present in the email headers. Mailtrap does not accept more than 1 template UUID in the email.', TemplateUuidHeader::class) 79 | ); 80 | } 81 | 82 | $payload[TemplateUuidHeader::VAR_NAME] = $header->getValue(); 83 | break; 84 | default: 85 | $payload['headers'][$header->getName()] = $header->getBodyAsString(); 86 | } 87 | } 88 | 89 | return $payload; 90 | } 91 | 92 | private function getAttachments(Email $email): array 93 | { 94 | $attachments = []; 95 | foreach ($email->getAttachments() as $attachment) { 96 | $headers = $attachment->getPreparedHeaders(); 97 | $filename = $headers->getHeaderParameter('Content-Disposition', 'filename'); 98 | $disposition = $headers->getHeaderBody('Content-Disposition'); 99 | 100 | $att = [ 101 | 'content' => str_replace("\r\n", '', $attachment->bodyToString()), 102 | 'type' => $headers->get('Content-Type')->getBody(), 103 | 'filename' => $filename, 104 | 'disposition' => $disposition, 105 | ]; 106 | 107 | if ('inline' === $disposition) { 108 | $att['content_id'] = $filename; 109 | } 110 | 111 | $attachments[] = $att; 112 | } 113 | 114 | return $attachments; 115 | } 116 | 117 | private function getStringifierAddress(Address $address): array 118 | { 119 | $res = ['email' => $address->getAddress()]; 120 | 121 | if ($address->getName()) { 122 | $res['name'] = $address->getName(); 123 | } 124 | 125 | return $res; 126 | } 127 | 128 | private function getSender(Headers $headers): Address 129 | { 130 | if ($sender = $headers->get('Sender')) { 131 | return $sender->getAddress(); 132 | } 133 | if ($return = $headers->get('Return-Path')) { 134 | return $return->getAddress(); 135 | } 136 | if ($from = $headers->get('From')) { 137 | return $from->getAddresses()[0]; 138 | } 139 | 140 | throw new LogicException('Unable to determine the sender of the message.'); 141 | } 142 | 143 | /** 144 | * @param Headers $headers 145 | * @param Email $email 146 | * 147 | * @return Address[] 148 | */ 149 | private function getRecipients(Headers $headers, Email $email): array 150 | { 151 | $recipients = []; 152 | foreach (['to', 'cc', 'bcc'] as $name) { 153 | foreach ($headers->all($name) as $header) { 154 | foreach ($header->getAddresses() as $address) { 155 | $recipients[] = $address; 156 | } 157 | } 158 | } 159 | 160 | return array_filter( 161 | $recipients, 162 | static fn (Address $address) => false === in_array($address, array_merge($email->getCc(), $email->getBcc()), true) 163 | ); 164 | } 165 | } 166 | -------------------------------------------------------------------------------- /src/Api/BulkSending/BulkSendingInterface.php: -------------------------------------------------------------------------------- 1 | handleResponse( 19 | $this->httpPost($this->getHost() . '/api/send', [], $this->getPayload($email)) 20 | ); 21 | } 22 | 23 | protected function getHost(): string 24 | { 25 | return $this->config->getHost() ?: self::SENDMAIL_BULK_HOST; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/Api/EmailsSendApiInterface.php: -------------------------------------------------------------------------------- 1 | handleResponse( 23 | $this->httpGet($this->getHost() . '/api/accounts') 24 | ); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/Api/General/Contact.php: -------------------------------------------------------------------------------- 1 | handleResponse( 31 | $this->httpGet($this->getBasePath() . '/lists') 32 | ); 33 | } 34 | 35 | /** 36 | * Create a new Contact. 37 | * 38 | * @param CreateContact $contact 39 | * @return ResponseInterface 40 | */ 41 | public function createContact(CreateContact $contact): ResponseInterface 42 | { 43 | return $this->handleResponse( 44 | $this->httpPost(path: $this->getBasePath(), body: ['contact' => $contact->toArray()]) 45 | ); 46 | } 47 | 48 | /** 49 | * Update an existing Contact by ID (UUID). 50 | * 51 | * @param string $contactId 52 | * @param UpdateContact $contact 53 | * @return ResponseInterface 54 | */ 55 | public function updateContactById(string $contactId, UpdateContact $contact): ResponseInterface 56 | { 57 | return $this->updateContact($contactId, $contact); 58 | } 59 | 60 | /** 61 | * Update an existing Contact by Email. 62 | * 63 | * @param string $email 64 | * @param UpdateContact $contact 65 | * @return ResponseInterface 66 | */ 67 | public function updateContactByEmail(string $email, UpdateContact $contact): ResponseInterface 68 | { 69 | return $this->updateContact($email, $contact); 70 | } 71 | 72 | /** 73 | * Delete a Contact by ID (UUID). 74 | * 75 | * @param string $contactId 76 | * @return ResponseInterface 77 | */ 78 | public function deleteContactById(string $contactId): ResponseInterface 79 | { 80 | return $this->deleteContact($contactId); 81 | } 82 | 83 | /** 84 | * Delete a Contact by Email. 85 | * 86 | * @param string $email 87 | * @return ResponseInterface 88 | */ 89 | public function deleteContactByEmail(string $email): ResponseInterface 90 | { 91 | return $this->deleteContact($email); 92 | } 93 | 94 | public function getAccountId(): int 95 | { 96 | return $this->accountId; 97 | } 98 | 99 | /** 100 | * Update an existing Contact. 101 | * 102 | * @param string $contactIdOrEmail 103 | * @param UpdateContact $contact 104 | * @return ResponseInterface 105 | */ 106 | private function updateContact(string $contactIdOrEmail, UpdateContact $contact): ResponseInterface 107 | { 108 | return $this->handleResponse( 109 | $this->httpPut( 110 | path: $this->getBasePath() . '/' . urlencode($contactIdOrEmail), 111 | body: ['contact' => $contact->toArray()] 112 | ) 113 | ); 114 | } 115 | 116 | /** 117 | * Delete a Contact by ID or Email. 118 | * 119 | * @param string $idOrEmail 120 | * @return ResponseInterface 121 | */ 122 | private function deleteContact(string $idOrEmail): ResponseInterface 123 | { 124 | return $this->handleResponse( 125 | $this->httpDelete($this->getBasePath() . '/' . urlencode($idOrEmail)) 126 | ); 127 | } 128 | 129 | private function getBasePath(): string 130 | { 131 | return sprintf('%s/api/accounts/%s/contacts', $this->getHost(), $this->getAccountId()); 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /src/Api/General/GeneralInterface.php: -------------------------------------------------------------------------------- 1 | handleResponse($this->httpGet( 31 | sprintf('%s/api/accounts/%s/permissions/resources', $this->getHost(), $this->getAccountId()) 32 | )); 33 | } 34 | 35 | /** 36 | * Manage user or token permissions. 37 | * If you send a combination of resource_type and resource_id that already exists, the permission is updated. 38 | * If the combination doesn’t exist, the permission is created. 39 | * 40 | * @param int $accountAccessId 41 | * @param Permissions $permissions 42 | * 43 | * @return ResponseInterface 44 | */ 45 | public function update(int $accountAccessId, Permissions $permissions): ResponseInterface 46 | { 47 | return $this->handleResponse($this->httpPut( 48 | sprintf('%s/api/accounts/%s/account_accesses/%s/permissions/bulk', $this->getHost(), $this->getAccountId(), $accountAccessId), 49 | [], 50 | ['permissions' => $this->getPayload($permissions)] 51 | )); 52 | } 53 | 54 | public function getAccountId(): int 55 | { 56 | return $this->accountId; 57 | } 58 | 59 | private function getPayload(Permissions $permissions): array 60 | { 61 | $payload = []; 62 | foreach ($permissions->getAll() as $permission) { 63 | $payload[] = $permission->toArray(); 64 | } 65 | 66 | if (count($payload) === 0) { 67 | throw new RuntimeException('At least one "permission" object should be added to manage user or token'); 68 | } 69 | 70 | return $payload; 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/Api/General/User.php: -------------------------------------------------------------------------------- 1 | 0) { 34 | $parameters['inbox_ids'] = $inboxIds; 35 | } 36 | 37 | if (count($projectIds) > 0) { 38 | $parameters['project_ids'] = $projectIds; 39 | } 40 | 41 | return $this->handleResponse($this->httpGet( 42 | sprintf('%s/api/accounts/%s/account_accesses', $this->getHost(), $this->getAccountId()), 43 | $parameters 44 | )); 45 | } 46 | 47 | /** 48 | * Remove user by their ID. You need to be an account admin/owner for this endpoint to work. 49 | * 50 | * @param int $accountAccessId 51 | * 52 | * @return ResponseInterface 53 | */ 54 | public function delete(int $accountAccessId): ResponseInterface 55 | { 56 | return $this->handleResponse($this->httpDelete( 57 | sprintf('%s/api/accounts/%s/account_accesses/%s', $this->getHost(), $this->getAccountId(), $accountAccessId) 58 | )); 59 | } 60 | 61 | public function getAccountId(): int 62 | { 63 | return $this->accountId; 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/Api/Sandbox/Attachment.php: -------------------------------------------------------------------------------- 1 | $attachmentType 40 | ]; 41 | } 42 | 43 | return $this->handleResponse($this->httpGet( 44 | sprintf( 45 | '%s/api/accounts/%s/inboxes/%s/messages/%s/attachments', 46 | $this->getHost(), 47 | $this->getAccountId(), 48 | $this->getInboxId(), 49 | $messageId 50 | ), 51 | $parameters 52 | )); 53 | } 54 | 55 | /** 56 | * Get message single attachment by id. 57 | * 58 | * @param int $messageId 59 | * @param int $attachmentId 60 | * 61 | * @return ResponseInterface 62 | */ 63 | public function getMessageAttachment(int $messageId, int $attachmentId): ResponseInterface 64 | { 65 | return $this->handleResponse($this->httpGet(sprintf( 66 | '%s/api/accounts/%s/inboxes/%s/messages/%s/attachments/%s', 67 | $this->getHost(), 68 | $this->getAccountId(), 69 | $this->getInboxId(), 70 | $messageId, 71 | $attachmentId 72 | ))); 73 | } 74 | 75 | public function getAccountId(): int 76 | { 77 | return $this->accountId; 78 | } 79 | 80 | public function getInboxId(): int 81 | { 82 | return $this->inboxId; 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /src/Api/Sandbox/Emails.php: -------------------------------------------------------------------------------- 1 | handleResponse( 25 | $this->httpPost(sprintf('%s/api/send/%s', $this->getHost(), $this->getInboxId()), [], $this->getPayload($email)) 26 | ); 27 | } 28 | 29 | protected function getHost(): string 30 | { 31 | return $this->config->getHost() ?: self::SENDMAIL_SANDBOX_HOST; 32 | } 33 | 34 | public function getInboxId(): int 35 | { 36 | return $this->inboxId; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/Api/Sandbox/Inbox.php: -------------------------------------------------------------------------------- 1 | handleResponse($this->httpGet( 31 | sprintf('%s/api/accounts/%s/inboxes', $this->getHost(), $this->getAccountId()) 32 | )); 33 | } 34 | 35 | /** 36 | * Get inbox attributes by inbox id. See the list of attributes in the example 37 | * 38 | * @param int $inboxId 39 | * 40 | * @return ResponseInterface 41 | */ 42 | public function getInboxAttributes(int $inboxId): ResponseInterface 43 | { 44 | return $this->handleResponse($this->httpGet( 45 | sprintf('%s/api/accounts/%s/inboxes/%s', $this->getHost(), $this->getAccountId(), $inboxId) 46 | )); 47 | } 48 | 49 | /** 50 | * Create an inbox in a project. 51 | * 52 | * @param int $projectId 53 | * @param string $inboxName 54 | * 55 | * @return ResponseInterface 56 | */ 57 | public function create(int $projectId, string $inboxName): ResponseInterface 58 | { 59 | return $this->handleResponse( 60 | $this->httpPost( 61 | sprintf('%s/api/accounts/%s/projects/%s/inboxes', $this->getHost(), $this->getAccountId(), $projectId), 62 | [], 63 | ['inbox' => ['name' => $inboxName]] 64 | ) 65 | ); 66 | } 67 | 68 | /** 69 | * Delete an inbox with all its emails. 70 | * 71 | * @param int $inboxId 72 | * 73 | * @return ResponseInterface 74 | */ 75 | public function delete(int $inboxId): ResponseInterface 76 | { 77 | return $this->handleResponse($this->httpDelete( 78 | sprintf('%s/api/accounts/%s/inboxes/%s', $this->getHost(), $this->getAccountId(), $inboxId) 79 | )); 80 | } 81 | 82 | /** 83 | * Update inbox name and/or inbox email username. 84 | * 85 | * @param int $inboxId 86 | * @param InboxRequest $updateInbox 87 | * 88 | * @return ResponseInterface 89 | */ 90 | public function update(int $inboxId, InboxRequest $updateInbox): ResponseInterface 91 | { 92 | return $this->handleResponse($this->httpPatch( 93 | sprintf('%s/api/accounts/%s/inboxes/%s', $this->getHost(), $this->getAccountId(), $inboxId), 94 | [], 95 | ['inbox' => $this->getUpdatePayload($updateInbox)] 96 | )); 97 | } 98 | 99 | /** 100 | * Delete all messages (emails) from inbox. 101 | * 102 | * @param int $inboxId 103 | * 104 | * @return ResponseInterface 105 | */ 106 | public function clean(int $inboxId): ResponseInterface 107 | { 108 | return $this->handleResponse($this->httpPatch( 109 | sprintf('%s/api/accounts/%s/inboxes/%s/clean', $this->getHost(), $this->getAccountId(), $inboxId) 110 | )); 111 | } 112 | 113 | /** 114 | * Mark all messages in the inbox as read. 115 | * 116 | * @param int $inboxId 117 | * 118 | * @return ResponseInterface 119 | */ 120 | public function markAsRead(int $inboxId): ResponseInterface 121 | { 122 | return $this->handleResponse($this->httpPatch( 123 | sprintf('%s/api/accounts/%s/inboxes/%s/all_read', $this->getHost(), $this->getAccountId(), $inboxId) 124 | )); 125 | } 126 | 127 | /** 128 | * Reset SMTP credentials of the inbox. 129 | * 130 | * @param int $inboxId 131 | * 132 | * @return ResponseInterface 133 | */ 134 | public function resetSmtpCredentials(int $inboxId): ResponseInterface 135 | { 136 | return $this->handleResponse($this->httpPatch( 137 | sprintf('%s/api/accounts/%s/inboxes/%s/reset_credentials', $this->getHost(), $this->getAccountId(), $inboxId) 138 | )); 139 | } 140 | 141 | /** 142 | * Turn the email address of the inbox on/off. 143 | * 144 | * @param int $inboxId 145 | * 146 | * @return ResponseInterface 147 | */ 148 | public function toggleEmailAddress(int $inboxId): ResponseInterface 149 | { 150 | return $this->handleResponse($this->httpPatch( 151 | sprintf('%s/api/accounts/%s/inboxes/%s/toggle_email_username', $this->getHost(), $this->getAccountId(), $inboxId) 152 | )); 153 | } 154 | 155 | /** 156 | * Reset username of email address per inbox. 157 | * 158 | * @param int $inboxId 159 | * 160 | * @return ResponseInterface 161 | */ 162 | public function resetEmailAddress(int $inboxId): ResponseInterface 163 | { 164 | return $this->handleResponse($this->httpPatch( 165 | sprintf('%s/api/accounts/%s/inboxes/%s/reset_email_username', $this->getHost(), $this->getAccountId(), $inboxId) 166 | )); 167 | } 168 | 169 | private function getUpdatePayload(InboxRequest $updateInbox): array 170 | { 171 | $result = $updateInbox->toArray(); 172 | if (empty($result)) { 173 | throw new RuntimeException('At least one inbox parameter should be populated ("name" or "email_username")'); 174 | } 175 | 176 | return $result; 177 | } 178 | 179 | public function getAccountId(): int 180 | { 181 | return $this->accountId; 182 | } 183 | } 184 | -------------------------------------------------------------------------------- /src/Api/Sandbox/Message.php: -------------------------------------------------------------------------------- 1 | handleResponse($this->httpGet( 58 | sprintf('%s/api/accounts/%s/inboxes/%s/messages', $this->getHost(), $this->getAccountId(), $this->getInboxId()), 59 | $parameters 60 | )); 61 | } 62 | 63 | /** 64 | * Get email message by ID. 65 | * 66 | * @param int $messageId 67 | * 68 | * @return ResponseInterface 69 | */ 70 | public function getById(int $messageId): ResponseInterface 71 | { 72 | return $this->handleResponse($this->httpGet( 73 | sprintf('%s/api/accounts/%s/inboxes/%s/messages/%s', $this->getHost(), $this->getAccountId(), $this->getInboxId(), $messageId) 74 | )); 75 | } 76 | 77 | /** 78 | * Get a brief spam report by message ID. 79 | * 80 | * @param int $messageId 81 | * 82 | * @return ResponseInterface 83 | */ 84 | public function getSpamScore(int $messageId): ResponseInterface 85 | { 86 | return $this->handleResponse($this->httpGet(sprintf( 87 | '%s/api/accounts/%s/inboxes/%s/messages/%s/spam_report', 88 | $this->getHost(), 89 | $this->getAccountId(), 90 | $this->getInboxId(), 91 | $messageId 92 | ))); 93 | } 94 | 95 | /** 96 | * Get a brief HTML report by message ID. 97 | * 98 | * @param int $messageId 99 | * 100 | * @return ResponseInterface 101 | */ 102 | public function getHtmlAnalysis(int $messageId): ResponseInterface 103 | { 104 | return $this->handleResponse($this->httpGet(sprintf( 105 | '%s/api/accounts/%s/inboxes/%s/messages/%s/analyze', 106 | $this->getHost(), 107 | $this->getAccountId(), 108 | $this->getInboxId(), 109 | $messageId 110 | ))); 111 | } 112 | 113 | /** 114 | * Get text email body, if it exists. 115 | * 116 | * @param int $messageId 117 | * 118 | * @return ResponseInterface 119 | */ 120 | public function getText(int $messageId): ResponseInterface 121 | { 122 | return $this->handleResponse($this->httpGet(sprintf( 123 | '%s/api/accounts/%s/inboxes/%s/messages/%s/body.txt', 124 | $this->getHost(), 125 | $this->getAccountId(), 126 | $this->getInboxId(), 127 | $messageId 128 | ))); 129 | } 130 | 131 | /** 132 | * Get raw email body. 133 | * 134 | * @param int $messageId 135 | * 136 | * @return ResponseInterface 137 | */ 138 | public function getRaw(int $messageId): ResponseInterface 139 | { 140 | return $this->handleResponse($this->httpGet(sprintf( 141 | '%s/api/accounts/%s/inboxes/%s/messages/%s/body.raw', 142 | $this->getHost(), 143 | $this->getAccountId(), 144 | $this->getInboxId(), 145 | $messageId 146 | ))); 147 | } 148 | 149 | /** 150 | * Get formatted HTML email body. Not applicable for plain text emails. 151 | * 152 | * @param int $messageId 153 | * 154 | * @return ResponseInterface 155 | */ 156 | public function getHtml(int $messageId): ResponseInterface 157 | { 158 | return $this->handleResponse($this->httpGet(sprintf( 159 | '%s/api/accounts/%s/inboxes/%s/messages/%s/body.html', 160 | $this->getHost(), 161 | $this->getAccountId(), 162 | $this->getInboxId(), 163 | $messageId 164 | ))); 165 | } 166 | 167 | /** 168 | * Get email message in .eml format. 169 | * 170 | * @param int $messageId 171 | * 172 | * @return ResponseInterface 173 | */ 174 | public function getEml( int $messageId): ResponseInterface 175 | { 176 | return $this->handleResponse($this->httpGet(sprintf( 177 | '%s/api/accounts/%s/inboxes/%s/messages/%s/body.eml', 178 | $this->getHost(), 179 | $this->getAccountId(), 180 | $this->getInboxId(), 181 | $messageId 182 | ))); 183 | } 184 | 185 | /** 186 | * Get HTML source of email. 187 | * 188 | * @param int $messageId 189 | * 190 | * @return ResponseInterface 191 | */ 192 | public function getSource(int $messageId): ResponseInterface 193 | { 194 | return $this->handleResponse($this->httpGet(sprintf( 195 | '%s/api/accounts/%s/inboxes/%s/messages/%s/body.htmlsource', 196 | $this->getHost(), 197 | $this->getAccountId(), 198 | $this->getInboxId(), 199 | $messageId 200 | ))); 201 | } 202 | 203 | /** 204 | * Update message attributes (right now only the is_read attribute is available for modification). 205 | * 206 | * @param int $messageId 207 | * @param bool $isRead 208 | * 209 | * @return ResponseInterface 210 | */ 211 | public function markAsRead(int $messageId, bool $isRead = true): ResponseInterface 212 | { 213 | return $this->handleResponse($this->httpPatch( 214 | sprintf('%s/api/accounts/%s/inboxes/%s/messages/%s', $this->getHost(), $this->getAccountId(), $this->getInboxId(), $messageId), 215 | [], 216 | [ 217 | 'message' => [ 218 | 'is_read' => $isRead 219 | ] 220 | ] 221 | )); 222 | } 223 | 224 | /** 225 | * Delete message from inbox. 226 | * 227 | * @param int $messageId 228 | * 229 | * @return ResponseInterface 230 | */ 231 | public function delete(int $messageId): ResponseInterface 232 | { 233 | return $this->handleResponse($this->httpDelete( 234 | sprintf('%s/api/accounts/%s/inboxes/%s/messages/%s', $this->getHost(), $this->getAccountId(), $this->getInboxId(), $messageId) 235 | )); 236 | } 237 | 238 | public function getAccountId(): int 239 | { 240 | return $this->accountId; 241 | } 242 | 243 | public function getInboxId(): int 244 | { 245 | return $this->inboxId; 246 | } 247 | } 248 | -------------------------------------------------------------------------------- /src/Api/Sandbox/Project.php: -------------------------------------------------------------------------------- 1 | handleResponse($this->httpGet( 29 | sprintf('%s/api/accounts/%s/projects', $this->getHost(), $this->getAccountId()) 30 | )); 31 | } 32 | 33 | /** 34 | * Get the project and its inboxes. 35 | * 36 | * @param int $projectId 37 | * 38 | * @return ResponseInterface 39 | */ 40 | public function getById(int $projectId): ResponseInterface 41 | { 42 | return $this->handleResponse($this->httpGet( 43 | sprintf('%s/api/accounts/%s/projects/%s', $this->getHost(), $this->getAccountId(), $projectId) 44 | )); 45 | } 46 | 47 | /** 48 | * Create a project 49 | * 50 | * @param string $projectName 51 | * 52 | * @return ResponseInterface 53 | */ 54 | public function create(string $projectName): ResponseInterface 55 | { 56 | return $this->handleResponse($this->httpPost( 57 | sprintf('%s/api/accounts/%s/projects', $this->getHost(), $this->getAccountId()), 58 | [], 59 | ['project' => ['name' => $projectName]] 60 | )); 61 | } 62 | 63 | /** 64 | * Delete project and its inboxes. 65 | * 66 | * @param int $projectId 67 | * 68 | * @return ResponseInterface 69 | */ 70 | public function delete(int $projectId): ResponseInterface 71 | { 72 | return $this->handleResponse($this->httpDelete( 73 | sprintf('%s/api/accounts/%s/projects/%s', $this->getHost(), $this->getAccountId(), $projectId) 74 | )); 75 | } 76 | 77 | /** 78 | * Update project name. 79 | * 80 | * @param int $projectId 81 | * @param string $projectName 82 | * 83 | * @return ResponseInterface 84 | */ 85 | public function updateName(int $projectId, string $projectName): ResponseInterface 86 | { 87 | return $this->handleResponse($this->httpPatch( 88 | sprintf('%s/api/accounts/%s/projects/%s', $this->getHost(), $this->getAccountId(), $projectId), 89 | [], 90 | ['project' => ['name' => $projectName]] 91 | )); 92 | } 93 | 94 | public function getAccountId(): int 95 | { 96 | return $this->accountId; 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /src/Api/Sandbox/SandboxInterface.php: -------------------------------------------------------------------------------- 1 | handleResponse( 19 | $this->httpPost($this->getHost() . '/api/send', [], $this->getPayload($email)) 20 | ); 21 | } 22 | 23 | protected function getHost(): string 24 | { 25 | return $this->config->getHost() ?: self::SENDMAIL_TRANSACTIONAL_HOST; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/Api/Sending/SendingInterface.php: -------------------------------------------------------------------------------- 1 | mergeConfigFrom(__DIR__ . '/config/mailtrap-sdk.php', 'services'); 23 | } 24 | 25 | /** 26 | * Bootstrap services. 27 | */ 28 | public function boot(): void 29 | { 30 | // https://laravel.com/docs/9.x/upgrade#symfony-mailer 31 | if ((int) $this->app->version() >= 9) { 32 | Mail::extend('mailtrap-sdk', function () { 33 | return (new MailtrapSdkTransportFactory)->create( 34 | new Dsn( 35 | 'mailtrap+sdk', 36 | config('services.mailtrap-sdk.host'), 37 | config('services.mailtrap-sdk.apiKey'), 38 | null, 39 | null, 40 | config('services.mailtrap-sdk', []) 41 | ) 42 | ); 43 | }); 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/Bridge/Laravel/config/mailtrap-sdk.php: -------------------------------------------------------------------------------- 1 | [ 5 | 'host' => env('MAILTRAP_HOST', 'send.api.mailtrap.io'), 6 | 'apiKey' => env('MAILTRAP_API_KEY'), 7 | 'inboxId' => env('MAILTRAP_INBOX_ID'), 8 | ], 9 | ]; 10 | -------------------------------------------------------------------------------- /src/Bridge/Symfony/README.md: -------------------------------------------------------------------------------- 1 | Mailtrap bridge for Symfony framework [SDK] 2 | =============== 3 | 4 | Provides full mailtrap.io integration for Symfony Mailer. 5 | 6 | ## Installation 7 | If you just want to get started quickly with the SDK library, you should run the following command: 8 | ```bash 9 | composer require railsware/mailtrap-php symfony/http-client nyholm/psr7 symfony/mailer 10 | ``` 11 | 12 | ## Usage 13 | 14 | Add MailtrapTransport into your `config/services.yaml` file 15 | ```yaml 16 | ... 17 | # add more service definitions when explicit configuration is needed 18 | # please note that last definitions always *replace* previous ones 19 | 20 | Mailtrap\Bridge\Transport\MailtrapSdkTransportFactory: 21 | tags: 22 | - { name: 'mailer.transport_factory' } 23 | ``` 24 | 25 | ### Sending 26 | Add or change MAILER_DSN variable inside your `.env` file. Also, you need to change the `YOUR_API_KEY_HERE` placeholder. 27 | ```bash 28 | MAILER_DSN=mailtrap+sdk://YOUR_API_KEY_HERE@default 29 | # or 30 | MAILER_DSN=mailtrap+sdk://YOUR_API_KEY_HERE@send.api.mailtrap.io 31 | ``` 32 | 33 | ### Bulk Sending 34 | Add or change MAILER_DSN variable inside your `.env` file. Also, you need to change the `YOUR_API_KEY_HERE` placeholder. 35 | 36 | More info about bulk sending -> https://help.mailtrap.io/article/113-sending-streams 37 | ```bash 38 | MAILER_DSN=mailtrap+sdk://YOUR_API_KEY_HERE@bulk.api.mailtrap.io 39 | ``` 40 | 41 | ### Sandbox 42 | 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`. 43 | 44 | More info sandbox -> https://help.mailtrap.io/article/109-getting-started-with-mailtrap-email-testing 45 | ```bash 46 | MAILER_DSN=mailtrap+sdk://YOUR_API_KEY_HERE@sandbox.api.mailtrap.io?inboxId=1000001 47 | ``` 48 | 49 | ### Send you first email 50 | 51 | #### CLI command (the mailer:test command was introduced only in Symfony 6.2) 52 | ```bash 53 | php bin/console mailer:test to@example.com 54 | ``` 55 | 56 | #### Controller (base example) 57 | 58 | ```php 59 | transport = $transport; 79 | } 80 | 81 | /** 82 | * @Route(name="send-email", path="/send-email", methods={"GET"}) 83 | * 84 | * @return JsonResponse 85 | */ 86 | public function sendEmail(): JsonResponse 87 | { 88 | $message = (new MailtrapEmail()) 89 | ->from('from@xample.com') 90 | ->to('to@xample.com') 91 | ->cc('cc@example.com') 92 | ->bcc('bcc@example.com') 93 | ->replyTo('fabien@example.com') 94 | ->priority(Email::PRIORITY_HIGH) 95 | ->subject('Test email') 96 | ->text('text') 97 | ->category('category') 98 | ->customVariables([ 99 | 'var1' => 'value1', 100 | 'var2' => 'value2' 101 | ]) 102 | ; 103 | 104 | $response = $this->transport->send($message); 105 | 106 | return JsonResponse::create(['messageId' => $response->getMessageId()]); 107 | } 108 | 109 | /** 110 | * WARNING! To send using Mailtrap Email Template, you should use the native library and its methods, 111 | * as mail transport validation does not allow you to send emails without ‘html’ or ‘text’ 112 | * 113 | * @Route(name="send-template-email", path="/send-template-email", methods={"GET"}) 114 | * 115 | * @return JsonResponse 116 | */ 117 | public function sendTemplateEmail(): JsonResponse 118 | { 119 | $email = (new MailtrapEmail()) 120 | ->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) 121 | ->replyTo(new Address('reply@YOUR-DOMAIN-HERE.com')) 122 | ->to(new Address('example@gmail.com', 'Jon')) 123 | // when using a template, you should not set a subject, text, HTML, category 124 | // otherwise there will be a validation error from the API side 125 | ->templateUuid('bfa432fd-0000-0000-0000-8493da283a69') 126 | ->templateVariables([ 127 | 'user_name' => 'Jon Bush', 128 | 'next_step_link' => 'https://mailtrap.io/', 129 | 'get_started_link' => 'https://mailtrap.io/', 130 | 'onboarding_video_link' => 'some_video_link', 131 | 'company' => [ 132 | 'name' => 'Best Company', 133 | 'address' => 'Its Address', 134 | ], 135 | 'products' => [ 136 | [ 137 | 'name' => 'Product 1', 138 | 'price' => 100, 139 | ], 140 | [ 141 | 'name' => 'Product 2', 142 | 'price' => 200, 143 | ], 144 | ], 145 | 'isBool' => true, 146 | 'int' => 123 147 | ]) 148 | ; 149 | 150 | $response = MailtrapClient::initSendingEmails( 151 | apiKey: env('MAILTRAP_API_KEY') // your API token from here https://mailtrap.io/api-tokens 152 | )->send($email); 153 | 154 | return JsonResponse::create(ResponseHelper::toArray($response)); 155 | } 156 | } 157 | ``` 158 | 159 | ## Resources 160 | 161 | * [Symfony mailer documentation](https://symfony.com/doc/current/mailer.html) 162 | 163 | ### Notes 164 | > If you are looking for a quicker setup and don’t need advanced features like templates or custom variables, 165 | > you can also use the [MailtrapMailer Symfony package](https://symfony.com/packages/MailtrapMailer), which provides a simple bridge for sending emails 166 | > through Mailtrap using standard Symfony Mailer configuration. 167 | -------------------------------------------------------------------------------- /src/Bridge/Transport/MailtrapSdkTransport.php: -------------------------------------------------------------------------------- 1 | getEndpoint()); 38 | } 39 | 40 | protected function doSend(SentMessage $message): void 41 | { 42 | try { 43 | $email = MessageConverter::toEmail($message->getOriginalMessage()); 44 | $envelope = $message->getEnvelope(); 45 | 46 | // overrides from the envelope 47 | $email->from($envelope->getSender()); 48 | $envelopeRecipients = $this->getEnvelopeRecipients($email, $envelope); 49 | if (!empty($envelopeRecipients)) { 50 | foreach ($envelopeRecipients as $envelopeRecipient) { 51 | $email->addTo($envelopeRecipient); 52 | } 53 | } 54 | 55 | $response = $this->emailsSendApiLayer->send($email); 56 | 57 | $body = ResponseHelper::toArray($response); 58 | $message->setMessageId(implode(',', $body['message_ids'])); 59 | } catch (\Exception $e) { 60 | throw new RuntimeException( 61 | sprintf('Unable to send a message with the "%s" transport: ', __CLASS__) . $e->getMessage(), 0, $e 62 | ); 63 | } 64 | } 65 | 66 | private function getEndpoint(): string 67 | { 68 | $inboxId = null; 69 | if ($this->emailsSendApiLayer instanceof SandboxEmails) { 70 | $inboxId = $this->emailsSendApiLayer->getInboxId(); 71 | } 72 | 73 | return $this->config->getHost() . (null === $inboxId ? '' : '?inboxId=' . $inboxId); 74 | } 75 | 76 | private function getEnvelopeRecipients(Email $email, Envelope $envelope): array 77 | { 78 | return array_filter( 79 | $envelope->getRecipients(), 80 | static fn (Address $address) => false === in_array($address, array_merge($email->getTo(), $email->getCc(), $email->getBcc()), true) 81 | ); 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /src/Bridge/Transport/MailtrapSdkTransportFactory.php: -------------------------------------------------------------------------------- 1 | getScheme(), $this->getSupportedSchemes())) { 28 | throw new UnsupportedSchemeException($dsn, 'mailtrap+sdk', $this->getSupportedSchemes()); 29 | } 30 | 31 | $inboxId = !empty($dsn->getOption('inboxId')) ? (int) $dsn->getOption('inboxId') : null; 32 | $config = (new Config($this->getUser($dsn))) 33 | ->setHost('default' === $dsn->getHost() ? AbstractApi::SENDMAIL_TRANSACTIONAL_HOST : $dsn->getHost()) 34 | ->setHttpClient(null === $this->client ? null : new Psr18Client($this->client)) 35 | ; 36 | 37 | $emailsSendMailtrapClient = $this->getEmailsSendMailTrapClient($config); 38 | if ($emailsSendMailtrapClient instanceof MailtrapSandboxClient) { 39 | if (null === $inboxId) { 40 | throw new RuntimeException( 41 | 'You cannot send an email to a sandbox with an empty "inboxId" parameter. Example -> "MAILER_DSN=mailtrap+sdk://APIKEY@sandbox.api.mailtrap.io?inboxId=1234"' 42 | ); 43 | } 44 | 45 | $emailsSendApiLayer = $emailsSendMailtrapClient->emails($inboxId); 46 | } else { 47 | $emailsSendApiLayer = $emailsSendMailtrapClient->emails(); 48 | } 49 | 50 | return new MailtrapSdkTransport($emailsSendApiLayer, $config, $this->dispatcher, $this->logger); 51 | } 52 | 53 | protected function getSupportedSchemes(): array 54 | { 55 | return ['mailtrap+sdk']; 56 | } 57 | 58 | private function getEmailsSendMailTrapClient(Config $config): EmailsSendMailtrapClientInterface 59 | { 60 | $layer = $this->determineLayerNameByHost($config->getHost()); 61 | 62 | return (new MailtrapClient($config))->{$layer}(); 63 | } 64 | 65 | private function determineLayerNameByHost(string $host): string 66 | { 67 | $hostLayers = [ 68 | AbstractApi::SENDMAIL_TRANSACTIONAL_HOST => MailtrapClient::LAYER_TRANSACTIONAL_SENDING, 69 | AbstractApi::SENDMAIL_BULK_HOST => MailtrapClient::LAYER_BULK_SENDING, 70 | AbstractApi::SENDMAIL_SANDBOX_HOST => MailtrapClient::LAYER_SANDBOX, 71 | ]; 72 | 73 | foreach ($hostLayers as $hostKey => $layer) { 74 | if (stripos($host, $hostKey) !== false) { 75 | return $layer; 76 | } 77 | } 78 | 79 | throw new UnsupportedHostException( 80 | sprintf( 81 | 'The "%s" host is not supported. Only these are available: %s', 82 | $host, 83 | implode( 84 | ', ', 85 | [ 86 | AbstractApi::SENDMAIL_TRANSACTIONAL_HOST, 87 | AbstractApi::SENDMAIL_BULK_HOST, 88 | AbstractApi::SENDMAIL_SANDBOX_HOST 89 | ] 90 | )) 91 | ); 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /src/Config.php: -------------------------------------------------------------------------------- 1 | apiToken; 32 | } 33 | 34 | public function getHost(): ?string 35 | { 36 | return $this->host; 37 | } 38 | 39 | public function setHost(?string $host): self 40 | { 41 | $this->host = $host; 42 | 43 | return $this; 44 | } 45 | 46 | public function getHttpClientBuilder(): HttpClientBuilderInterface 47 | { 48 | if (null === $this->httpClientBuilder) { 49 | $this->httpClientBuilder = new HttpClientBuilder( 50 | $this->apiToken, 51 | $this->httpClient, 52 | $this->requestFactory, 53 | $this->streamFactory 54 | ); 55 | } 56 | 57 | return $this->httpClientBuilder; 58 | } 59 | 60 | public function setHttpClientBuilder(?HttpClientBuilderInterface $httpClientBuilder): self 61 | { 62 | $this->httpClientBuilder = $httpClientBuilder; 63 | 64 | return $this; 65 | } 66 | 67 | public function setHttpClient(?ClientInterface $httpClient): self 68 | { 69 | $this->httpClient = $httpClient; 70 | 71 | return $this; 72 | } 73 | 74 | public function setRequestFactory(?RequestFactoryInterface $requestFactory): self 75 | { 76 | $this->requestFactory = $requestFactory; 77 | 78 | return $this; 79 | } 80 | 81 | public function setStreamFactory(?StreamFactoryInterface $streamFactory): self 82 | { 83 | $this->streamFactory = $streamFactory; 84 | 85 | return $this; 86 | } 87 | 88 | public function isResponseThrowOnError(): bool 89 | { 90 | return $this->responseThrowOnError; 91 | } 92 | 93 | /** 94 | * Throw an HttpException if the response returns a status other than 20x (default: true) 95 | * otherwise a response will be returned 96 | * 97 | * @param bool $responseThrowOnError 98 | * 99 | * @return $this 100 | */ 101 | public function setResponseThrowOnError(bool $responseThrowOnError): self 102 | { 103 | $this->responseThrowOnError = $responseThrowOnError; 104 | 105 | return $this; 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /src/ConfigInterface.php: -------------------------------------------------------------------------------- 1 | email; 27 | } 28 | 29 | public function getFields(): array 30 | { 31 | return $this->fields; 32 | } 33 | 34 | public function getListIds(): array 35 | { 36 | return $this->listIds; 37 | } 38 | 39 | public function toArray(): array 40 | { 41 | return [ 42 | 'email' => $this->getEmail(), 43 | 'fields' => $this->getFields(), 44 | 'list_ids' => $this->getListIds(), 45 | ]; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/DTO/Request/Contact/UpdateContact.php: -------------------------------------------------------------------------------- 1 | email; 34 | } 35 | 36 | public function getFields(): array 37 | { 38 | return $this->fields; 39 | } 40 | 41 | public function getListIdsIncluded(): array 42 | { 43 | return $this->listIdsIncluded; 44 | } 45 | 46 | public function getListIdsExcluded(): array 47 | { 48 | return $this->listIdsExcluded; 49 | } 50 | 51 | public function getUnsubscribed(): ?bool 52 | { 53 | return $this->unsubscribed; 54 | } 55 | 56 | public function toArray(): array 57 | { 58 | return array_filter( 59 | [ 60 | 'email' => $this->getEmail(), 61 | 'fields' => $this->getFields(), 62 | 'list_ids_included' => $this->getListIdsIncluded(), 63 | 'list_ids_excluded' => $this->getListIdsExcluded(), 64 | 'unsubscribed' => $this->getUnsubscribed(), 65 | ], 66 | fn($value) => $value !== null 67 | ); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/DTO/Request/Inbox.php: -------------------------------------------------------------------------------- 1 | name; 22 | } 23 | 24 | /** 25 | * @return string|null 26 | */ 27 | public function getEmailUsername(): ?string 28 | { 29 | return $this->emailUsername; 30 | } 31 | 32 | public function toArray(): array 33 | { 34 | $array = []; 35 | 36 | if (!empty($this->getName())) { 37 | $array['name'] = $this->getName(); 38 | } 39 | 40 | if (!empty($this->getEmailUsername())) { 41 | $array['email_username'] = $this->getEmailUsername(); 42 | } 43 | 44 | return $array; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/DTO/Request/Permission/CreateOrUpdatePermission.php: -------------------------------------------------------------------------------- 1 | resourceId = (string) $resourceId; 24 | $this->resourceType = $resourceType; 25 | $this->accessLevel = (string) $accessLevel; 26 | } 27 | 28 | public function getResourceId(): string 29 | { 30 | return $this->resourceId; 31 | } 32 | 33 | public function getResourceType(): string 34 | { 35 | return $this->resourceType; 36 | } 37 | 38 | public function getAccessLevel(): string 39 | { 40 | return $this->accessLevel; 41 | } 42 | 43 | public function toArray(): array 44 | { 45 | return [ 46 | 'resource_id' => $this->getResourceId(), 47 | 'resource_type' => $this->getResourceType(), 48 | 'access_level' => $this->getAccessLevel(), 49 | ]; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/DTO/Request/Permission/DestroyPermission.php: -------------------------------------------------------------------------------- 1 | resourceId = (string) $resourceId; 22 | $this->resourceType = $resourceType; 23 | } 24 | 25 | public function getResourceId(): string 26 | { 27 | return $this->resourceId; 28 | } 29 | 30 | public function getResourceType(): string 31 | { 32 | return $this->resourceType; 33 | } 34 | 35 | public function toArray(): array 36 | { 37 | return [ 38 | 'resource_id' => $this->getResourceId(), 39 | 'resource_type' => $this->getResourceType(), 40 | '_destroy' => true, 41 | ]; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/DTO/Request/Permission/PermissionInterface.php: -------------------------------------------------------------------------------- 1 | add($permission); 21 | } 22 | } 23 | 24 | public function add(PermissionInterface $permission): Permissions 25 | { 26 | $this->permissions[] = $permission; 27 | 28 | return $this; 29 | } 30 | 31 | /** 32 | * @return PermissionInterface[] 33 | */ 34 | public function getAll(): array 35 | { 36 | return $this->permissions; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/DTO/Request/RequestInterface.php: -------------------------------------------------------------------------------- 1 | setValue($value); 25 | } 26 | 27 | /** 28 | * @param string $body 29 | */ 30 | public function setBody($body): void 31 | { 32 | $this->setValue($body); 33 | } 34 | 35 | /** 36 | * @psalm-suppress MethodSignatureMismatch 37 | */ 38 | public function getBody(): string 39 | { 40 | return $this->getValue(); 41 | } 42 | 43 | /** 44 | * Get the (unencoded) value of this header. 45 | */ 46 | public function getValue(): string 47 | { 48 | return $this->value; 49 | } 50 | 51 | /** 52 | * Set the (unencoded) value of this header. 53 | */ 54 | public function setValue(string $value): void 55 | { 56 | $this->value = $value; 57 | } 58 | 59 | /** 60 | * Get the value of this header prepared for rendering. 61 | */ 62 | public function getBodyAsString(): string 63 | { 64 | return $this->encodeWords($this, $this->value); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/EmailHeader/CustomHeaderInterface.php: -------------------------------------------------------------------------------- 1 | setValue($value); 27 | } 28 | 29 | /** 30 | * @param string $body 31 | */ 32 | public function setBody($body): void 33 | { 34 | $this->setValue($body); 35 | } 36 | 37 | /** 38 | * @psalm-suppress MethodSignatureMismatch 39 | */ 40 | public function getBody(): string 41 | { 42 | return $this->getValue(); 43 | } 44 | 45 | /** 46 | * Get the (unencoded) value of this header. 47 | */ 48 | public function getValue(): string 49 | { 50 | return $this->value; 51 | } 52 | 53 | /** 54 | * Set the (unencoded) value of this header. 55 | */ 56 | public function setValue(string $value): void 57 | { 58 | $this->value = $value; 59 | } 60 | 61 | public function getNameWithoutPrefix(): string 62 | { 63 | return substr($this->getName(), strlen(self::NAME_PREFIX)); 64 | } 65 | 66 | /** 67 | * Get the value of this header prepared for rendering. 68 | */ 69 | public function getBodyAsString(): string 70 | { 71 | return $this->encodeWords($this, $this->value); 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/EmailHeader/Template/TemplateUuidHeader.php: -------------------------------------------------------------------------------- 1 | setValue($value); 25 | } 26 | 27 | /** 28 | * @param string $body 29 | */ 30 | public function setBody($body): void 31 | { 32 | $this->setValue($body); 33 | } 34 | 35 | /** 36 | * @psalm-suppress MethodSignatureMismatch 37 | */ 38 | public function getBody(): string 39 | { 40 | return $this->getValue(); 41 | } 42 | 43 | /** 44 | * Get the (unencoded) value of this header. 45 | */ 46 | public function getValue(): string 47 | { 48 | return $this->value; 49 | } 50 | 51 | /** 52 | * Set the (unencoded) value of this header. 53 | */ 54 | public function setValue(string $value): void 55 | { 56 | $this->value = $value; 57 | } 58 | 59 | /** 60 | * Get the value of this header prepared for rendering. 61 | */ 62 | public function getBodyAsString(): string 63 | { 64 | return $this->encodeWords($this, $this->value); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/EmailHeader/Template/TemplateVariableHeader.php: -------------------------------------------------------------------------------- 1 | setValue($value); 29 | } 30 | 31 | public function setBody(mixed $body): void 32 | { 33 | $this->setValue($body); 34 | } 35 | 36 | /** 37 | * @psalm-suppress MethodSignatureMismatch 38 | */ 39 | public function getBody(): mixed 40 | { 41 | return $this->getValue(); 42 | } 43 | 44 | /** 45 | * Get the (unencoded) value of this header. 46 | */ 47 | public function getValue(): mixed 48 | { 49 | return $this->value; 50 | } 51 | 52 | /** 53 | * Set the (unencoded) value of this header. 54 | */ 55 | public function setValue(mixed $value): void 56 | { 57 | $this->value = $value; 58 | } 59 | 60 | public function getNameWithoutPrefix(): string 61 | { 62 | return substr($this->getName(), strlen(self::NAME_PREFIX)); 63 | } 64 | 65 | public function getBodyAsString(): string 66 | { 67 | throw new RuntimeException(__METHOD__ . ' method is not supported for this type of header'); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/EmailsSendMailtrapClientInterface.php: -------------------------------------------------------------------------------- 1 | 'Bad request. Fix errors listed in response before retrying.', 18 | 401 => 'Unauthorized. Make sure you are sending correct credentials with the request before retrying.', 19 | 403 => 'Forbidden. Make sure domain verification process is completed or check your permissions.', 20 | 404 => 'The requested entity has not been found.', 21 | ]; 22 | 23 | public static function createFromResponse(ResponseInterface $response): HttpClientException 24 | { 25 | $errorMsg = ''; 26 | $statusCode = $response->getStatusCode(); 27 | 28 | try { 29 | $body = ResponseHelper::toArray($response); 30 | } catch (JsonException|InvalidTypeException) { 31 | $body['error'] = $response->getBody()->__toString(); 32 | } 33 | 34 | if (isset(self::ERROR_PREFIXES[$statusCode])) { 35 | $errorMsg .= self::ERROR_PREFIXES[$statusCode] . ' '; 36 | } 37 | 38 | $errorMsg .= trim('Errors: ' . self::getErrorMsg(!empty($body['errors']) ? $body['errors'] : $body['error'])); 39 | 40 | return new self ( 41 | !empty($errorMsg) 42 | ? $errorMsg 43 | : sprintf('HTTP response code ("%d") received from the API server (no error info)', $statusCode), 44 | $statusCode 45 | ); 46 | } 47 | 48 | /** 49 | * It can be different structure of errors in the response... 50 | * 51 | * Examples: 52 | * {"errors": ["'to' address is required", "'subject' is required"]} 400 errorS (array) 53 | * {"error": "Incorrect API token"} 401 error (string) 54 | * {"errors": "Access forbidden"} 403 errorS (string) 55 | * {"error": "Not found"} 404 error (string) 56 | * {"errors": {"name":["is too short (minimum is 2 characters)"]}} 422 errorS (array with key name) 57 | * 58 | * 59 | * @param array|string $errors 60 | * 61 | * @return string 62 | */ 63 | public static function getErrorMsg(array|string $errors): string 64 | { 65 | $errorMsg = ''; 66 | if (is_array($errors)) { 67 | foreach ($errors as $key => $value) { 68 | if (is_string($key)) { 69 | // add name of field 70 | $errorMsg .= $key . ' -> '; 71 | } 72 | 73 | $errorMsg .= self::getErrorMsg($value); 74 | } 75 | } else { 76 | $errorMsg .= $errors . '. '; 77 | } 78 | 79 | return $errorMsg; 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/Exception/HttpException.php: -------------------------------------------------------------------------------- 1 | getHeaderLine('Content-Type'), 'application/json')) { 22 | // This can happen when the URL structure changes and the response returns a 404 HTML page. (rare case) 23 | throw new InvalidTypeException(sprintf( 24 | 'Invalid content type in response. "%s" type expected, but received "%s"', 25 | 'application/json', 26 | $response->getHeaderLine('Content-Type') 27 | )); 28 | } 29 | 30 | return json_decode($response->getBody()->__toString(), true, 512, JSON_THROW_ON_ERROR); 31 | } 32 | 33 | public static function toString(ResponseInterface $response): string 34 | { 35 | return $response->getBody()->__toString(); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/HttpClient/HttpClientBuilder.php: -------------------------------------------------------------------------------- 1 | httpClient = $httpClient ?? Psr18ClientDiscovery::find(); 35 | $this->requestFactory = $requestFactory ?? Psr17FactoryDiscovery::findRequestFactory(); 36 | $this->streamFactory = $streamFactory ?? Psr17FactoryDiscovery::findStreamFactory(); 37 | } 38 | 39 | public function getHttpClient(): ClientInterface 40 | { 41 | if (null === $this->pluginClient) { 42 | $plugins = [ 43 | new Plugin\HeaderDefaultsPlugin([ 44 | 'User-Agent' => 'mailtrap-php (https://github.com/railsware/mailtrap-php)', 45 | 'Content-Type' => 'application/json', 46 | ]), 47 | new Plugin\AuthenticationPlugin( 48 | new Bearer($this->apiToken) 49 | ) 50 | ]; 51 | $this->pluginClient = new HttpMethodsClient( 52 | (new PluginClientFactory())->createClient($this->httpClient, $plugins), 53 | $this->requestFactory, 54 | $this->streamFactory 55 | ); 56 | } 57 | 58 | return $this->pluginClient; 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/HttpClient/HttpClientBuilderInterface.php: -------------------------------------------------------------------------------- 1 | Api\BulkSending\Emails::class, 16 | ]; 17 | } 18 | -------------------------------------------------------------------------------- /src/MailtrapClient.php: -------------------------------------------------------------------------------- 1 | MailtrapGeneralClient::class, 29 | self::LAYER_SANDBOX => MailtrapSandboxClient::class, 30 | self::LAYER_TRANSACTIONAL_SENDING => MailtrapSendingClient::class, 31 | self::LAYER_BULK_SENDING => MailtrapBulkSendingClient::class, 32 | ]; 33 | 34 | public static function initSendingEmails( 35 | string $apiKey, 36 | bool $isBulk = false, 37 | bool $isSandbox = false, 38 | ?int $inboxId = null, 39 | ): EmailsSendApiInterface { 40 | $client = new self(new Config($apiKey)); 41 | 42 | if ($isBulk && $isSandbox) { 43 | throw new InvalidArgumentException('Bulk mode is not applicable for sandbox API'); 44 | } 45 | 46 | if ($isSandbox) { 47 | return $client->sandbox()->emails($inboxId); 48 | } 49 | 50 | if ($isBulk) { 51 | return $client->bulkSending()->emails(); 52 | } 53 | 54 | return $client->sending()->emails(); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/MailtrapClientInterface.php: -------------------------------------------------------------------------------- 1 | Api\General\Account::class, 19 | 'users' => Api\General\User::class, 20 | 'permissions' => Api\General\Permission::class, 21 | 'contacts' => Api\General\Contact::class, 22 | ]; 23 | } 24 | -------------------------------------------------------------------------------- /src/MailtrapSandboxClient.php: -------------------------------------------------------------------------------- 1 | Api\Sandbox\Emails::class, 20 | 'projects' => Api\Sandbox\Project::class, 21 | 'inboxes' => Api\Sandbox\Inbox::class, 22 | 'attachments' => Api\Sandbox\Attachment::class, 23 | 'messages' => Api\Sandbox\Message::class, 24 | ]; 25 | } 26 | -------------------------------------------------------------------------------- /src/MailtrapSendingClient.php: -------------------------------------------------------------------------------- 1 | Api\Sending\Emails::class, 16 | ]; 17 | } 18 | -------------------------------------------------------------------------------- /src/Mime/MailtrapEmail.php: -------------------------------------------------------------------------------- 1 | getHeaders()->has(TemplateUuidHeader::VAR_NAME)) { 24 | $this->getHeaders()->remove(TemplateUuidHeader::VAR_NAME); 25 | } 26 | 27 | $this->getHeaders()->add(new TemplateUuidHeader($templateUuid)); 28 | 29 | return $this; 30 | } 31 | 32 | public function templateVariable(string $name, mixed $value): self 33 | { 34 | $this->getHeaders()->add(new TemplateVariableHeader($name, $value)); 35 | 36 | return $this; 37 | } 38 | 39 | public function templateVariables(array $variables): self 40 | { 41 | foreach ($variables as $name => $value) { 42 | $this->templateVariable($name, $value); 43 | } 44 | 45 | return $this; 46 | } 47 | 48 | public function category(string $category): self 49 | { 50 | // Only one category is allowed 51 | if ($this->getHeaders()->has(CategoryHeader::VAR_NAME)) { 52 | $this->getHeaders()->remove(CategoryHeader::VAR_NAME); 53 | } 54 | 55 | $this->getHeaders()->add(new CategoryHeader($category)); 56 | 57 | return $this; 58 | } 59 | 60 | public function customVariable(string $name, string $value): self 61 | { 62 | $this->getHeaders()->add(new CustomVariableHeader($name, $value)); 63 | 64 | return $this; 65 | } 66 | 67 | public function customVariables(array $variables): self 68 | { 69 | foreach ($variables as $name => $value) { 70 | $this->customVariable($name, $value); 71 | } 72 | 73 | return $this; 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /tests/Api/BulkSending/BulkEmailsTest.php: -------------------------------------------------------------------------------- 1 | email = $this->getMockBuilder(Emails::class) 23 | ->onlyMethods(['httpPost']) 24 | ->setConstructorArgs([$this->getConfigMock()]) 25 | ->getMock() 26 | ; 27 | } 28 | 29 | protected function tearDown(): void 30 | { 31 | $this->email = null; 32 | 33 | parent::tearDown(); 34 | } 35 | 36 | protected function getHost(): string 37 | { 38 | return AbstractApi::SENDMAIL_BULK_HOST; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /tests/Api/General/AccountTest.php: -------------------------------------------------------------------------------- 1 | account = $this->getMockBuilder(Account::class) 31 | ->onlyMethods(['httpGet']) 32 | ->setConstructorArgs([$this->getConfigMock()]) 33 | ->getMock() 34 | ; 35 | } 36 | 37 | protected function tearDown(): void 38 | { 39 | $this->account = null; 40 | 41 | parent::tearDown(); 42 | } 43 | 44 | public function testValidGetAll(): void 45 | { 46 | $expectedData = [ 47 | [ 48 | "id" => 26730, 49 | "name" => "James", 50 | "access_levels" => [ 51 | 100 52 | ] 53 | ], 54 | [ 55 | "id" => 26731, 56 | "name" => "John", 57 | "access_levels" => [ 58 | 1000 59 | ] 60 | ] 61 | ]; 62 | 63 | $this->account->expects($this->once()) 64 | ->method('httpGet') 65 | ->with(AbstractApi::DEFAULT_HOST . '/api/accounts') 66 | ->willReturn(new Response(200, ['Content-Type' => 'application/json'], json_encode($expectedData))); 67 | 68 | $response = $this->account->getList(); 69 | $responseData = ResponseHelper::toArray($response); 70 | 71 | $this->assertInstanceOf(Response::class, $response); 72 | $this->assertCount(2, $responseData); 73 | $this->assertArrayHasKey('access_levels', array_shift($responseData)); 74 | } 75 | 76 | public function testInvalidGetAll(): void 77 | { 78 | $expectedData = ['error' => 'Incorrect API token']; 79 | 80 | $this->account->expects($this->once()) 81 | ->method('httpGet') 82 | ->with(AbstractApi::DEFAULT_HOST . '/api/accounts') 83 | ->willReturn(new Response(401, ['Content-Type' => 'application/json'], json_encode($expectedData))); 84 | 85 | $this->expectException(HttpClientException::class); 86 | $this->expectExceptionMessage( 87 | 'Unauthorized. Make sure you are sending correct credentials with the request before retrying. Errors: Incorrect API token.' 88 | ); 89 | 90 | $this->account->getList(); 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /tests/Api/General/PermissionTest.php: -------------------------------------------------------------------------------- 1 | permission = $this->getMockBuilder(Permission::class) 34 | ->onlyMethods(['httpGet', 'httpPut']) 35 | ->setConstructorArgs([$this->getConfigMock(), self::FAKE_ACCOUNT_ID]) 36 | ->getMock() 37 | ; 38 | } 39 | 40 | protected function tearDown(): void 41 | { 42 | $this->permission = null; 43 | 44 | parent::tearDown(); 45 | } 46 | 47 | public function testValidGetResources(): void 48 | { 49 | $this->permission->expects($this->once()) 50 | ->method('httpGet') 51 | ->with(AbstractApi::DEFAULT_HOST . '/api/accounts/' . self::FAKE_ACCOUNT_ID . '/permissions/resources') 52 | ->willReturn(new Response(200, ['Content-Type' => 'application/json'], json_encode($this->getExpectedData()))); 53 | 54 | $response = $this->permission->getResources(); 55 | $responseData = ResponseHelper::toArray($response); 56 | 57 | $this->assertInstanceOf(Response::class, $response); 58 | $this->assertCount(2, $responseData); 59 | $this->assertArrayHasKey('resources', array_shift($responseData)); 60 | } 61 | 62 | public function test401InvalidGetResources(): void 63 | { 64 | $this->permission->expects($this->once()) 65 | ->method('httpGet') 66 | ->with(AbstractApi::DEFAULT_HOST . '/api/accounts/' . self::FAKE_ACCOUNT_ID . '/permissions/resources') 67 | ->willReturn(new Response(401, ['Content-Type' => 'application/json'], json_encode(['error' => 'Incorrect API token']))); 68 | 69 | $this->expectException(HttpClientException::class); 70 | $this->expectExceptionMessage( 71 | 'Unauthorized. Make sure you are sending correct credentials with the request before retrying. Errors: Incorrect API token.' 72 | ); 73 | 74 | $this->permission->getResources(); 75 | } 76 | 77 | public function test403InvalidGetResources(): void 78 | { 79 | $this->permission->expects($this->once()) 80 | ->method('httpGet') 81 | ->with(AbstractApi::DEFAULT_HOST . '/api/accounts/' . self::FAKE_ACCOUNT_ID . '/permissions/resources') 82 | ->willReturn(new Response(403, ['Content-Type' => 'application/json'], json_encode(['errors' => 'Access forbidden']))); 83 | 84 | $this->expectException(HttpClientException::class); 85 | $this->expectExceptionMessage( 86 | 'Forbidden. Make sure domain verification process is completed or check your permissions. Errors: Access forbidden.' 87 | ); 88 | 89 | $this->permission->getResources(); 90 | } 91 | 92 | /** 93 | * @dataProvider validUpdateDataProvider 94 | */ 95 | public function testValidUpdate($permissions): void 96 | { 97 | $this->permission->expects($this->once()) 98 | ->method('httpPut') 99 | ->with(AbstractApi::DEFAULT_HOST . '/api/accounts/' . self::FAKE_ACCOUNT_ID . '/account_accesses/' . self::FAKE_ACCOUNT_ACCESS_ID . '/permissions/bulk') 100 | ->willReturn(new Response(200, ['Content-Type' => 'application/json'], json_encode(['message' => 'Permissions have been updated!']))); 101 | 102 | $response = $this->permission->update(self::FAKE_ACCOUNT_ACCESS_ID, $permissions); 103 | $responseData = ResponseHelper::toArray($response); 104 | 105 | $this->assertInstanceOf(Response::class, $response); 106 | $this->assertArrayHasKey('message', $responseData); 107 | $this->assertEquals('Permissions have been updated!', $responseData['message']); 108 | } 109 | 110 | public function testInvalidUpdate(): void 111 | { 112 | $this->expectException(RuntimeException::class); 113 | $this->expectExceptionMessage( 114 | 'At least one "permission" object should be added to manage user or token' 115 | ); 116 | 117 | $emptyPermissions = new Permissions(); 118 | $this->permission->update(self::FAKE_ACCOUNT_ACCESS_ID, $emptyPermissions); 119 | } 120 | 121 | /** 122 | * @dataProvider validUpdateDataProvider 123 | */ 124 | public function testValidGetPayload($permissions, $expectedResult): void 125 | { 126 | $method = new \ReflectionMethod(Permission::class, 'getPayload'); 127 | $method->setAccessible(true); 128 | $payload = $method->invoke(new Permission($this->getConfigMock(), self::FAKE_ACCOUNT_ID), $permissions); 129 | 130 | $this->assertEquals($expectedResult, $payload); 131 | } 132 | 133 | public function validUpdateDataProvider(): iterable 134 | { 135 | // create/update 136 | yield [ 137 | new Permissions( 138 | new CreateOrUpdatePermission(1000001, PermissionInterface::TYPE_PROJECT, 10), 139 | ), 140 | [ 141 | [ 142 | "resource_id" => "1000001", 143 | "resource_type" => "project", 144 | "access_level" => "10" 145 | ] 146 | ] 147 | ]; 148 | 149 | // destroy 150 | yield [ 151 | new Permissions( 152 | new DestroyPermission(1000009, PermissionInterface::TYPE_PROJECT), 153 | ), 154 | [ 155 | [ 156 | "resource_id" => "1000009", 157 | "resource_type" => "project", 158 | "_destroy" => true 159 | ] 160 | ] 161 | ]; 162 | 163 | // create/update and destroy together 164 | yield [ 165 | new Permissions( 166 | new CreateOrUpdatePermission(1000001, PermissionInterface::TYPE_PROJECT, 10), 167 | new CreateOrUpdatePermission(2000002, PermissionInterface::TYPE_INBOX, 100), 168 | new DestroyPermission(1000009, PermissionInterface::TYPE_PROJECT), 169 | ), 170 | [ 171 | [ 172 | "resource_id" => "1000001", 173 | "resource_type" => "project", 174 | "access_level" => "10" 175 | ], 176 | [ 177 | "resource_id" => "2000002", 178 | "resource_type" => "inbox", 179 | "access_level" => "100" 180 | ], 181 | [ 182 | "resource_id" => "1000009", 183 | "resource_type" => "project", 184 | "_destroy" => true 185 | ] 186 | ] 187 | ]; 188 | } 189 | 190 | private function getExpectedData() 191 | { 192 | return [ 193 | [ 194 | "id" => 4001, 195 | "name" => "My First Project", 196 | "type" => "project", 197 | "access_level" => 1, 198 | "resources" => [ 199 | [ 200 | "id" => 3816, 201 | "name" => "My First Inbox", 202 | "type" => "inbox", 203 | "access_level" => 100, 204 | "resources" => [ 205 | ] 206 | ] 207 | ] 208 | ], 209 | [ 210 | "id" => 4002, 211 | "name" => "My Second Project", 212 | "type" => "project", 213 | "access_level" => 1, 214 | "resources" => [ 215 | [ 216 | "id" => 3820, 217 | "name" => "My Second Inbox", 218 | "type" => "inbox", 219 | "access_level" => 100, 220 | "resources" => [ 221 | ] 222 | ] 223 | ] 224 | ] 225 | ]; 226 | } 227 | } 228 | -------------------------------------------------------------------------------- /tests/Api/General/UserTest.php: -------------------------------------------------------------------------------- 1 | user = $this->getMockBuilder(User::class) 32 | ->onlyMethods(['httpGet', 'httpDelete']) 33 | ->setConstructorArgs([$this->getConfigMock(), self::FAKE_ACCOUNT_ID]) 34 | ->getMock() 35 | ; 36 | } 37 | 38 | protected function tearDown(): void 39 | { 40 | $this->user = null; 41 | 42 | parent::tearDown(); 43 | } 44 | 45 | public function testValidGetListWithoutFilters(): void 46 | { 47 | $this->user->expects($this->once()) 48 | ->method('httpGet') 49 | ->with(AbstractApi::DEFAULT_HOST . '/api/accounts/' . self::FAKE_ACCOUNT_ID . '/account_accesses') 50 | ->willReturn(new Response(200, ['Content-Type' => 'application/json'], json_encode($this->getExpectedData()))); 51 | 52 | $response = $this->user->getList(); 53 | $responseData = ResponseHelper::toArray($response); 54 | 55 | $this->assertInstanceOf(Response::class, $response); 56 | $this->assertCount(3, $responseData); 57 | $this->assertArrayHasKey('specifier_type', array_shift($responseData)); 58 | } 59 | 60 | public function testValidGetListWithFilters(): void 61 | { 62 | $inboxIds = [2090015, 2157025]; 63 | $projectIds = [1515592]; 64 | $expectedResult = $this->getExpectedData(); 65 | 66 | $this->user->expects($this->once()) 67 | ->method('httpGet') 68 | ->with( 69 | AbstractApi::DEFAULT_HOST . '/api/accounts/' . self::FAKE_ACCOUNT_ID . '/account_accesses', 70 | ['inbox_ids' => $inboxIds, 'project_ids' => $projectIds] 71 | ) 72 | ->willReturn(new Response(200, ['Content-Type' => 'application/json'], json_encode(array_slice($expectedResult, 1)))); 73 | 74 | $response = $this->user->getList($inboxIds, $projectIds); 75 | $responseData = ResponseHelper::toArray($response); 76 | 77 | $this->assertInstanceOf(Response::class, $response); 78 | $this->assertCount(2, $responseData); 79 | $this->assertArrayHasKey('specifier_type', array_shift($responseData)); 80 | } 81 | 82 | public function test401InvalidGetList(): void 83 | { 84 | $this->user->expects($this->once()) 85 | ->method('httpGet') 86 | ->with(AbstractApi::DEFAULT_HOST . '/api/accounts/' . self::FAKE_ACCOUNT_ID . '/account_accesses') 87 | ->willReturn(new Response(401, ['Content-Type' => 'application/json'], json_encode(['error' => 'Incorrect API token']))); 88 | 89 | $this->expectException(HttpClientException::class); 90 | $this->expectExceptionMessage( 91 | 'Unauthorized. Make sure you are sending correct credentials with the request before retrying. Errors: Incorrect API token.' 92 | ); 93 | 94 | $this->user->getList(); 95 | } 96 | 97 | public function test403InvalidGetList(): void 98 | { 99 | $this->user->expects($this->once()) 100 | ->method('httpGet') 101 | ->with(AbstractApi::DEFAULT_HOST . '/api/accounts/' . self::FAKE_ACCOUNT_ID . '/account_accesses') 102 | ->willReturn(new Response(403, ['Content-Type' => 'application/json'], json_encode(['errors' => 'Access forbidden']))); 103 | 104 | $this->expectException(HttpClientException::class); 105 | $this->expectExceptionMessage( 106 | 'Forbidden. Make sure domain verification process is completed or check your permissions. Errors: Access forbidden.' 107 | ); 108 | 109 | $this->user->getList(); 110 | } 111 | 112 | public function testValidRemove(): void 113 | { 114 | $this->user->expects($this->once()) 115 | ->method('httpDelete') 116 | ->with(AbstractApi::DEFAULT_HOST . '/api/accounts/' . self::FAKE_ACCOUNT_ID . '/account_accesses/' . self::FAKE_ACCOUNT_ACCESS_ID) 117 | ->willReturn(new Response(200, ['Content-Type' => 'application/json'], json_encode(['id' => self::FAKE_ACCOUNT_ACCESS_ID]))); 118 | 119 | $response = $this->user->delete(self::FAKE_ACCOUNT_ACCESS_ID); 120 | $responseData = ResponseHelper::toArray($response); 121 | 122 | $this->assertInstanceOf(Response::class, $response); 123 | $this->assertArrayHasKey('id', $responseData); 124 | $this->assertEquals(self::FAKE_ACCOUNT_ACCESS_ID, $responseData['id']); 125 | } 126 | 127 | public function test401InvalidRemove(): void 128 | { 129 | $this->user->expects($this->once()) 130 | ->method('httpDelete') 131 | ->with(AbstractApi::DEFAULT_HOST . '/api/accounts/' . self::FAKE_ACCOUNT_ID . '/account_accesses/' . self::FAKE_ACCOUNT_ACCESS_ID) 132 | ->willReturn(new Response(401, ['Content-Type' => 'application/json'], json_encode(['error' => 'Incorrect API token']))); 133 | 134 | $this->expectException(HttpClientException::class); 135 | $this->expectExceptionMessage( 136 | 'Unauthorized. Make sure you are sending correct credentials with the request before retrying. Errors: Incorrect API token.' 137 | ); 138 | 139 | $this->user->delete(self::FAKE_ACCOUNT_ACCESS_ID); 140 | } 141 | 142 | public function test403InvalidRemove(): void 143 | { 144 | $this->user->expects($this->once()) 145 | ->method('httpDelete') 146 | ->with(AbstractApi::DEFAULT_HOST . '/api/accounts/' . self::FAKE_ACCOUNT_ID . '/account_accesses/' . self::FAKE_ACCOUNT_ACCESS_ID) 147 | ->willReturn(new Response(403, ['Content-Type' => 'application/json'], json_encode(['error' => 'Access forbidden']))); 148 | 149 | $this->expectException(HttpClientException::class); 150 | $this->expectExceptionMessage( 151 | 'Forbidden. Make sure domain verification process is completed or check your permissions. Errors: Access forbidden.' 152 | ); 153 | 154 | $this->user->delete(self::FAKE_ACCOUNT_ACCESS_ID); 155 | } 156 | 157 | public function test404InvalidRemove(): void 158 | { 159 | $this->user->expects($this->once()) 160 | ->method('httpDelete') 161 | ->with(AbstractApi::DEFAULT_HOST . '/api/accounts/' . self::FAKE_ACCOUNT_ID . '/account_accesses/' . self::FAKE_ACCOUNT_ACCESS_ID) 162 | ->willReturn(new Response(404, ['Content-Type' => 'application/json'], json_encode(['error' => 'Not Found']))); 163 | 164 | $this->expectException(HttpClientException::class); 165 | $this->expectExceptionMessage( 166 | 'The requested entity has not been found. Errors: Not Found.' 167 | ); 168 | 169 | $this->user->delete(self::FAKE_ACCOUNT_ACCESS_ID); 170 | } 171 | 172 | private function getExpectedData(): array 173 | { 174 | return [ 175 | [ 176 | "id" => 4773, 177 | "specifier_type" => "User", 178 | "resources" => [ 179 | [ 180 | "resource_type" => "account", 181 | "resource_id" => 3229, 182 | "access_level" => 1000 183 | ] 184 | ], 185 | "specifier" => [ 186 | "id" => 2077, 187 | "email" => "james@mailtrap.io", 188 | "name" => "James" 189 | ], 190 | "permissions" => [ 191 | "can_read" => false, 192 | "can_update" => false, 193 | "can_destroy" => false, 194 | "can_leave" => false 195 | ] 196 | ], 197 | [ 198 | "id" => 4775, 199 | "specifier_type" => "User", 200 | "resources" => [ 201 | [ 202 | "resource_type" => "account", 203 | "resource_id" => 3229, 204 | "access_level" => 100 205 | ] 206 | ], 207 | "specifier" => [ 208 | "id" => 3230, 209 | "email" => "john@mailtrap.io", 210 | "name" => "John" 211 | ], 212 | "permissions" => [ 213 | "can_read" => false, 214 | "can_update" => true, 215 | "can_destroy" => true, 216 | "can_leave" => false 217 | ] 218 | ], 219 | [ 220 | "id" => 4776, 221 | "specifier_type" => "Invite", 222 | "resources" => [ 223 | [ 224 | "resource_type" => "project", 225 | "resource_id" => 3938, 226 | "access_level" => 10 227 | ], 228 | [ 229 | "resource_type" => "inbox", 230 | "resource_id" => 3757, 231 | "access_level" => 100 232 | ] 233 | ], 234 | "specifier" => [ 235 | "id" => 64, 236 | "email" => "mary@mailtrap.io" 237 | ], 238 | "permissions" => [ 239 | "can_read" => false, 240 | "can_update" => true, 241 | "can_destroy" => true, 242 | "can_leave" => false 243 | ] 244 | ] 245 | ]; 246 | } 247 | } 248 | -------------------------------------------------------------------------------- /tests/Api/Sandbox/AttachmentTest.php: -------------------------------------------------------------------------------- 1 | attachment = $this->getMockBuilder(Attachment::class) 29 | ->onlyMethods(['httpGet']) 30 | ->setConstructorArgs([$this->getConfigMock(), self::FAKE_ACCOUNT_ID, self::FAKE_INBOX_ID]) 31 | ->getMock() 32 | ; 33 | } 34 | 35 | protected function tearDown(): void 36 | { 37 | $this->attachment = null; 38 | 39 | parent::tearDown(); 40 | } 41 | 42 | public function testGetMessageAttachments(): void 43 | { 44 | $attachmentType = 'inline'; 45 | $this->attachment->expects($this->once()) 46 | ->method('httpGet') 47 | ->with( 48 | sprintf( 49 | '%s/api/accounts/%s/inboxes/%s/messages/%s/attachments', 50 | AbstractApi::DEFAULT_HOST, 51 | self::FAKE_ACCOUNT_ID, 52 | self::FAKE_INBOX_ID, 53 | self::FAKE_MESSAGE_ID, 54 | ), 55 | [ 56 | 'attachment_type' => $attachmentType 57 | ] 58 | ) 59 | ->willReturn(new Response(200, ['Content-Type' => 'application/json'], json_encode($this->getExpectedData()))); 60 | 61 | $response = $this->attachment->getMessageAttachments( 62 | self::FAKE_MESSAGE_ID, 63 | $attachmentType 64 | ); 65 | $responseData = ResponseHelper::toArray($response); 66 | 67 | $this->assertInstanceOf(Response::class, $response); 68 | $this->assertCount(1, $responseData); 69 | $this->assertArrayHasKey('filename', array_shift($responseData)); 70 | } 71 | 72 | public function testGetMessageAttachment(): void 73 | { 74 | $this->attachment->expects($this->once()) 75 | ->method('httpGet') 76 | ->with(sprintf( 77 | '%s/api/accounts/%s/inboxes/%s/messages/%s/attachments/%s', 78 | AbstractApi::DEFAULT_HOST, 79 | self::FAKE_ACCOUNT_ID, 80 | self::FAKE_INBOX_ID, 81 | self::FAKE_MESSAGE_ID, 82 | self::FAKE_MESSAGE_ATTACHMENT_ID 83 | )) 84 | ->willReturn(new Response(200, ['Content-Type' => 'application/json'], json_encode($this->getExpectedData()))); 85 | 86 | $response = $this->attachment->getMessageAttachment( 87 | self::FAKE_MESSAGE_ID, 88 | self::FAKE_MESSAGE_ATTACHMENT_ID 89 | ); 90 | $responseData = ResponseHelper::toArray($response); 91 | 92 | $this->assertInstanceOf(Response::class, $response); 93 | $this->assertCount(1, $responseData); 94 | $this->assertArrayHasKey('filename', array_shift($responseData)); 95 | } 96 | 97 | private function getExpectedData(): array 98 | { 99 | return [ 100 | [ 101 | "id" => self::FAKE_MESSAGE_ATTACHMENT_ID, 102 | "message_id" => self::FAKE_MESSAGE_ID, 103 | "filename" => "test.csv", 104 | "attachment_type" => "inline", 105 | "content_type" => "plain/text", 106 | "content_id" => null, 107 | "transfer_encoding" => null, 108 | "attachment_size" => 0, 109 | "created_at" => "2022-06-02T19:25:54.827Z", 110 | "updated_at" => "2022-06-02T19:25:54.827Z", 111 | "attachment_human_size" => "0 Bytes", 112 | "download_path" => "/api/accounts/3831/inboxes/4394/messages/457/attachments/67/download" 113 | ] 114 | ]; 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /tests/Api/Sending/EmailsTest.php: -------------------------------------------------------------------------------- 1 | email = $this->getMockBuilder(Emails::class) 23 | ->onlyMethods(['httpPost']) 24 | ->setConstructorArgs([$this->getConfigMock()]) 25 | ->getMock() 26 | ; 27 | } 28 | 29 | protected function tearDown(): void 30 | { 31 | $this->email = null; 32 | 33 | parent::tearDown(); 34 | } 35 | 36 | protected function getHost(): string 37 | { 38 | return AbstractApi::SENDMAIL_TRANSACTIONAL_HOST; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /tests/Bridge/Transport/MailtrapSdkTransportFactoryTest.php: -------------------------------------------------------------------------------- 1 | markTestSkipped( 27 | 'The "MailtrapSdkTransportFactoryTest" tests skipped, because "symfony/mailer" package is not installed.' 28 | ); 29 | } 30 | 31 | parent::setUp(); 32 | } 33 | 34 | public function getFactory(): TransportFactoryInterface 35 | { 36 | return new MailtrapSdkTransportFactory($this->getDispatcher(), $this->getClient(), $this->getLogger()); 37 | } 38 | 39 | public function supportsProvider(): iterable 40 | { 41 | yield [ 42 | new Dsn('mailtrap+sdk', 'default'), 43 | true, 44 | ]; 45 | } 46 | 47 | public function createProvider(): iterable 48 | { 49 | $dispatcher = $this->getDispatcher(); 50 | $logger = $this->getLogger(); 51 | $psrClient = new Psr18Client($this->getClient()); 52 | $sendConfig = (new Config(self::USER)) 53 | ->setHttpClient($psrClient) 54 | ->setHost(AbstractApi::SENDMAIL_TRANSACTIONAL_HOST); 55 | $sandboxConfig = (new Config(self::USER)) 56 | ->setHttpClient($psrClient) 57 | ->setHost(AbstractApi::SENDMAIL_SANDBOX_HOST); 58 | $bulkConfig = (new Config(self::USER)) 59 | ->setHttpClient($psrClient) 60 | ->setHost(AbstractApi::SENDMAIL_BULK_HOST); 61 | $inboxId = 1234; 62 | 63 | yield [ 64 | new Dsn('mailtrap+sdk', 'default', self::USER), 65 | new MailtrapSdkTransport( 66 | (new MailtrapClient($sendConfig))->sending()->emails(), 67 | $sendConfig, 68 | $dispatcher, 69 | $logger 70 | ), 71 | ]; 72 | 73 | yield [ 74 | new Dsn('mailtrap+sdk', AbstractApi::SENDMAIL_TRANSACTIONAL_HOST, self::USER), 75 | new MailtrapSdkTransport( 76 | (new MailtrapClient($sendConfig))->sending()->emails(), 77 | $sendConfig, 78 | $dispatcher, 79 | $logger 80 | ), 81 | ]; 82 | 83 | // sandbox 84 | yield [ 85 | new Dsn('mailtrap+sdk', AbstractApi::SENDMAIL_SANDBOX_HOST, self::USER, null, null, ['inboxId' => 1234]), 86 | new MailtrapSdkTransport( 87 | (new MailtrapClient($sandboxConfig))->sandbox()->emails($inboxId), 88 | $sandboxConfig, 89 | $dispatcher, 90 | $logger 91 | ), 92 | ]; 93 | 94 | // bulk sending 95 | yield [ 96 | new Dsn('mailtrap+sdk', AbstractApi::SENDMAIL_BULK_HOST, self::USER), 97 | new MailtrapSdkTransport( 98 | (new MailtrapClient($bulkConfig))->bulkSending()->emails(), 99 | $bulkConfig, 100 | $dispatcher, 101 | $logger 102 | ), 103 | ]; 104 | } 105 | 106 | public function unsupportedSchemeProvider(): iterable 107 | { 108 | yield [ 109 | new Dsn('mailtrap+foo', 'mailtrap', self::USER) 110 | ]; 111 | } 112 | 113 | public function incompleteDsnProvider(): iterable 114 | { 115 | yield [new Dsn('mailtrap+sdk', 'default')]; 116 | } 117 | 118 | public function unsupportedHostsProvider(): iterable 119 | { 120 | yield [new Dsn('mailtrap+sdk', 'invalid_url.api.mailtrap.io', self::USER)]; 121 | yield [new Dsn('mailtrap+sdk', 'mailtrap.io', self::USER)]; 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /tests/Bridge/Transport/MailtrapSdkTransportTest.php: -------------------------------------------------------------------------------- 1 | markTestSkipped( 25 | 'The "MailtrapSdkTransportTest" tests skipped, because "symfony/mailer" package is not installed.' 26 | ); 27 | } 28 | 29 | parent::setUp(); 30 | } 31 | 32 | /** 33 | * @dataProvider getTransportData 34 | */ 35 | public function testToString(MailtrapSdkTransport $transport, string $expected): void 36 | { 37 | $this->assertSame($expected, (string) $transport); 38 | } 39 | 40 | public static function getTransportData(): array 41 | { 42 | $sendConfig = (new Config('key'))->setHost(AbstractApi::SENDMAIL_TRANSACTIONAL_HOST); 43 | $sandboxConfig = (new Config('key'))->setHost(AbstractApi::SENDMAIL_SANDBOX_HOST); 44 | $bulkConfig = (new Config('key'))->setHost(AbstractApi::SENDMAIL_BULK_HOST); 45 | $inboxId = 1234; 46 | 47 | return [ 48 | [ 49 | new MailtrapSdkTransport( 50 | (new MailtrapSendingClient($sendConfig))->emails(), 51 | $sendConfig 52 | ), 53 | sprintf('mailtrap+sdk://%s', AbstractApi::SENDMAIL_TRANSACTIONAL_HOST), 54 | ], 55 | [ 56 | new MailtrapSdkTransport( 57 | (new MailtrapSandboxClient($sandboxConfig))->emails($inboxId), 58 | $sandboxConfig 59 | ), 60 | sprintf('mailtrap+sdk://%s?inboxId=%s', AbstractApi::SENDMAIL_SANDBOX_HOST, $inboxId), 61 | ], 62 | [ 63 | new MailtrapSdkTransport( 64 | (new MailtrapBulkSendingClient($bulkConfig))->emails(), 65 | $bulkConfig 66 | ), 67 | sprintf('mailtrap+sdk://%s', AbstractApi::SENDMAIL_BULK_HOST), 68 | ], 69 | ]; 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /tests/Bridge/Transport/TransportFactoryTestCase.php: -------------------------------------------------------------------------------- 1 | getFactory(); 56 | 57 | $this->assertSame($supports, $factory->supports($dsn)); 58 | } 59 | 60 | /** 61 | * @dataProvider createProvider 62 | */ 63 | public function testCreate(Dsn $dsn, TransportInterface $transport): void 64 | { 65 | $factory = $this->getFactory(); 66 | 67 | $this->assertEquals($transport, $factory->create($dsn)); 68 | 69 | if ('default' === $dsn->getHost()) { 70 | $this->assertStringMatchesFormat('mailtrap+sdk://' . AbstractApi::SENDMAIL_TRANSACTIONAL_HOST, (string) $transport); 71 | } else { 72 | $this->assertStringStartsWith('mailtrap+sdk://' . $dsn->getHost(), (string) $transport); 73 | } 74 | } 75 | 76 | /** 77 | * @dataProvider unsupportedSchemeProvider 78 | */ 79 | public function testUnsupportedSchemeException(Dsn $dsn, string $message = null): void 80 | { 81 | $factory = $this->getFactory(); 82 | 83 | $this->expectException(UnsupportedSchemeException::class); 84 | if (null !== $message) { 85 | $this->expectExceptionMessage($message); 86 | } 87 | 88 | $factory->create($dsn); 89 | } 90 | 91 | /** 92 | * @dataProvider unsupportedHostsProvider 93 | */ 94 | public function testUnsupportedHostsException(Dsn $dsn): void 95 | { 96 | $factory = $this->getFactory(); 97 | 98 | $this->expectException(UnsupportedHostException::class); 99 | 100 | $factory->create($dsn); 101 | } 102 | 103 | /** 104 | * @dataProvider incompleteDsnProvider 105 | */ 106 | public function testIncompleteDsnException(Dsn $dsn): void 107 | { 108 | $factory = $this->getFactory(); 109 | 110 | $this->expectException(IncompleteDsnException::class); 111 | $factory->create($dsn); 112 | } 113 | 114 | protected function getDispatcher(): EventDispatcherInterface 115 | { 116 | return $this->dispatcher ??= $this->createMock(EventDispatcherInterface::class); 117 | } 118 | 119 | protected function getClient(): HttpClientInterface 120 | { 121 | return $this->client ??= $this->createMock(HttpClientInterface::class); 122 | } 123 | 124 | protected function getLogger(): LoggerInterface 125 | { 126 | return $this->logger ??= $this->createMock(LoggerInterface::class); 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /tests/MailtrapBulkSendingClientTest.php: -------------------------------------------------------------------------------- 1 | getConfigMock())]; 34 | } 35 | } 36 | 37 | public function testValidInitBulkSendingEmails(): void 38 | { 39 | $this->assertInstanceOf( 40 | BulkSendingEmails::class, 41 | MailtrapClient::initSendingEmails(apiKey: self::DEFAULT_API_KEY, isBulk: true) 42 | ); 43 | } 44 | 45 | public function testInValidInitBulkSendingEmails(): void 46 | { 47 | $this->expectException(InvalidArgumentException::class); 48 | 49 | MailtrapClient::initSendingEmails(apiKey: self::DEFAULT_API_KEY, isBulk: true, isSandbox: true); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /tests/MailtrapClientTestCase.php: -------------------------------------------------------------------------------- 1 | getMailtrapClientClassName(); 29 | $this->mailtrapClientLayer = new $className($this->createMock(ConfigInterface::class)); 30 | } 31 | 32 | protected function tearDown(): void 33 | { 34 | $this->mailtrapClientLayer = null; 35 | 36 | parent::tearDown(); 37 | } 38 | 39 | /** 40 | * @dataProvider invalidApiClassNameProvider 41 | */ 42 | public function testInvalidApiClassName($name): void 43 | { 44 | $this->expectExceptionObject( 45 | new BadMethodCallException( 46 | sprintf('%s -> undefined method called: "%s"', $this->getMailtrapClientClassName(), $name) 47 | ) 48 | ); 49 | 50 | $this->mailtrapClientLayer->{$name}(); 51 | } 52 | 53 | /** 54 | * @dataProvider mapInstancesProvider 55 | */ 56 | public function testMapInstance($instance): void 57 | { 58 | $this->assertInstanceOf($this->getLayerInterfaceClassName(), $instance); 59 | } 60 | 61 | public function invalidApiClassNameProvider(): array 62 | { 63 | return [['fakeclass1'], ['fakeclass2']]; 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /tests/MailtrapGeneralClientTest.php: -------------------------------------------------------------------------------- 1 | $item) { 30 | yield match ($key) { 31 | 'permissions', 'users', 'contacts' => [new $item($this->getConfigMock(), self::FAKE_ACCOUNT_ID)], 32 | default => [new $item($this->getConfigMock())], 33 | }; 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /tests/MailtrapSandboxClientTest.php: -------------------------------------------------------------------------------- 1 | $item) { 32 | yield match ($key) { 33 | 'emails' => [new $item($this->getConfigMock(), self::FAKE_INBOX_ID)], 34 | 'projects', 'inboxes' => [new $item($this->getConfigMock(), self::FAKE_ACCOUNT_ID)], 35 | 'messages', 'attachments' => [new $item($this->getConfigMock(), self::FAKE_ACCOUNT_ID, self::FAKE_INBOX_ID)], 36 | default => [new $item($this->getConfigMock())], 37 | }; 38 | } 39 | } 40 | 41 | public function testValidInitSandboxSendingEmails(): void 42 | { 43 | $this->assertInstanceOf( 44 | SandboxSendingEmails::class, 45 | MailtrapClient::initSendingEmails(apiKey: self::DEFAULT_API_KEY, isSandbox: true, inboxId: self::FAKE_INBOX_ID) 46 | ); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /tests/MailtrapSendingClientTest.php: -------------------------------------------------------------------------------- 1 | getConfigMock())]; 33 | } 34 | } 35 | 36 | public function testValidInitTransactionSendingEmails(): void 37 | { 38 | $this->assertInstanceOf( 39 | TransactionSendingEmails::class, 40 | MailtrapClient::initSendingEmails(apiKey: self::DEFAULT_API_KEY) 41 | ); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /tests/MailtrapTestCase.php: -------------------------------------------------------------------------------- 1 | getMockBuilder(ClientInterface::class) 28 | ->onlyMethods(['sendRequest']) 29 | ->getMock(); 30 | 31 | $client 32 | ->expects($this->any()) 33 | ->method('sendRequest'); 34 | 35 | return $client; 36 | } 37 | 38 | protected function getConfigMock(): ConfigInterface 39 | { 40 | $config = $this->getMockBuilder(ConfigInterface::class) 41 | ->onlyMethods(['getHttpClientBuilder', 'getApiToken', 'getHost', 'isResponseThrowOnError']) 42 | ->getMock(); 43 | 44 | $config 45 | ->method('getHttpClientBuilder') 46 | ->willReturn($this->getHttpClientBuilderMock()); 47 | 48 | $config 49 | ->method('getApiToken') 50 | ->willReturn(self::DEFAULT_API_KEY); 51 | 52 | $config 53 | ->method('isResponseThrowOnError') 54 | ->willReturn(true); 55 | 56 | return $config; 57 | } 58 | 59 | protected function getHttpClientBuilderMock(): HttpClientBuilderInterface 60 | { 61 | $builder = $this->getMockBuilder(HttpClientBuilderInterface::class) 62 | ->onlyMethods(['getHttpClient']) 63 | ->disableOriginalConstructor() 64 | ->getMock(); 65 | 66 | $builder 67 | ->method('getHttpClient') 68 | ->willReturn($this->getHttpClientMock()); 69 | 70 | return $builder; 71 | } 72 | } 73 | --------------------------------------------------------------------------------