浏览代码

feat: EK-1345: 发送email失败

kevinElken 8 月之前
父节点
当前提交
99584560d2
共有 100 个文件被更改,包括 11920 次插入272 次删除
  1. 14 0
      common/helpers/Email.php
  2. 6 2
      composer.json
  3. 513 269
      composer.lock
  4. 2 1
      console/controllers/ToolController.php
  5. 二进制
      http-client-7.2.zip
  6. 二进制
      mailgun-php-master.zip
  7. 二进制
      psr7-master.zip
  8. 2 0
      vendor/clue/stream-filter/.github/FUNDING.yml
  9. 97 0
      vendor/clue/stream-filter/CHANGELOG.md
  10. 21 0
      vendor/clue/stream-filter/LICENSE
  11. 326 0
      vendor/clue/stream-filter/README.md
  12. 32 0
      vendor/clue/stream-filter/composer.json
  13. 120 0
      vendor/clue/stream-filter/src/CallbackFilter.php
  14. 380 0
      vendor/clue/stream-filter/src/functions.php
  15. 9 0
      vendor/clue/stream-filter/src/functions_include.php
  16. 36 0
      vendor/mailgun/mailgun-php/.php-cs-fixer.php
  17. 452 0
      vendor/mailgun/mailgun-php/CHANGELOG.md
  18. 17 0
      vendor/mailgun/mailgun-php/LICENSE
  19. 364 0
      vendor/mailgun/mailgun-php/README.md
  20. 60 0
      vendor/mailgun/mailgun-php/composer.json
  21. 47 0
      vendor/mailgun/mailgun-php/doc/attachments.md
  22. 101 0
      vendor/mailgun/mailgun-php/doc/examples.md
  23. 367 0
      vendor/mailgun/mailgun-php/doc/index.md
  24. 17 0
      vendor/mailgun/mailgun-php/doc/pagination.md
  25. 7 0
      vendor/mailgun/mailgun-php/phpstan.neon
  26. 21 0
      vendor/mailgun/mailgun-php/phpunit.xml.dist.bak
  27. 125 0
      vendor/mailgun/mailgun-php/psalm.baseline.xml
  28. 16 0
      vendor/mailgun/mailgun-php/psalm.xml
  29. 95 0
      vendor/mailgun/mailgun-php/src/Api/AccountManagement.php
  30. 45 0
      vendor/mailgun/mailgun-php/src/Api/Attachment.php
  31. 549 0
      vendor/mailgun/mailgun-php/src/Api/Domain.php
  32. 142 0
      vendor/mailgun/mailgun-php/src/Api/DomainKeys.php
  33. 524 0
      vendor/mailgun/mailgun-php/src/Api/DomainV4.php
  34. 80 0
      vendor/mailgun/mailgun-php/src/Api/EmailValidation.php
  35. 284 0
      vendor/mailgun/mailgun-php/src/Api/EmailValidationV4.php
  36. 46 0
      vendor/mailgun/mailgun-php/src/Api/Event.php
  37. 241 0
      vendor/mailgun/mailgun-php/src/Api/HttpApi.php
  38. 102 0
      vendor/mailgun/mailgun-php/src/Api/HttpClient.php
  39. 271 0
      vendor/mailgun/mailgun-php/src/Api/Ip.php
  40. 94 0
      vendor/mailgun/mailgun-php/src/Api/Mailboxes.php
  41. 266 0
      vendor/mailgun/mailgun-php/src/Api/MailingList.php
  42. 251 0
      vendor/mailgun/mailgun-php/src/Api/MailingList/Member.php
  43. 250 0
      vendor/mailgun/mailgun-php/src/Api/Message.php
  44. 73 0
      vendor/mailgun/mailgun-php/src/Api/Metrics.php
  45. 98 0
      vendor/mailgun/mailgun-php/src/Api/Pagination.php
  46. 178 0
      vendor/mailgun/mailgun-php/src/Api/Route.php
  47. 112 0
      vendor/mailgun/mailgun-php/src/Api/Stats.php
  48. 103 0
      vendor/mailgun/mailgun-php/src/Api/SubAccounts.php
  49. 87 0
      vendor/mailgun/mailgun-php/src/Api/Suppression.php
  50. 143 0
      vendor/mailgun/mailgun-php/src/Api/Suppression/Bounce.php
  51. 146 0
      vendor/mailgun/mailgun-php/src/Api/Suppression/Complaint.php
  52. 151 0
      vendor/mailgun/mailgun-php/src/Api/Suppression/Unsubscribe.php
  53. 154 0
      vendor/mailgun/mailgun-php/src/Api/Suppression/Whitelist.php
  54. 200 0
      vendor/mailgun/mailgun-php/src/Api/Tag.php
  55. 151 0
      vendor/mailgun/mailgun-php/src/Api/Templates.php
  56. 169 0
      vendor/mailgun/mailgun-php/src/Api/Webhook.php
  57. 33 0
      vendor/mailgun/mailgun-php/src/Assert.php
  58. 21 0
      vendor/mailgun/mailgun-php/src/Exception.php
  59. 174 0
      vendor/mailgun/mailgun-php/src/Exception/HttpClientException.php
  60. 47 0
      vendor/mailgun/mailgun-php/src/Exception/HttpServerException.php
  61. 18 0
      vendor/mailgun/mailgun-php/src/Exception/HydrationException.php
  62. 21 0
      vendor/mailgun/mailgun-php/src/Exception/InvalidArgumentException.php
  63. 21 0
      vendor/mailgun/mailgun-php/src/Exception/UnknownErrorException.php
  64. 217 0
      vendor/mailgun/mailgun-php/src/HttpClient/HttpClientConfigurator.php
  65. 43 0
      vendor/mailgun/mailgun-php/src/HttpClient/Plugin/History.php
  66. 41 0
      vendor/mailgun/mailgun-php/src/HttpClient/Plugin/HistoryTrait.php
  67. 52 0
      vendor/mailgun/mailgun-php/src/HttpClient/Plugin/ReplaceUriPlugin.php
  68. 190 0
      vendor/mailgun/mailgun-php/src/HttpClient/RequestBuilder.php
  69. 44 0
      vendor/mailgun/mailgun-php/src/Hydrator/ArrayHydrator.php
  70. 28 0
      vendor/mailgun/mailgun-php/src/Hydrator/Hydrator.php
  71. 63 0
      vendor/mailgun/mailgun-php/src/Hydrator/ModelHydrator.php
  72. 32 0
      vendor/mailgun/mailgun-php/src/Hydrator/NoopHydrator.php
  73. 284 0
      vendor/mailgun/mailgun-php/src/Mailgun.php
  74. 147 0
      vendor/mailgun/mailgun-php/src/Message/BatchMessage.php
  75. 27 0
      vendor/mailgun/mailgun-php/src/Message/Exceptions/LimitExceeded.php
  76. 31 0
      vendor/mailgun/mailgun-php/src/Message/Exceptions/MissingRequiredParameter.php
  77. 18 0
      vendor/mailgun/mailgun-php/src/Message/Exceptions/RuntimeException.php
  78. 37 0
      vendor/mailgun/mailgun-php/src/Message/Exceptions/TooManyRecipients.php
  79. 530 0
      vendor/mailgun/mailgun-php/src/Message/MessageBuilder.php
  80. 96 0
      vendor/mailgun/mailgun-php/src/Message/README.md
  81. 36 0
      vendor/mailgun/mailgun-php/src/Model/AccountManagement/AccountResponse.php
  82. 91 0
      vendor/mailgun/mailgun-php/src/Model/AccountManagement/HttpSigningKeyResponse.php
  83. 43 0
      vendor/mailgun/mailgun-php/src/Model/AccountManagement/SandboxAuthRecipientsResponse.php
  84. 23 0
      vendor/mailgun/mailgun-php/src/Model/ApiResponse.php
  85. 86 0
      vendor/mailgun/mailgun-php/src/Model/Domain/AbstractDomainResponse.php
  86. 128 0
      vendor/mailgun/mailgun-php/src/Model/Domain/CertStatusResponse.php
  87. 59 0
      vendor/mailgun/mailgun-php/src/Model/Domain/ClickTracking.php
  88. 61 0
      vendor/mailgun/mailgun-php/src/Model/Domain/ConnectionResponse.php
  89. 39 0
      vendor/mailgun/mailgun-php/src/Model/Domain/CreateCredentialResponse.php
  90. 19 0
      vendor/mailgun/mailgun-php/src/Model/Domain/CreateResponse.php
  91. 65 0
      vendor/mailgun/mailgun-php/src/Model/Domain/CredentialResponse.php
  92. 70 0
      vendor/mailgun/mailgun-php/src/Model/Domain/CredentialResponseItem.php
  93. 62 0
      vendor/mailgun/mailgun-php/src/Model/Domain/DeleteCredentialResponse.php
  94. 52 0
      vendor/mailgun/mailgun-php/src/Model/Domain/DeleteResponse.php
  95. 117 0
      vendor/mailgun/mailgun-php/src/Model/Domain/DnsRecord.php
  96. 253 0
      vendor/mailgun/mailgun-php/src/Model/Domain/Domain.php
  97. 123 0
      vendor/mailgun/mailgun-php/src/Model/Domain/DomainKeyResponse.php
  98. 88 0
      vendor/mailgun/mailgun-php/src/Model/Domain/IndexResponse.php
  99. 51 0
      vendor/mailgun/mailgun-php/src/Model/Domain/OpenTracking.php
  100. 82 0
      vendor/mailgun/mailgun-php/src/Model/Domain/ShowResponse.php

+ 14 - 0
common/helpers/Email.php

@@ -2,10 +2,13 @@
 
 namespace common\helpers;
 
+use Mailgun\Mailgun;
 use Yii;
 
 class Email
 {
+    private static string $mailKey = '4f64981556009f8e9fc2e112a65d3be3-67bd41c2-4e17cb05';
+
     /**
      * @param string $email
      * @param string $userName
@@ -27,4 +30,15 @@ class Email
 
         LoggerTool::debug('sendRegistrationEmail' . sprintf('sendRegistrationEmail: %s', json_encode([$email, $userName, $password, $response])));
     }
+
+    public static function sendEmail(string $email, string $userName, string $password)
+    {
+        $mg = Mailgun::create(self::$mailKey);
+        $mg->messages()->send(getenv('DOMAIN') ?: 'DOMAIN', [
+            'from' => 'Elken <no.reply.elken@gmail.com>',
+            'to' => $email,
+            'subject' => \Yii::t('app', 'decRegistrationEmailSubject', []),
+            'html' => \Yii::t('app', 'decRegistrationEmailContent', ['userName' => $userName, 'passWord' => $password]),
+        ]);
+    }
 }

+ 6 - 2
composer.json

@@ -29,7 +29,10 @@
         "monolog/monolog": "*",
         "tecnickcom/tcpdf": "^6.4",
         "ext-curl": "*",
-        "smladeoye/yii2-paystack": "^1.0"
+        "smladeoye/yii2-paystack": "^1.0",
+        "mailgun/mailgun-php": "^4.3",
+        "nyholm/psr7": "^1.6",
+        "symfony/http-client": "^5.4"
     },
     "require-dev": {
         "yiisoft/yii2-debug": "~2.0.0",
@@ -44,7 +47,8 @@
             "enabled": false
         },
         "allow-plugins": {
-            "yiisoft/yii2-composer": true
+            "yiisoft/yii2-composer": true,
+            "php-http/discovery": true
         }
     },
     "autoload": {

文件差异内容过多而无法显示
+ 513 - 269
composer.lock


+ 2 - 1
console/controllers/ToolController.php

@@ -205,8 +205,9 @@ class ToolController extends BaseController
 
     public function actionSendEmail()
     {
-        Email::sendRegistrationEmail('managerukay@gmail.com', 'AEtest', 'aetest');
+//        Email::sendRegistrationEmail('managerukay@gmail.com', 'AEtest', 'aetest');
 //        Email::sendRegistrationEmail('no.reply.elken@gmail.com', 'AEtest', 'aetest');
+        Email::sendEmail('18511880790@163.com', 'AEtest', 'aetest');
     }
 
     public function actionUpdatePercent() {

二进制
http-client-7.2.zip


二进制
mailgun-php-master.zip


二进制
psr7-master.zip


+ 2 - 0
vendor/clue/stream-filter/.github/FUNDING.yml

@@ -0,0 +1,2 @@
+github: clue
+custom: https://clue.engineering/support

+ 97 - 0
vendor/clue/stream-filter/CHANGELOG.md

@@ -0,0 +1,97 @@
+# Changelog
+
+## 1.7.0 (2023-12-20)
+
+*   Feature: Full PHP 8.3 and PHP 8.2 compatibility.
+    (#51 by @yadaiio and #50 by @clue)
+
+*   Feature / Fix: Improve error reporting when custom error handler is used.
+    (#47 by @SimonFrings)
+
+*   Improve test suite and ensure 100% code coverage.
+    (#46 by @SimonFrings, #48 and #50 by @clue and #51 by @yadaiio)
+
+## 1.6.0 (2022-02-21)
+
+*   Feature: Support PHP 8.1 release.
+    (#45 by @clue)
+
+*   Improve documentation to use fully-qualified function names.
+    (#43 by @SimonFrings and #42 by @PaulRotmann)
+
+*   Improve test suite and use GitHub actions for continuous integration (CI).
+    (#39 and #40 by @SimonFrings)
+
+## 1.5.0 (2020-10-02)
+
+*   Feature: Improve performance by using global imports.
+    (#38 by @clue)
+
+*   Improve API documentation and add support / sponsorship info.
+    (#30 by @clue and #35 by @SimonFrings)
+
+*   Improve test suite and add `.gitattributes` to exclude dev files from exports.
+    Prepare PHP 8 support, update to PHPUnit 9 and simplify test matrix.
+    (#32 and #37 by @clue and #34 and #36 by @SimonFrings)
+
+## 1.4.1 (2019-04-09)
+
+*   Fix: Check if the function is declared before declaring it.
+    (#23 by @Niko9911)
+
+*   Improve test suite to also test against PHP 7.2 and
+    add test for base64 encoding and decoding filters.
+    (#22 by @arubacao and #25 by @Nyholm and @clue)
+
+## 1.4.0 (2017-08-18)
+
+*   Feature / Fix: The `fun()` function does not pass filter parameter `null`
+    to underlying `stream_filter_append()` by default
+    (#15 by @Nyholm)
+
+    Certain filters (such as `convert.quoted-printable-encode`) do not accept
+    a filter parameter at all. If no explicit filter parameter is given, we no
+    longer pass a default `null` value.
+
+    ```php
+    $encode = Filter\fun('convert.quoted-printable-encode');
+    assert('t=C3=A4st' === $encode('täst'));
+    ```
+
+*   Add examples and improve documentation
+    (#13 and #20 by @clue and #18 by @Nyholm)
+
+*   Improve test suite by adding PHPUnit to require-dev,
+    fix HHVM build for now again and ignore future HHVM build errors,
+    lock Travis distro so new future defaults will not break the build
+    and test on PHP 7.1
+    (#12, #14 and #19 by @clue and #16 by @Nyholm)
+
+## 1.3.0 (2015-11-08)
+
+*   Feature: Support accessing built-in filters as callbacks
+    (#5 by @clue)
+
+    ```php
+    $fun = Filter\fun('zlib.deflate');
+
+    $ret = $fun('hello') . $fun('world') . $fun();
+    assert('helloworld' === gzinflate($ret));
+    ```
+
+## 1.2.0 (2015-10-23)
+
+* Feature: Invoke close event when closing filter (flush buffer)
+  (#9 by @clue)
+
+## 1.1.0 (2015-10-22)
+
+* Feature: Abort filter operation when catching an Exception
+  (#10 by @clue)
+
+* Feature: Additional safeguards to prevent filter state corruption
+  (#7 by @clue)
+
+## 1.0.0 (2015-10-18)
+
+* First tagged release

+ 21 - 0
vendor/clue/stream-filter/LICENSE

@@ -0,0 +1,21 @@
+The MIT License (MIT)
+
+Copyright (c) 2015 Christian Lück
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is furnished
+to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.

+ 326 - 0
vendor/clue/stream-filter/README.md

@@ -0,0 +1,326 @@
+# clue/stream-filter
+
+[![CI status](https://github.com/clue/stream-filter/actions/workflows/ci.yml/badge.svg)](https://github.com/clue/stream-filter/actions)
+[![installs on Packagist](https://img.shields.io/packagist/dt/clue/stream-filter?color=blue&label=installs%20on%20Packagist)](https://packagist.org/packages/clue/stream-filter)
+
+A simple and modern approach to stream filtering in PHP
+
+**Table of contents**
+
+* [Why?](#why)
+* [Support us](#support-us)
+* [Usage](#usage)
+    * [append()](#append)
+    * [prepend()](#prepend)
+    * [fun()](#fun)
+    * [remove()](#remove)
+* [Install](#install)
+* [Tests](#tests)
+* [License](#license)
+
+## Why?
+
+PHP's stream filtering system is great!
+
+It offers very powerful stream filtering options and comes with a useful set of built-in filters.
+These filters can be used to easily and efficiently perform various transformations on-the-fly, such as:
+
+* read from a gzip'ed input file,
+* transcode from ISO-8859-1 (Latin1) to UTF-8,
+* write to a bzip output file
+* and much more.
+
+But let's face it:
+Its API is [*difficult to work with*](https://www.php.net/manual/en/php-user-filter.filter.php)
+and its documentation is [*subpar*](https://stackoverflow.com/questions/27103269/what-is-a-bucket-brigade).
+This combined means its powerful features are often neglected.
+
+This project aims to make these features more accessible to a broader audience.
+* **Lightweight, SOLID design** -
+  Provides a thin abstraction that is [*just good enough*](https://en.wikipedia.org/wiki/Principle_of_good_enough)
+  and does not get in your way.
+  Custom filters require trivial effort.
+* **Good test coverage** -
+  Comes with an automated tests suite and is regularly tested in the *real world*
+
+## Support us
+
+We invest a lot of time developing, maintaining and updating our awesome
+open-source projects. You can help us sustain this high-quality of our work by
+[becoming a sponsor on GitHub](https://github.com/sponsors/clue). Sponsors get
+numerous benefits in return, see our [sponsoring page](https://github.com/sponsors/clue)
+for details.
+
+Let's take these projects to the next level together! 🚀
+
+## Usage
+
+This lightweight library consists only of a few simple functions.
+All functions reside under the `Clue\StreamFilter` namespace.
+
+The below examples refer to all functions with their fully-qualified names like this:
+
+```php
+Clue\StreamFilter\append(…);
+```
+
+As of PHP 5.6+ you can also import each required function into your code like this:
+
+```php
+use function Clue\StreamFilter\append;
+
+append(…);
+```
+
+Alternatively, you can also use an import statement similar to this:
+
+```php
+use Clue\StreamFilter as Filter;
+
+Filter\append(…);
+```
+
+### append()
+
+The `append(resource<stream> $stream, callable $callback, int $read_write = STREAM_FILTER_ALL): resource<stream filter>` function can be used to
+append a filter callback to the given stream.
+
+Each stream can have a list of filters attached.
+This function appends a filter to the end of this list.
+
+If the given filter can not be added, it throws an `Exception`.
+
+The `$stream` can be any valid stream resource, such as:
+
+```php
+$stream = fopen('demo.txt', 'w+');
+```
+
+The `$callback` should be a valid callable function which accepts 
+an individual chunk of data and should return the updated chunk:
+
+```php
+$filter = Clue\StreamFilter\append($stream, function ($chunk) {
+    // will be called each time you read or write a $chunk to/from the stream
+    return $chunk;
+});
+```
+
+As such, you can also use native PHP functions or any other `callable`:
+
+```php
+Clue\StreamFilter\append($stream, 'strtoupper');
+
+// will write "HELLO" to the underlying stream
+fwrite($stream, 'hello');
+```
+
+If the `$callback` accepts invocation without parameters,
+then this signature will be invoked once ending (flushing) the filter:
+
+```php
+Clue\StreamFilter\append($stream, function ($chunk = null) {
+    if ($chunk === null) {
+        // will be called once ending the filter
+        return 'end';
+    }
+    // will be called each time you read or write a $chunk to/from the stream
+    return $chunk;
+});
+
+fclose($stream);
+```
+
+> Note: Legacy PHP versions (PHP < 5.4) do not support passing additional data
+from the end signal handler if the stream is being closed.
+
+If your callback throws an `Exception`, then the filter process will be aborted.
+In order to play nice with PHP's stream handling,
+the `Exception` will be transformed to a PHP warning instead:
+
+```php
+Clue\StreamFilter\append($stream, function ($chunk) {
+    throw new \RuntimeException('Unexpected chunk');
+});
+
+// raises an E_USER_WARNING with "Error invoking filter: Unexpected chunk"
+fwrite($stream, 'hello');
+```
+
+The optional `$read_write` parameter can be used to only invoke the `$callback`
+when either writing to the stream or only when reading from the stream:
+
+```php
+Clue\StreamFilter\append($stream, function ($chunk) {
+    // will be called each time you write to the stream
+    return $chunk;
+}, STREAM_FILTER_WRITE);
+
+Clue\StreamFilter\append($stream, function ($chunk) {
+    // will be called each time you read from the stream
+    return $chunk;
+}, STREAM_FILTER_READ);
+```
+
+This function returns a filter resource which can be passed to [`remove()`](#remove).
+
+> Note that once a filter has been added to stream, the stream can no longer be passed to
+> [`stream_select()`](https://www.php.net/manual/en/function.stream-select.php)
+> (and family).
+>
+> > Warning: stream_select(): cannot cast a filtered stream on this system in {file} on line {line}
+>
+> This is due to limitations of PHP's stream filter support, as it can no longer reliably
+> tell when the underlying stream resource is actually ready.
+> As an alternative, consider calling `stream_select()` on the unfiltered stream and
+> then pass the unfiltered data through the [`fun()`](#fun) function.
+
+### prepend()
+
+The `prepend(resource<stream> $stream, callable $callback, int $read_write = STREAM_FILTER_ALL): resource<stream filter>` function can be used to
+prepend a filter callback to the given stream.
+
+Each stream can have a list of filters attached.
+This function prepends a filter to the start of this list.
+
+If the given filter can not be added, it throws an `Exception`.
+
+```php
+$filter = Clue\StreamFilter\prepend($stream, function ($chunk) {
+    // will be called each time you read or write a $chunk to/from the stream
+    return $chunk;
+});
+```
+
+This function returns a filter resource which can be passed to [`remove()`](#remove).
+
+Except for the position in the list of filters, this function behaves exactly
+like the [`append()`](#append) function.
+For more details about its behavior, see also the [`append()`](#append) function.
+
+### fun()
+
+The `fun(string $filter, mixed $parameters = null): callable` function can be used to
+create a filter function which uses the given built-in `$filter`.
+
+PHP comes with a useful set of [built-in filters](https://www.php.net/manual/en/filters.php).
+Using `fun()` makes accessing these as easy as passing an input string to filter
+and getting the filtered output string.
+
+```php
+$fun = Clue\StreamFilter\fun('string.rot13');
+
+assert('grfg' === $fun('test'));
+assert('test' === $fun($fun('test'));
+```
+
+Please note that not all filter functions may be available depending 
+on installed PHP extensions and the PHP version in use.
+In particular, [HHVM](https://hhvm.com/) may not offer the same filter functions
+or parameters as Zend PHP.
+Accessing an unknown filter function will result in a `RuntimeException`:
+
+```php
+Clue\StreamFilter\fun('unknown'); // throws RuntimeException
+```
+
+Some filters may accept or require additional filter parameters – most
+filters do not require filter parameters.
+If given, the optional `$parameters` argument will be passed to the
+underlying filter handler as-is.
+In particular, note how *not passing* this parameter at all differs from
+explicitly passing a `null` value (which many filters do not accept).
+Please refer to the individual filter definition for more details.
+For example, the `string.strip_tags` filter can be invoked like this:
+
+```php
+$fun = Clue\StreamFilter\fun('string.strip_tags', '<a><b>');
+
+$ret = $fun('<b>h<br>i</b>');
+assert('<b>hi</b>' === $ret);
+```
+
+Under the hood, this function allocates a temporary memory stream, so it's
+recommended to clean up the filter function after use.
+Also, some filter functions (in particular the
+[zlib compression filters](https://www.php.net/manual/en/filters.compression.php))
+may use internal buffers and may emit a final data chunk on close.
+The filter function can be closed by invoking without any arguments:
+
+```php
+$fun = Clue\StreamFilter\fun('zlib.deflate');
+
+$ret = $fun('hello') . $fun('world') . $fun();
+assert('helloworld' === gzinflate($ret));
+```
+
+The filter function must not be used anymore after it has been closed.
+Doing so will result in a `RuntimeException`:
+
+```php
+$fun = Clue\StreamFilter\fun('string.rot13');
+$fun();
+
+$fun('test'); // throws RuntimeException
+```
+
+> Note: If you're using the zlib compression filters, then you should be wary
+about engine inconsistencies between different PHP versions and HHVM.
+These inconsistencies exist in the underlying PHP engines and there's little we
+can do about this in this library.
+[Our test suite](tests/) contains several test cases that exhibit these issues.
+If you feel some test case is missing or outdated, we're happy to accept PRs! :)
+
+### remove()
+
+The `remove(resource<stream filter> $filter): bool` function can be used to
+remove a filter previously added via [`append()`](#append) or [`prepend()`](#prepend).
+
+```php
+$filter = Clue\StreamFilter\append($stream, function () {
+    // …
+});
+Clue\StreamFilter\remove($filter);
+```
+
+## Install
+
+The recommended way to install this library is [through Composer](https://getcomposer.org/).
+[New to Composer?](https://getcomposer.org/doc/00-intro.md)
+
+This project follows [SemVer](https://semver.org/).
+This will install the latest supported version:
+
+```bash
+$ composer require clue/stream-filter:^1.7
+```
+
+See also the [CHANGELOG](CHANGELOG.md) for details about version upgrades.
+
+This project aims to run on any platform and thus does not require any PHP
+extensions and supports running on legacy PHP 5.3 through current PHP 8+ and
+HHVM.
+It's *highly recommended to use the latest supported PHP version* for this project.
+Older PHP versions may suffer from a number of inconsistencies documented above.
+
+## Tests
+
+To run the test suite, you first need to clone this repo and then install all
+dependencies [through Composer](https://getcomposer.org/):
+
+```bash
+$ composer install
+```
+
+To run the test suite, go to the project root and run:
+
+```bash
+$ vendor/bin/phpunit
+```
+
+## License
+
+This project is released under the permissive [MIT license](LICENSE).
+
+> Did you know that I offer custom development services and issuing invoices for
+  sponsorships of releases and for contributions? Contact me (@clue) for details.

+ 32 - 0
vendor/clue/stream-filter/composer.json

@@ -0,0 +1,32 @@
+{
+    "name": "clue/stream-filter",
+    "description": "A simple and modern approach to stream filtering in PHP",
+    "keywords": ["stream", "callback", "filter", "php_user_filter", "stream_filter_append", "stream_filter_register", "bucket brigade"],
+    "homepage": "https://github.com/clue/stream-filter",
+    "license": "MIT",
+    "authors": [
+        {
+            "name": "Christian Lück",
+            "email": "christian@clue.engineering"
+        }
+    ],
+    "require": {
+        "php": ">=5.3"
+    },
+    "require-dev": {
+        "phpunit/phpunit": "^9.6 || ^5.7 || ^4.8.36"
+    },
+    "autoload": {
+        "psr-4": {
+            "Clue\\StreamFilter\\": "src/"
+        },
+        "files": [
+            "src/functions_include.php"
+        ]
+    },
+    "autoload-dev": {
+        "psr-4": {
+            "Clue\\Tests\\StreamFilter\\": "tests/"
+        }
+    }
+}

+ 120 - 0
vendor/clue/stream-filter/src/CallbackFilter.php

@@ -0,0 +1,120 @@
+<?php
+
+namespace Clue\StreamFilter;
+
+/**
+ * @internal
+ * @see append()
+ * @see prepend()
+ */
+class CallbackFilter extends \php_user_filter
+{
+    private $callback;
+    private $closed = true;
+    private $supportsClose = false;
+
+    /** @return bool */
+    #[\ReturnTypeWillChange]
+    public function onCreate()
+    {
+        $this->closed = false;
+
+        if (!\is_callable($this->params)) {
+            throw new \InvalidArgumentException('No valid callback parameter given to stream_filter_(append|prepend)');
+        }
+        $this->callback = $this->params;
+
+        // callback supports end event if it accepts invocation without arguments
+        $ref = new \ReflectionFunction($this->callback);
+        $this->supportsClose = ($ref->getNumberOfRequiredParameters() === 0);
+
+        return true;
+    }
+
+    /** @return void */
+    #[\ReturnTypeWillChange]
+    public function onClose()
+    {
+        $this->closed = true;
+
+        // callback supports closing and is not already closed
+        if ($this->supportsClose) {
+            $this->supportsClose = false;
+            // invoke without argument to signal end and discard resulting buffer
+            try {
+                \call_user_func($this->callback);
+            } catch (\Exception $ignored) {
+                // this might be called during engine shutdown, so it's not safe
+                // to raise any errors or exceptions here
+                // trigger_error('Error closing filter: ' . $ignored->getMessage(), E_USER_WARNING);
+            }
+        }
+
+        $this->callback = null;
+    }
+
+    /** @return int */
+    #[\ReturnTypeWillChange]
+    public function filter($in, $out, &$consumed, $closing)
+    {
+        // concatenate whole buffer from input brigade
+        $data = '';
+        while ($bucket = \stream_bucket_make_writeable($in)) {
+            $consumed += $bucket->datalen;
+            $data .= $bucket->data;
+        }
+
+        // skip processing callback that already ended
+        if ($this->closed) {
+            return \PSFS_FEED_ME;
+        }
+
+        // only invoke filter function if buffer is not empty
+        // this may skip flushing a closing filter
+        if ($data !== '') {
+            try {
+                $data = \call_user_func($this->callback, $data);
+            } catch (\Exception $e) {
+                // exception should mark filter as closed
+                $this->onClose();
+                \trigger_error('Error invoking filter: ' . $e->getMessage(), \E_USER_WARNING);
+
+                return \PSFS_ERR_FATAL;
+            }
+        }
+
+        // mark filter as closed after processing closing chunk
+        if ($closing) {
+            $this->closed = true;
+
+            // callback supports closing and is not already closed
+            if ($this->supportsClose) {
+                $this->supportsClose = false;
+
+                // invoke without argument to signal end and append resulting buffer
+                try {
+                    $data .= \call_user_func($this->callback);
+                } catch (\Exception $e) {
+                    \trigger_error('Error ending filter: ' . $e->getMessage(), \E_USER_WARNING);
+
+                    return \PSFS_ERR_FATAL;
+                }
+            }
+        }
+
+        if ($data !== '') {
+            // create a new bucket for writing the resulting buffer to the output brigade
+            // reusing an existing bucket turned out to be bugged in some environments (ancient PHP versions and HHVM)
+            $bucket = @\stream_bucket_new($this->stream, $data);
+
+            // legacy PHP versions (PHP < 5.4) do not support passing data from the event signal handler
+            // because closing the stream invalidates the stream and its stream bucket brigade before
+            // invoking the filter close handler.
+            if ($bucket !== false) {
+                \stream_bucket_append($out, $bucket);
+            }
+        }
+
+        return \PSFS_PASS_ON;
+    }
+}

+ 380 - 0
vendor/clue/stream-filter/src/functions.php

@@ -0,0 +1,380 @@
+<?php
+
+namespace Clue\StreamFilter;
+
+/**
+ * Append a filter callback to the given stream.
+ *
+ * Each stream can have a list of filters attached.
+ * This function appends a filter to the end of this list.
+ *
+ * If the given filter can not be added, it throws an `Exception`.
+ *
+ * The `$stream` can be any valid stream resource, such as:
+ *
+ * ```php
+ * $stream = fopen('demo.txt', 'w+');
+ * ```
+ *
+ * The `$callback` should be a valid callable function which accepts
+ * an individual chunk of data and should return the updated chunk:
+ *
+ * ```php
+ * $filter = Clue\StreamFilter\append($stream, function ($chunk) {
+ *     // will be called each time you read or write a $chunk to/from the stream
+ *     return $chunk;
+ * });
+ * ```
+ *
+ * As such, you can also use native PHP functions or any other `callable`:
+ *
+ * ```php
+ * Clue\StreamFilter\append($stream, 'strtoupper');
+ *
+ * // will write "HELLO" to the underlying stream
+ * fwrite($stream, 'hello');
+ * ```
+ *
+ * If the `$callback` accepts invocation without parameters,
+ * then this signature will be invoked once ending (flushing) the filter:
+ *
+ * ```php
+ * Clue\StreamFilter\append($stream, function ($chunk = null) {
+ *     if ($chunk === null) {
+ *         // will be called once ending the filter
+ *         return 'end';
+ *     }
+ *     // will be called each time you read or write a $chunk to/from the stream
+ *     return $chunk;
+ * });
+ *
+ * fclose($stream);
+ * ```
+ *
+ * > Note: Legacy PHP versions (PHP < 5.4) do not support passing additional data
+ * from the end signal handler if the stream is being closed.
+ *
+ * If your callback throws an `Exception`, then the filter process will be aborted.
+ * In order to play nice with PHP's stream handling,
+ * the `Exception` will be transformed to a PHP warning instead:
+ *
+ * ```php
+ * Clue\StreamFilter\append($stream, function ($chunk) {
+ *     throw new \RuntimeException('Unexpected chunk');
+ * });
+ *
+ * // raises an E_USER_WARNING with "Error invoking filter: Unexpected chunk"
+ * fwrite($stream, 'hello');
+ * ```
+ *
+ * The optional `$read_write` parameter can be used to only invoke the `$callback`
+ * when either writing to the stream or only when reading from the stream:
+ *
+ * ```php
+ * Clue\StreamFilter\append($stream, function ($chunk) {
+ *     // will be called each time you write to the stream
+ *     return $chunk;
+ * }, STREAM_FILTER_WRITE);
+ *
+ * Clue\StreamFilter\append($stream, function ($chunk) {
+ *     // will be called each time you read from the stream
+ *     return $chunk;
+ * }, STREAM_FILTER_READ);
+ * ```
+ *
+ * This function returns a filter resource which can be passed to [`remove()`](#remove).
+ *
+ * > Note that once a filter has been added to stream, the stream can no longer be passed to
+ * > [`stream_select()`](https://www.php.net/manual/en/function.stream-select.php)
+ * > (and family).
+ * >
+ * > > Warning: stream_select(): cannot cast a filtered stream on this system in {file} on line {line}
+ * >
+ * > This is due to limitations of PHP's stream filter support, as it can no longer reliably
+ * > tell when the underlying stream resource is actually ready.
+ * > As an alternative, consider calling `stream_select()` on the unfiltered stream and
+ * > then pass the unfiltered data through the [`fun()`](#fun) function.
+ *
+ * @param resource $stream
+ * @param callable $callback
+ * @param int $read_write
+ * @return resource filter resource which can be used for `remove()`
+ * @throws \Exception on error
+ * @uses stream_filter_append()
+ */
+function append($stream, $callback, $read_write = STREAM_FILTER_ALL)
+{
+    $errstr = '';
+    \set_error_handler(function ($_, $error) use (&$errstr) {
+        // Match errstr from PHP's warning message.
+        // stream_filter_append() expects parameter 1 to be resource,...
+        $errstr = $error; // @codeCoverageIgnore
+    });
+
+    try {
+        $ret = \stream_filter_append($stream, register(), $read_write, $callback);
+    } catch (\TypeError $e) { // @codeCoverageIgnoreStart
+        // Throws TypeError on PHP 8.0+
+        \restore_error_handler();
+        throw $e;
+    } // @codeCoverageIgnoreEnd
+
+    \restore_error_handler();
+
+    // PHP 8 throws above on type errors, older PHP and memory issues can throw here
+    // @codeCoverageIgnoreStart
+    if ($ret === false) {
+        throw new \RuntimeException('Unable to append filter: ' . $errstr);
+    }
+    // @codeCoverageIgnoreEnd
+
+    return $ret;
+}
+
+/**
+ * Prepend a filter callback to the given stream.
+ *
+ * Each stream can have a list of filters attached.
+ * This function prepends a filter to the start of this list.
+ *
+ * If the given filter can not be added, it throws an `Exception`.
+ *
+ * ```php
+ * $filter = Clue\StreamFilter\prepend($stream, function ($chunk) {
+ *     // will be called each time you read or write a $chunk to/from the stream
+ *     return $chunk;
+ * });
+ * ```
+ *
+ * This function returns a filter resource which can be passed to [`remove()`](#remove).
+ *
+ * Except for the position in the list of filters, this function behaves exactly
+ * like the [`append()`](#append) function.
+ * For more details about its behavior, see also the [`append()`](#append) function.
+ *
+ * @param resource $stream
+ * @param callable $callback
+ * @param int $read_write
+ * @return resource filter resource which can be used for `remove()`
+ * @throws \Exception on error
+ * @uses stream_filter_prepend()
+ */
+function prepend($stream, $callback, $read_write = STREAM_FILTER_ALL)
+{
+    $errstr = '';
+    \set_error_handler(function ($_, $error) use (&$errstr) {
+        // Match errstr from PHP's warning message.
+        // stream_filter_prepend() expects parameter 1 to be resource,...
+        $errstr = $error; // @codeCoverageIgnore
+    });
+
+    try {
+        $ret = \stream_filter_prepend($stream, register(), $read_write, $callback);
+    } catch (\TypeError $e) { // @codeCoverageIgnoreStart
+        // Throws TypeError on PHP 8.0+
+        \restore_error_handler();
+        throw $e;
+    } // @codeCoverageIgnoreEnd
+
+    \restore_error_handler();
+
+    // PHP 8 throws above on type errors, older PHP and memory issues can throw here
+    // @codeCoverageIgnoreStart
+    if ($ret === false) {
+        throw new \RuntimeException('Unable to prepend filter: ' . $errstr);
+    }
+    // @codeCoverageIgnoreEnd
+
+    return $ret;
+}
+
+/**
+ * Create a filter function which uses the given built-in `$filter`.
+ *
+ * PHP comes with a useful set of [built-in filters](https://www.php.net/manual/en/filters.php).
+ * Using `fun()` makes accessing these as easy as passing an input string to filter
+ * and getting the filtered output string.
+ *
+ * ```php
+ * $fun = Clue\StreamFilter\fun('string.rot13');
+ *
+ * assert('grfg' === $fun('test'));
+ * assert('test' === $fun($fun('test'));
+ * ```
+ *
+ * Please note that not all filter functions may be available depending
+ * on installed PHP extensions and the PHP version in use.
+ * In particular, [HHVM](https://hhvm.com/) may not offer the same filter functions
+ * or parameters as Zend PHP.
+ * Accessing an unknown filter function will result in a `RuntimeException`:
+ *
+ * ```php
+ * Clue\StreamFilter\fun('unknown'); // throws RuntimeException
+ * ```
+ *
+ * Some filters may accept or require additional filter parameters – most
+ * filters do not require filter parameters.
+ * If given, the optional `$parameters` argument will be passed to the
+ * underlying filter handler as-is.
+ * In particular, note how *not passing* this parameter at all differs from
+ * explicitly passing a `null` value (which many filters do not accept).
+ * Please refer to the individual filter definition for more details.
+ * For example, the `string.strip_tags` filter can be invoked like this:
+ *
+ * ```php
+ * $fun = Clue\StreamFilter\fun('string.strip_tags', '<a><b>');
+ *
+ * $ret = $fun('<b>h<br>i</b>');
+ * assert('<b>hi</b>' === $ret);
+ * ```
+ *
+ * Under the hood, this function allocates a temporary memory stream, so it's
+ * recommended to clean up the filter function after use.
+ * Also, some filter functions (in particular the
+ * [zlib compression filters](https://www.php.net/manual/en/filters.compression.php))
+ * may use internal buffers and may emit a final data chunk on close.
+ * The filter function can be closed by invoking without any arguments:
+ *
+ * ```php
+ * $fun = Clue\StreamFilter\fun('zlib.deflate');
+ *
+ * $ret = $fun('hello') . $fun('world') . $fun();
+ * assert('helloworld' === gzinflate($ret));
+ * ```
+ *
+ * The filter function must not be used anymore after it has been closed.
+ * Doing so will result in a `RuntimeException`:
+ *
+ * ```php
+ * $fun = Clue\StreamFilter\fun('string.rot13');
+ * $fun();
+ *
+ * $fun('test'); // throws RuntimeException
+ * ```
+ *
+ * > Note: If you're using the zlib compression filters, then you should be wary
+ * about engine inconsistencies between different PHP versions and HHVM.
+ * These inconsistencies exist in the underlying PHP engines and there's little we
+ * can do about this in this library.
+ * [Our test suite](tests/) contains several test cases that exhibit these issues.
+ * If you feel some test case is missing or outdated, we're happy to accept PRs! :)
+ *
+ * @param string $filter     built-in filter name. See stream_get_filters() or http://php.net/manual/en/filters.php
+ * @param mixed  $parameters (optional) parameters to pass to the built-in filter as-is
+ * @return callable a filter callback which can be append()'ed or prepend()'ed
+ * @throws \RuntimeException on error
+ * @link http://php.net/manual/en/filters.php
+ * @see stream_get_filters()
+ * @see append()
+ */
+function fun($filter, $parameters = null)
+{
+    $fp = \fopen('php://memory', 'w');
+
+    $errstr = '';
+    \set_error_handler(function ($_, $error) use (&$errstr) {
+        // Match errstr from PHP's warning message.
+        // stream_filter_append() expects parameter 1 to be resource,...
+        $errstr = $error;
+    });
+
+    if (\func_num_args() === 1) {
+        $filter = \stream_filter_append($fp, $filter, \STREAM_FILTER_WRITE);
+    } else {
+        $filter = \stream_filter_append($fp, $filter, \STREAM_FILTER_WRITE, $parameters);
+    }
+
+    \restore_error_handler();
+
+    if ($filter === false) {
+        \fclose($fp);
+        throw new \RuntimeException('Unable to access built-in filter: ' . $errstr);
+    }
+
+    // append filter function which buffers internally
+    $buffer = '';
+    append($fp, function ($chunk) use (&$buffer) {
+        $buffer .= $chunk;
+
+        // always return empty string in order to skip actually writing to stream resource
+        return '';
+    }, \STREAM_FILTER_WRITE);
+
+    $closed = false;
+
+    return function ($chunk = null) use ($fp, $filter, &$buffer, &$closed) {
+        if ($closed) {
+            throw new \RuntimeException('Unable to perform operation on closed stream');
+        }
+        if ($chunk === null) {
+            $closed = true;
+            $buffer = '';
+            \fclose($fp);
+            return $buffer;
+        }
+        // initialize buffer and invoke filters by attempting to write to stream
+        $buffer = '';
+        \fwrite($fp, $chunk);
+
+        // buffer now contains everything the filter function returned
+        return $buffer;
+    };
+}
+
+/**
+ * Remove a filter previously added via `append()` or `prepend()`.
+ *
+ * ```php
+ * $filter = Clue\StreamFilter\append($stream, function () {
+ *     // …
+ * });
+ * Clue\StreamFilter\remove($filter);
+ * ```
+ *
+ * @param resource $filter
+ * @return bool true on success or false on error
+ * @throws \RuntimeException on error
+ * @uses stream_filter_remove()
+ */
+function remove($filter)
+{
+    $errstr = '';
+    \set_error_handler(function ($_, $error) use (&$errstr) {
+        // Match errstr from PHP's warning message.
+        // stream_filter_remove() expects parameter 1 to be resource,...
+        $errstr = $error;
+    });
+
+    try {
+        $ret = \stream_filter_remove($filter);
+    } catch (\TypeError $e) { // @codeCoverageIgnoreStart
+        // Throws TypeError on PHP 8.0+
+        \restore_error_handler();
+        throw $e;
+    } // @codeCoverageIgnoreEnd
+
+    \restore_error_handler();
+
+    if ($ret === false) {
+        // PHP 8 throws above on type errors, older PHP and memory issues can throw here
+        throw new \RuntimeException('Unable to remove filter: ' . $errstr);
+    }
+}
+
+/**
+ * Registers the callback filter and returns the resulting filter name
+ *
+ * There should be little reason to call this function manually.
+ *
+ * @return string filter name
+ * @uses CallbackFilter
+ */
+function register()
+{
+    static $registered = null;
+    if ($registered === null) {
+        $registered = 'stream-callback';
+        \stream_filter_register($registered, __NAMESPACE__ . '\CallbackFilter');
+    }
+    return $registered;
+}

+ 9 - 0
vendor/clue/stream-filter/src/functions_include.php

@@ -0,0 +1,9 @@
+<?php
+
+namespace Clue\StreamFilter;
+
+// @codeCoverageIgnoreStart
+if (!\function_exists(__NAMESPACE__ . '\\append')) {
+    require __DIR__ . '/functions.php';
+}
+// @codeCoverageIgnoreEnd

+ 36 - 0
vendor/mailgun/mailgun-php/.php-cs-fixer.php

@@ -0,0 +1,36 @@
+<?php
+
+$header = <<<TXT
+Copyright (C) 2013 Mailgun
+
+This software may be modified and distributed under the terms
+of the MIT license. See the LICENSE file for details.
+TXT;
+
+$finder = PhpCsFixer\Finder::create()
+    ->in('src')
+    ->in('tests');
+
+
+return (new PhpCsFixer\Config())
+    ->setRiskyAllowed(true)
+    ->setRules([
+        '@PSR2' => true,
+        '@PSR12' => true,
+        '@Symfony' => false,
+        'strict_param' => true,
+        'array_syntax' => ['syntax' => 'short'],
+        'declare_strict_types' => true,
+        'no_empty_phpdoc' => false,
+        'no_superfluous_phpdoc_tags' => false,
+        'phpdoc_separation' => false,
+        'no_unneeded_final_method' => false, # prevent phpstan divergence
+        'header_comment' => [
+            'comment_type' => 'comment',
+            'header' => $header,
+            'location' => 'after_declare_strict',
+            'separate' => 'both',
+        ],
+    ])
+    ->setFinder($finder)
+;

+ 452 - 0
vendor/mailgun/mailgun-php/CHANGELOG.md

@@ -0,0 +1,452 @@
+# Change Log
+
+The change log describes what is "Added", "Removed", "Changed" or "Fixed" between each release.
+
+## 4.3.5
+- Implemented `AccountManagement` API endpoints:
+    - `updateAccountSettings` for `PUT /v5/accounts`
+    - `getHttpSigningKey` for `GET /v5/accounts/http_signing_key`
+    - `createHttpSigningKey` for `POST /v5/accounts/http_signing_key`
+    - `getSandboxAuthRecipients` for `GET /v5/sandbox/auth_recipients`
+- Added model classes for `AccountManagement` API responses:
+    - `AccountResponse`
+    - `HttpSigningKeyResponse`
+    - `SandboxAuthRecipientsResponse`
+- Updated `Mailgun` class to include `AccountManagement` API.
+
+## 4.3.4
+ - Extended limit of tags to 10.
+
+## 4.3.3
+ - Fixed way of sending request with JSON body. loadMetrics works. Adjusted tests. Fixed warning related to the php 8.4 and nullable types
+
+## 4.3.2
+ - Added new API endpoint for getting metrics @see https://documentation.mailgun.com/docs/mailgun/api-reference/openapi-final/tag/Metrics/
+
+## 4.3.1
+ - Add method for retrieving stored messages by @oleksandr-mykhailenko in #920
+ - Add missed params to the create method for DomainV4.php by @oleksandr-mykhailenko in #921
+
+## 4.3.0
+- End of support php 7.3
+- Updated properties and added types to the classes properties
+- Update code style
+- Add missed field into IndexResponse for Webhooks
+- Fixed template search filters
+- Fixed tags API endpoints
+- Added new API endpoints
+
+## 4.2.0
+- Added basic templates functionality
+
+## 4.0.1
+ - Fix wrong classes in tests
+ - Fixed response in case of 404 http error. Respect server error message
+
+## 4.0
+ - SubAccount support @oleksandr-mykhailenko in #886
+ - Requests of behalf of Sub Account
+
+## 3.6.2
+ - Bugfix: TypeError caused by improper use of new self() instead of new static() in base class method
+
+## 3.6.1
+ - update library
+ - Improvement: SDK version headers v2 vs v3
+ - Update packages by @oleksandr-mykhailenko
+
+## 3.5.9
+- Fixed: bug when params `to` and `reply-to` have the same address
+
+## 3.5.6
+ - Changed: support bool value for method `createMultiple`
+
+## 3.5.5
+
+### Fixed
+- Cast integer values to string for sending multipart data
+
+## 3.5.4
+
+### Added
+- Added ability to make own API request to needed endpoint
+
+## 3.5.3
+
+### Added
+
+- Ability to update web_scheme for domain
+- `http` or `https` - set your open, click and unsubscribe URLs to use http or https. The default is http
+
+## 3.5.1
+
+### Fixed
+
+- Error with empty array for param recipient-variables. Fix was suggested by @deviarte
+- Use null coalescing operator in IndexResponse.php when. Fix proposed by @TWithers
+
+## 3.5.0
+
+### Added
+
+- Support for new webhook endpoints (#609 by @svenbw & #749 by @Nyholm)
+
+## 3.4.1
+
+### Fixed
+
+- Fix double urlencoding (#747 by @uavn)
+
+## 3.4.0
+
+### Added
+
+- Email Validation (#726 by @uavn)
+  - Please note the Email Validation requires **always** to use the US servers. The Mailgun Team didn't enable this service on the European endpoints.
+
+## 3.3.0
+
+### Added
+
+- Support for PHP 8 (#727 by @DavidGarciaCat)
+- Added `opened`, `clicked`, `unsubscribed` and `stored` to the `TotalResponseItem` (#739 by @Arkitecht)
+
+### Removed
+
+- Support for PHP 7.1 and 7.2 as they both have reached their end of life
+
+## 3.2.0
+
+### Added
+
+- Domain tracking implementation (#711 by @uavn)
+- Mailing list validation (#712 by @uavn)
+- Suppression Whitelists (#713 by @uavn)
+
+### Fixed
+
+- Added pagination to tags (#717 by @uavn)
+
+### Changed
+
+- Expect Client to be of type ClientInterface or PluginClient (#660 by @tonythomas01)
+
+## 3.1.0
+
+### Added
+
+- Suppressions allow now deleting an address passing an optional tag (#643 by @iwahara)
+- Allow both `^1.0` and `^2.0` for `php-http/guzzle6-adapter` (#680 by @boboldehampsink)
+- Add support for Mailing List `reply_preference` parameter (#684 by @twoonesixdigital)
+- Add support for `Force DKIM Authority` when creating a Domain (#686 by @Tiboonn)
+- Add support for PHP 7.4 (#698 by @snapshotpl)
+- Allow assigning a domain to a list of ips during creation (#703 by @josephshanak)
+- Add unmapped `log-level` property for Events (#704 by @uavn)
+
+### Fixed
+
+- Provide the Member's name just when it's not `null` (#639 by @indapublic)
+- Fix typehint for Message `ShowResponse::getContentIdMap()` (#664 by @lvdhoorn)
+- Fix endpoint for Domain's API (#668 by @tomschlick)
+- Webhook support for array handling (#675 by @martin-sanjuan)
+- Fix parameter name when assigning an IP to the specified Domain (#702 by @josephshanak)
+- `Ip::index()` now returns all IPs instead of the shared IPs (#707 by @josephshanak)
+
+### Changed
+
+- Updated examples for Debugging and Hydrator usage (#634 by @tonybolzan and #681 by @Jiia)
+- Updated link to the Mailgun Documentation page (#688 by @Casmo)
+- Remove deprecated Laravel package due to it is archived (#695 by @tomschlick)
+
+### Removed
+
+- Remove method for non-existing Stats URL (#705 by @uavn)
+
+## 3.0.0
+
+### Added
+
+- Support for PSR-4
+- All classes `Mailgun\Model` are final or abstract.
+
+### Changed
+
+- Dropped PHP5 support
+- Removed deprecated code
+- Moved `RequestBuilder` and `HttpClientConfigurator` to `Mailgun\HttpClient` namespace
+- Updated signature of `Mailgun::__construct()`
+
+### Removed
+
+- Dependency on `php-http/message`.
+
+## 2.8.1
+
+### Fixed
+
+- Added missing method to use all Mailing List and Ip features.
+
+## 2.8.0
+
+### Added
+
+- Add support for IPs endpoints
+- Add spport for Mailing Lists
+- Add `complaints` to Stats / Total Response
+- Add more tests for our models
+
+### Changed
+
+- Change the PHP Exception message for Bad Request errors to help to find the issue
+
+### Fixed
+
+- Fix an issue validating the max path length
+
+## 2.7.0
+
+### Added
+
+- Allow to set the Mailgun server when instantiating the Mailgun's client: `$mailgun = Mailgun::create('key', 'server');`
+- Add new PHPUnit tests for our models
+- Add new PHPUnit tests for our API
+- Added `Mailgun\Api\Attachment`
+- Fluent interface for `MessageBuilder` and `BatchMessage`
+- Support for HTTPlug 2.0
+
+### Changed
+
+- Second argument to `Mailgun\Message\MessageBuilder::addBccRecipient()` is now optional.
+- We try to close open resources
+
+### Fixed
+
+- Fixed the type error when creating tags.
+
+## 2.6.0
+
+### Added
+
+- Ported MessageBuilder and BatchMessage #472
+
+### Changed
+
+- Cast campaign IDs to string #460
+- Suggest packages used on Dev #440
+
+## 2.5.0
+
+### Added
+
+- Support for 413 HTTP status codes, when we send too large payloads to the API
+
+## 2.4.1
+
+### Added
+
+- Add new `Suppressions::getTotalCount()` method
+
+### Changed
+
+- Apply fixes from StyleCI
+- Updated `README.md` file
+
+### Fixed
+
+- Fix `Tags` on `Unsubscribe`
+- Fix typo on `Mailgun\Exception\HttpServerException`
+
+## 2.4.0
+
+### Added
+
+- Add cached property for DNS record
+- Add domain verification
+- `HttpClientException::getResponseCode()`
+- Added `AbstractDomainResponse` that `VerifyResponse` and `CreateResponse` extends.
+
+### Fixed
+
+- Possible empty content of `WebhookIndexResponse`.
+- Typo in `TotalResponse` that caused the content to be empty.
+
+### Changed
+
+- Allow some parameters to `Domain::create` to be optional.
+
+## 2.3.4
+
+### Fixed
+
+- Typo in DnsRecord::isValid. This make sure the correct result of the function is returned.
+
+## 2.3.3
+
+### Changed
+
+- Using stable version of `php-http/multipart-stream-builder`
+- Improved tests
+
+## 2.3.2
+
+### Fixed
+
+- When parsing an address in `MessageBuilder` we surround the recipient name with double quotes instead of single quotes.
+
+## 2.3.1
+
+### Fixed
+
+- Make sure to reset the `MultipartStreamBuilder` after a stream is built.
+
+## 2.3.0
+
+### Added
+
+- Support for sending messages with Mime. `$mailgun->messages()->sendMime()`
+
+## 2.2.0
+
+This version contains a new way of using the API. Each endpoint return a domain object and the
+endpoints are grouped like the API documentation.
+
+### Added
+
+- Api classes in Mailgun\Api\*
+- Api models/responses in Mailgun\Model\*
+- Added Hydrators to hydrate PSR-7 responses to arrays or domain objects.
+- All exceptions extend `Mailgun\Exception`.
+- New exceptions in `Mailgun\Exception` namespace.
+- Added `HttpClientConfigurator` to configure the HTTP client.
+- Added HttpClient plugins `History` and `ReplaceUriPlugin`
+- Assertions with Webmozart\Assert
+- `Mailgun\Mailgun::getLastResponse()`
+- `Mailgun\Connection\RestClient::getAttachment($url)`
+- Clear license information
+
+### Fixed
+
+- Fix disordered POST parameters. We do not use array syntax.
+- Code styles
+
+### Deprecated
+
+The following classes will be removed in version 3.0.
+
+- `Mailgun\Connection\Exceptions\GenericHTTPError`
+- `Mailgun\Connection\Exceptions\InvalidCredentials`
+- `Mailgun\Connection\Exceptions\MissingEndpoint`
+- `Mailgun\Connection\Exceptions\MissingRequiredParameters`
+- `Mailgun\Connection\Exceptions\NoDomainsConfigured`
+- `Mailgun\Connection\RestClient`
+- `Mailgun\Constants\Api`
+- `Mailgun\Constants\ExceptionMessages`
+- `Mailgun\Mailgun::$resetClient`
+- `Mailgun\Mailgun::sendMessage()`
+- `Mailgun\Mailgun::verifyWebhookSignature()`
+- `Mailgun\Mailgun::post()`
+- `Mailgun\Mailgun::get()`
+- `Mailgun\Mailgun::delete()`
+- `Mailgun\Mailgun::put()`
+- `Mailgun\Mailgun::setApiVersion()`
+- `Mailgun\Mailgun::setSslEnabled()`
+- `Mailgun\Mailgun::MessageBuilder()`
+- `Mailgun\Mailgun::OptInHandler()`
+- `Mailgun\Mailgun::BatchMessage()`
+
+## 2.1.2
+
+- Bug fixes with multiple recipients, inline images and attachments.
+- Added more tests
+- Using PSR-2 code style
+
+## 2.1.1
+
+- Require php-http/message (#142)
+- Declare BatchMessage::endpointUrl (#112)
+
+## 2.1.0
+
+- Strict comparison of hash (#117)
+- No dependency on Guzzle/PSR7 (#139)
+- Build URL string form an array (#138)
+- Docblock update (#134)
+- Minor fixes (#90, #121, #98)
+
+## 2.0
+
+- Migrated to PHP-HTTP (#94)
+- Dropped support for PHP 5.4.
+
+## 1.8.0
+
+- Updated to Guzzle5 (#79)
+- Updated default API version from v2 to v3 (#75)
+- Show response message on 400, 401 and 404. (#72)
+- PHP DocBlocks, Constants Changes, and Minor Refactors (#66)
+- Added PHP 7.0 support for Travis-CI, removed PHP 5.3 support (#79)
+
+## 1.7.2
+
+- Added webhook signature verification - (#50)
+- Test PHP 5.6 and HHVM - (#51)
+- Improved error handling - (#48)
+- Fixed attachment handling in Message Builder - (#56)
+- Allow any data type in custom data - (#57)
+- Return non-JSON response data - (#60)
+- Removed legacy closing braces - (#64)
+
+## 1.7.1
+
+- Improved security of OptInHandler - (#31)
+- Fixed typo for including an Exception - (#41)
+- Fixed Mocks, removed unnecessary code, applied styling - (#44 & #42)
+- Less restrictive Guzzle requirement - (#45)
+
+## 1.7 (2014-1-30)
+
+Bugfixes:
+  - patched bug for attachments related to duplicate aggregator bug in Guzzle (#32 @travelton)
+
+## 1.6 (2014-1-13)
+
+Enhancement:
+  - adjust file attachment/inline name (#21 @travelton)
+
+Bugfixes:
+  - fixed issue with unordered route actions (#23 @travelton)
+
+## 1.5 (2013-12-13)
+
+Enhancement:
+  - added ability to define non-https endpoint for debugging purposes (#23 @travelton)
+
+## 1.4 (2013-10-16)
+
+Bugfixes:
+  - template IDs were missing from recipient-variables (#15 @travelton)
+  - batch jobs trigger on to, cc, and bcc (#18 @travelton)
+  - batch jobs include recipient-variables for to, cc, and bcc (#18 @travelton)
+  - added method to return message-ids, for easier access (#19 @travelton)
+
+## 1.3 (2013-09-12)
+
+Bugfixes:
+
+  - relaxed Guzzle requirement (#7 @travelton)
+  - fixed reply-to bug (#9 @travelton)
+
+## 1.2 (2013-09-05)
+
+Bugfixes:
+
+  - fixed exception handling constants (@travelton)
+  - fixed MessageBuilder $baseAddress return (#1 @yoye)
+  - adjusted scope of recipient-variables (#3 @yoye)
+  - fixed misspellings of Exceptions (#2 @dboggus)
+  - undefined DEFAULT_TIME_ZONE (#4 @yoye)
+  - added message IDs to return for BatchMessage (@travelton)
+
+## 1.1 (2013-08-21)
+
+Initial Release!

+ 17 - 0
vendor/mailgun/mailgun-php/LICENSE

@@ -0,0 +1,17 @@
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.

+ 364 - 0
vendor/mailgun/mailgun-php/README.md

@@ -0,0 +1,364 @@
+# Mailgun PHP client
+
+This is the Mailgun PHP SDK. This SDK contains methods for easily interacting
+with the Mailgun API. Below are examples to get you started. For additional
+examples, please see our official documentation at http://documentation.mailgun.com
+
+[![Latest Version](https://img.shields.io/github/release/mailgun/mailgun-php.svg?style=flat-square)](https://github.com/mailgun/mailgun-php/releases)
+[![Total Downloads](https://img.shields.io/packagist/dt/mailgun/mailgun-php.svg?style=flat-square)](https://packagist.org/packages/mailgun/mailgun-php)
+[![Join the chat at https://gitter.im/mailgun/mailgun-php](https://badges.gitter.im/mailgun/mailgun-php.svg)](https://gitter.im/mailgun/mailgun-php?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
+
+## Installation
+
+To install the SDK, you will need to be using [Composer](http://getcomposer.org/)
+in your project.
+If you aren't using Composer yet, it's really simple! Here's how to install
+composer:
+
+```bash
+curl -sS https://getcomposer.org/installer | php
+```
+
+## Required minimum php version
+ - minimum php version 7.4
+
+The Mailgun API Client is not hard coupled to Guzzle, Buzz or any other library that sends
+HTTP messages. Instead, it uses the [PSR-18](https://www.php-fig.org/psr/psr-18/) client abstraction.
+This will give you the flexibility to choose what
+[PSR-7 implementation](https://packagist.org/providers/psr/http-factory-implementation)
+and [HTTP client](https://packagist.org/providers/psr/http-client-implementation)
+you want to use.
+
+If you just want to get started quickly you should run the following command:
+
+```bash
+composer require mailgun/mailgun-php symfony/http-client nyholm/psr7
+```
+
+## Usage
+
+You should always use Composer autoloader in your application to automatically load
+your dependencies. All the examples below assume you've already included this in your
+file:
+
+```php
+require 'vendor/autoload.php';
+use Mailgun\Mailgun;
+```
+
+Here's how to send a message using the SDK:
+
+```php
+// First, instantiate the SDK with your API credentials
+$mg = Mailgun::create('key-example'); // For US servers
+$mg = Mailgun::create('key-example', 'https://api.eu.mailgun.net'); // For EU servers
+
+// Now, compose and send your message.
+// $mg->messages()->send($domain, $params);
+$mg->messages()->send('example.com', [
+  'from'    => 'bob@example.com',
+  'to'      => 'sally@example.com',
+  'subject' => 'The PHP SDK is awesome!',
+  'text'    => 'It is so simple to send a message.'
+]);
+```
+
+Attention: `$domain` must match to the domain you have configured on [app.mailgun.com](https://app.mailgun.com/app/domains).
+
+### Usage of new method for updating web scheme
+
+```php
+# Include the Autoloader (see "Libraries" for install instructions)
+require 'vendor/autoload.php';
+use Mailgun\Mailgun;
+
+# Instantiate the client.
+$mgClient = Mailgun::create('KEY', 'FULL_DOMAIN_URL');
+$domain = "DOMAIN";
+
+# Issue the call to the client.
+$result = $mgClient->domains()->updateWebScheme($domain, 'https');
+
+print_r($result);
+```
+
+### Update web prefix
+
+```php
+# Include the Autoloader (see "Libraries" for install instructions)
+require 'vendor/autoload.php';
+use Mailgun\Mailgun;
+
+# Instantiate the client.
+$mgClient = Mailgun::create('KEY', 'FULL_DOMAIN_URL');
+$domain = "DOMAIN";
+
+# Issue the call to the client.
+$result = $mgClient->domains()->updateWebPrefix($domain, 'tracking');
+print_r($result);
+```
+
+ - Example of response
+```
+Mailgun\Model\Domain\WebPrefixResponse Object
+(
+    [message:Mailgun\Model\Domain\AbstractDomainResponse:private] => Domain web prefix updated
+    [domain:Mailgun\Model\Domain\AbstractDomainResponse:private] =>
+    [inboundDnsRecords:Mailgun\Model\Domain\AbstractDomainResponse:private] => Array
+        (
+        )
+    [outboundDnsRecords:Mailgun\Model\Domain\AbstractDomainResponse:private] => Array
+        (
+        )
+)
+```
+
+### Custom http request to the API
+
+```php
+<?php
+# Include the Autoloader (see "Libraries" for install instructions)
+require 'vendor/autoload.php';
+use Mailgun\Mailgun;
+
+# Instantiate the client.
+$mgClient = Mailgun::create('KEY', 'ENDPOINT');
+$domain = "DOMAIN";
+
+$path = 'some path';
+$params = [];
+
+# Issue the call to the client.
+$resultPost = $mgClient->httpClient()->httpPost($path, $params);
+
+$resultGet = $mgClient->httpClient()->httpGet($path, $params);
+
+$resultPut = $mgClient->httpClient()->httpPut($path, $params);
+
+$resultDelete = $mgClient->httpClient()->httpDelete($path, $params);
+
+```
+
+
+### SubAccounts
+
+```php
+//Enable Sub Account
+try {
+    $items = $mgClient->subaccounts()->enable($id);
+} catch (Exception $exception) {
+    echo sprintf('HTTP CODE - %s,', $exception->getCode());
+    echo sprintf('Error - %s', $exception->getMessage());
+}
+
+//Create a new Sub Account
+try {
+    $items = $mgClient->subaccounts()->create('some name');
+} catch (Exception $exception) {
+    echo sprintf('HTTP CODE - %s,', $exception->getCode());
+    echo sprintf('Error - %s', $exception->getMessage());
+}
+
+//Get All
+try {
+    $items = $mgClient->subaccounts()->index();
+
+    print_r($items->getItems());
+} catch (Exception $exception) {
+    echo sprintf('HTTP CODE - %s,', $exception->getCode());
+    echo sprintf('Error - %s', $exception->getMessage());
+}
+```
+### Performing API Requests "On Behalf Of" Subaccounts
+More Detailed you can read here - [https://help.mailgun.com/hc/en-us/articles/16380043681435-Subaccounts#01H2VMHAW8CN4A7WXM6ZFNSH4R](https://help.mailgun.com/hc/en-us/articles/16380043681435-Subaccounts#01H2VMHAW8CN4A7WXM6ZFNSH4R)
+```php
+$mgClient = Mailgun::create(
+    'xxx',
+    'yyy',
+    $subAccountId
+);
+```
+
+```php
+use Mailgun\HttpClient\HttpClientConfigurator;
+use Mailgun\Hydrator\NoopHydrator;
+
+$configurator = new HttpClientConfigurator();
+$configurator->setEndpoint('http://bin.mailgun.net/aecf68de');
+$configurator->setApiKey('key-example');
+$configurator->setSubAccountId($subAccountId)
+```
+
+### Load data from the Analytics API
+
+```php
+<?php
+# Include the Autoloader (see "Libraries" for install instructions)
+require 'vendor/autoload.php';
+
+use Mailgun\Mailgun;
+
+# Instantiate the client.
+$mgClient = Mailgun::create('xxx');
+$domain = "xxx.mailgun.org";
+
+$payload = [
+    "resolution" => "day",
+    "metrics" => [
+        "accepted_count",
+        "delivered_count",
+        "clicked_rate",
+        "opened_rate"
+    ],
+    "include_aggregates" => true,
+    "start" => "Sun, 22 Dec 2024 18:29:02 +0000",
+    "dimensions" => [
+        "time"
+    ],
+    "end" => "Wed, 25 Dec 2024 18:29:02 +0000",
+    "include_subaccounts" => true
+];
+
+$result = $mgClient->metrics()->loadMetrics($payload);
+
+print_r($result->getItems());
+
+````
+
+### All usage examples
+
+You will find more detailed documentation at [/doc](doc/index.md) and on
+[https://documentation.mailgun.com](https://documentation.mailgun.com/en/latest/api_reference.html).
+
+### Response
+
+The result of an API call is, by default, a domain object. This will make it easy
+to understand the response without reading the documentation. One can just read the
+doc blocks on the response classes. This provides an excellent IDE integration.
+
+```php
+$mg = Mailgun::create('key-example');
+$dns = $mg->domains()->show('example.com')->getInboundDNSRecords();
+
+foreach ($dns as $record) {
+  echo $record->getType();
+}
+```
+
+If you'd rather work with an array than an object you can inject the `ArrayHydrator`
+to the Mailgun class.
+
+```php
+use Mailgun\Hydrator\ArrayHydrator;
+
+$configurator = new HttpClientConfigurator();
+$configurator->setApiKey('key-example');
+
+$mg = new Mailgun($configurator, new ArrayHydrator());
+$data = $mg->domains()->show('example.com');
+
+foreach ($data['receiving_dns_records'] as $record) {
+  echo isset($record['record_type']) ? $record['record_type'] : null;
+}
+```
+
+You can also use the `NoopHydrator` to get a PSR7 Response returned from
+the API calls.
+
+**Warning: When using `NoopHydrator` there will be no exceptions on a non-200 response.**
+
+### Debugging
+
+Debugging the PHP SDK can be helpful when things aren't working quite right.
+To debug the SDK, here are some suggestions:
+
+Set the endpoint to Mailgun's Postbin. A Postbin is a web service that allows you to
+post data, which then you can display it through a browser. Using Postbin is an easy way
+to quickly determine what data you're transmitting to Mailgun's API.
+
+**Step 1 - Create a new Postbin.**
+Go to http://bin.mailgun.net. The Postbin will generate a special URL. Save that URL.
+
+**Step 2 - Instantiate the Mailgun client using Postbin.**
+
+*Tip: The bin id will be the URL part after bin.mailgun.net. It will be random generated letters and numbers.
+For example, the bin id in this URL (http://bin.mailgun.net/aecf68de) is `aecf68de`.*
+
+```php
+use Mailgun\HttpClient\HttpClientConfigurator;
+use Mailgun\Hydrator\NoopHydrator;
+
+$configurator = new HttpClientConfigurator();
+$configurator->setEndpoint('http://bin.mailgun.net/aecf68de');
+$configurator->setApiKey('key-example');
+$configurator->setDebug(true);
+
+$mg = new Mailgun($configurator, new NoopHydrator());
+
+# Now, compose and send your message.
+$mg->messages()->send('example.com', [
+  'from'    => 'bob@example.com',
+  'to'      => 'sally@example.com',
+  'subject' => 'The PHP SDK is awesome!',
+  'text'    => 'It is so simple to send a message.'
+]);
+```
+### Additional Info
+
+For usage examples on each API endpoint, head over to our official documentation
+pages.
+
+This SDK includes a [Message Builder](src/Message/README.md),
+[Batch Message](src/Message/README.md).
+
+Message Builder allows you to quickly create the array of parameters, required
+to send a message, by calling a methods for each parameter.
+Batch Message is an extension of Message Builder, and allows you to easily send
+a batch message job within a few seconds. The complexity of
+batch messaging is eliminated!
+
+## Framework integration
+
+If you are using a framework you might consider these composer packages to make the framework integration easier.
+
+* [tehplague/swiftmailer-mailgun-bundle](https://github.com/tehplague/swiftmailer-mailgun-bundle) for Symfony
+* [katanyoo/yii2-mailgun-mailer](https://github.com/katanyoo/yii2-mailgun-mailer) for Yii2
+* [narendravaghela/cakephp-mailgun](https://github.com/narendravaghela/cakephp-mailgun) for CakePHP
+* [drupal/mailgun](https://www.drupal.org/project/mailgun) for Drupal
+* [Laravel](https://laravel.com/docs/8.x/mail#mailgun-driver) Mail comes with Mailgun driver support
+
+## Contribute
+
+This SDK is an Open Source under the MIT license. It is, thus, maintained by collaborators and contributors.
+
+Feel free to contribute in any way. As an example you may:
+* Trying out the `dev-master` code
+* Create issues if you find problems
+* Reply to other people's issues
+* Review PRs
+
+### Running the test code
+
+If you want to run the tests you should run the following commands:
+
+```terminal
+git clone git@github.com:mailgun/mailgun-php.git
+cd mailgun-php
+composer update
+composer test
+```
+
+## Support and Feedback
+
+Be sure to visit the Mailgun official
+[documentation website](http://documentation.mailgun.com/) for additional
+information about our API.
+
+If you find a bug, please submit the issue in Github directly.
+[Mailgun-PHP Issues](https://github.com/mailgun/mailgun-php/issues)
+
+As always, if you need additional assistance, drop us a note through your account at
+[https://app.mailgun.com/support](https://app.mailgun.com/support).
+
+## Examples section
+[Examples section](doc/examples.md) contains examples of how to use the SDK.

+ 60 - 0
vendor/mailgun/mailgun-php/composer.json

@@ -0,0 +1,60 @@
+{
+    "name": "mailgun/mailgun-php",
+    "description": "The Mailgun SDK provides methods for all API functions.",
+    "license": "MIT",
+    "authors": [
+        {
+            "name": "Travis Swientek",
+            "email": "travis@mailgunhq.com"
+        }
+    ],
+    "require": {
+        "php": "^7.4 || ^8.0",
+        "php-http/client-common": "^2.2.1",
+        "php-http/discovery": "^1.19",
+        "php-http/multipart-stream-builder": "^1.1.2",
+        "psr/http-client": "^1.0",
+        "webmozart/assert": "^1.9.1"
+    },
+    "require-dev": {
+        "ergebnis/composer-normalize": "^2.43",
+        "nyholm/nsa": "^1.2.1",
+        "nyholm/psr7": "^1.3.1",
+        "phpcompatibility/php-compatibility": "^9.3",
+        "phpunit/phpunit": "^9.3",
+        "squizlabs/php_codesniffer": "^3.7",
+        "symfony/http-client": "^5.4 || ^6.3",
+        "vimeo/psalm": "^4.0 || ^5.25"
+    },
+    "suggest": {
+        "nyholm/psr7": "PSR-7 message implementation",
+        "symfony/http-client": "HTTP client"
+    },
+    "prefer-stable": true,
+    "autoload": {
+        "psr-4": {
+            "Mailgun\\": "src/"
+        }
+    },
+    "autoload-dev": {
+        "psr-4": {
+            "Mailgun\\Tests\\": "tests/"
+        }
+    },
+    "config": {
+        "allow-plugins": {
+            "ergebnis/composer-normalize": true,
+            "php-http/discovery": true
+        }
+    },
+    "extra": {
+        "branch-alias": {
+            "dev-master": "3.0-dev"
+        }
+    },
+    "scripts": {
+        "post-install-cmd": "\"vendor/bin/phpcs\" --config-set installed_paths vendor/phpcompatibility/php-compatibility",
+        "post-update-cmd": "\"vendor/bin/phpcs\" --config-set installed_paths vendor/phpcompatibility/php-compatibility",
+        "test": "vendor/bin/phpunit"
+    }
+}

+ 47 - 0
vendor/mailgun/mailgun-php/doc/attachments.md

@@ -0,0 +1,47 @@
+# Attachments
+
+You may attach a file from memory or by a file path.
+
+## From file path
+
+```php
+$mg->messages()->send('example.com', [
+  'from'    => 'bob@example.com', 
+  'to'      => 'sally@example.com', 
+  'subject' => 'Test file path attachments', 
+  'text'    => 'Test',
+  'attachment' => [
+    ['filePath'=>'/tmp/foo.jpg', 'filename'=>'test.jpg']
+  ]
+]);
+```
+## From memory
+
+```php
+// Some how load the file to memory
+$binaryFile = '[Binary data]';
+
+$mg->messages()->send('example.com', [
+  'from'    => 'bob@example.com', 
+  'to'      => 'sally@example.com', 
+  'subject' => 'Test memory attachments', 
+  'text'    => 'Test',
+  'attachment' => [
+    ['fileContent'=>$binaryFile, 'filename'=>'test.jpg']
+  ]
+]);
+```
+
+## Inline attachments
+
+```php
+$mg->messages()->send('example.com', [
+  'from'    => 'bob@example.com', 
+  'to'      => 'sally@example.com', 
+  'subject' => 'Test inline attachments', 
+  'text'    => 'Test',
+  'inline' => [
+    ['filePath'=>'/tmp/foo.jpg', 'filename'=>'test.jpg']
+  ]
+]);
+```

+ 101 - 0
vendor/mailgun/mailgun-php/doc/examples.md

@@ -0,0 +1,101 @@
+## Domain Keys Example
+
+```php
+<?php
+require 'vendor/autoload.php';
+
+use Mailgun\Mailgun;
+
+$mgClient = Mailgun::create('xxx');
+$domain = "xxx.mailgun.org";
+
+try {
+    $res = $mgClient->domainKeys()->listKeysForDomains();
+    print_r($res);
+} catch (Throwable $t) {
+    print_r($t->getMessage());
+    print_r($t->getTraceAsString());
+}
+
+try {
+    $res = $mgClient->domainKeys()->deleteDomainKey($domain, 'xxx');
+} catch (Throwable $t) {
+    print_r($t->getMessage());
+    print_r($t->getTraceAsString());
+}
+
+try {
+    $res = $mgClient->domainKeys()->listDomainKeys($domain);
+    print_r($res);
+} catch (Throwable $t) {
+    print_r($t->getMessage());
+    print_r($t->getTraceAsString());
+}
+try {
+    $res = $mgClient->domainKeys()->createDomainKey($domain, sprintf('key-%s', time()));
+    print_r($res);
+} catch (Throwable $t) {
+    print_r($t->getMessage());
+    print_r($t->getTraceAsString());
+}
+
+try {
+    $res = $mgClient->domainKeys()->deleteDomainKey($domain, 'key-xxx');
+} catch (Throwable $t) {
+    print_r($t->getMessage());
+    print_r($t->getTraceAsString());
+}
+
+
+try {
+    $res = $mgClient->domainKeys()->createDomainKey($domain, sprintf('key-%s', time()));
+    print_r($res);
+} catch (Throwable $t) {
+    print_r($t->getMessage());
+    print_r($t->getTraceAsString());
+}
+
+```
+
+
+## Account Management Examples
+
+```php
+<?php
+require 'vendor/autoload.php';
+
+use Mailgun\Mailgun;
+
+$mgClient = Mailgun::create('xxx');
+$domain = "yyy.mailgun.org";
+
+try {
+    $res = $mgClient->accountManagement()->addRecipientSandbox('xxx@gmail.com');
+    print_r($res);
+} catch (Throwable $t) {
+    print_r($t->getMessage());
+    print_r($t->getTraceAsString());
+}
+
+try {
+    $res = $mgClient->accountManagement()->getSandboxAuthRecipients();
+    print_r($res);
+} catch (Throwable $t) {
+    print_r($t->getMessage());
+    print_r($t->getTraceAsString());
+}
+
+try {
+    $res = $mgClient->accountManagement()->getHttpSigningKey();
+    print_r($res);
+} catch (Throwable $t) {
+    print_r($t->getMessage());
+    print_r($t->getTraceAsString());
+}
+
+
+
+
+//print_r($res);
+
+```

+ 367 - 0
vendor/mailgun/mailgun-php/doc/index.md

@@ -0,0 +1,367 @@
+# API documentation
+
+This page will document the API classes and ways to properly use the API. These resources will eventually move to
+the official documentation at [https://documentation.mailgun.com](https://documentation.mailgun.com/en/latest/api_reference.html).
+
+Other relevant documentation pages might be:
+
+* [Attachments](attachments.md)
+* [Pagination](pagination.md)
+* [Message Builder](/src/Message/README.md)
+* [Batch Message](/src/Message/README.md)
+
+## Domain API
+
+#### Get a list of all domains
+
+```php
+$mailgun->domains()->index();
+```
+
+#### Show a single domains
+
+```php
+$mailgun->domains()->show('example.com');
+```
+
+#### Verify a domain
+
+```php
+$mailgun->domains()->verify('example.com');
+```
+
+#### Create a new domain
+
+```php
+$mailgun->domains()->create('new.example.com', 'password', 'disable', '*');
+```
+
+#### Delete a domain
+
+```php
+$mailgun->domains()->delete('example.com');
+```
+
+#### Get credentials for a domain
+
+```php
+$mailgun->domains()->credentials('example.com');
+```
+
+#### Create credentials for a domain
+
+```php
+$mailgun->domains()->createCredential('example.com', 'login', 'password');
+```
+
+#### Update credentials for a domain
+
+```php
+$mailgun->domains()->updateCredential('example.com', 'login', 'password');
+```
+
+#### Delete credentials for a domain
+
+```php
+$mailgun->domains()->deleteCredential('example.com', 'login');
+```
+
+#### Get connection for a domain
+
+```php
+$mailgun->domains()->connection('example.com');
+```
+
+#### Update connection for a domain
+
+```php
+$mailgun->domains()->updateConnection('example.com', true, false);
+```
+
+## Email Validation API
+
+#### Validate a single address (v3)
+Note: Email address validation v3 has been depreciated in favor of v4.
+```php
+$mailgun->EmailValidation()->validate('alice@example.com');
+```
+
+#### Validate a single address (v4)
+```php
+$mailgun->EmailValidationV4()->validate('alice@example.com');
+```
+
+## Event API
+
+#### Get all events for a domain
+```php
+$mailgun->events()->get('example.com');
+```
+
+## Message API
+
+#### Send a message
+```php
+$parameters = [
+    'from'    => 'bob@example.com',
+    'to'      => 'sally@example.com',
+    'subject' => 'The PHP SDK is awesome!',
+    'text'    => 'It is so simple to send a message.'
+];
+$mailgun->messages()->send('example.com', $parameters);
+```
+#### Send a message with Mime (old version)
+
+Below in an example how to create a Mime message with SwiftMailer (this one is outdated).
+
+```php
+$message = new Swift_Message('Mail Subject');
+$message->setFrom(['from@exemple.com' => 'Example Inc']);
+$message->setTo(['user0gmail.com' => 'User 0', 'user1@hotmail.com' => 'User 1']);
+// $message->setBcc('admin@example.com'); Do not do this, BCC will be visible for all receipients if you do.
+$message->setCc('invoice@example.com');
+
+$messageBody = 'Look at the <b>fancy</b> HTML body.';
+$message->setBody($messageBody, 'text/html');
+
+// We need all "tos". Incluce the BCC here.
+$to = ['admin@example.com', 'user0gmail.com', 'user1@hotmail.com', 'invoice@example.com']
+
+// Send the message
+$mailgun->messages()->sendMime('example.com', $to, $message->toString(), []);
+```
+
+#### Send a message with Mime (Recommended)
+
+Below in an example how to create a Mime message with Symfony Mailer.
+
+```php
+$email = (new \Symfony\Component\Mime\Email())
+    ->from('mailgun@example.com')
+    ->to('bestcustomer@example.com')
+    ->cc('cc@example.com')
+    ->bcc('bcc@example.com')
+    ->replyTo('fabien@example.com')
+    //->priority('some priority')
+    ->subject('Time for Symfony Mailer!')
+    ->text('Sending emails is fun again!')
+    ->html('<p>Your awesome text!</p>');
+
+$mailgun->messages()->sendMime($domain, $recipients, $email->getHtmlBody(), $params);
+```
+
+#### Show a stored message
+
+If you got an URL to a stored message you may get the details by:
+
+```php
+$url = // ...
+$mailgun->messages()->show($url);
+```
+
+## Route API
+
+#### Show all routes
+
+```php
+$mailgun->routes()->index();
+```
+
+#### Show a routes
+
+Get a route by its ID
+
+```php
+$mailgun->routes()->show(4711);
+```
+#### Create a route
+
+```php
+$expression = "match_recipient('.*@gmail.com')";
+$actions = ["forward('alice@example.com')"];
+$description = 'Test route';
+
+$mailgun->routes()->create($expression, $actions, $description);
+```
+
+#### Update a route
+
+```php
+$expression = "match_recipient('.*@gmail.com')";
+$actions = ["forward('alice@example.com')"];
+$description = 'Test route';
+
+$mailgun->routes()->update(4711, $expression, $actions, $description);
+```
+
+#### Delete a route
+```php
+$mailgun->routes()->delete(4711);
+```
+
+## Stats API
+
+#### Get total stats for a domain
+```php
+$mailgun->stats()->total('example.com');
+```
+
+#### Get all stats for a domain
+```php
+$mailgun->stats()->all('example.com');
+```
+
+## Suppression API
+
+The suppression API consists of 3 parts; `Bounce`, `Complaint` and `Unsubscribe`.
+
+### Bounce API
+#### Get all bounces
+```php
+$mailgun->suppressions()->bounces()->index('example.com');
+```
+
+#### Show bounces for a specific address
+```php
+$mailgun->suppressions()->bounces()->show('example.com', 'alice@gmail.com');
+```
+
+#### Create a bounce
+```php
+$mailgun->suppressions()->bounces()->create('example.com', 'alice@gmail.com');
+```
+
+#### Delete a bounce
+```php
+$mailgun->suppressions()->bounces()->delete('example.com', 'alice@gmail.com');
+```
+
+#### Delete all bounces
+```php
+$mailgun->suppressions()->bounces()->deleteAll('example.com');
+```
+
+### Complaint API
+#### Get all complaints
+```php
+$mailgun->suppressions()->complaints()->index('example.com');
+```
+
+#### Show complaints for a specific address
+```php
+$mailgun->suppressions()->complaints()->show('example.com', 'alice@gmail.com');
+```
+
+#### Create a complaint
+```php
+$mailgun->suppressions()->complaints()->create('example.com', 'alice@gmail.com');
+```
+
+#### Delete a complaint
+```php
+$mailgun->suppressions()->complaints()->delete('example.com', 'alice@gmail.com');
+```
+
+#### Delete all complaints
+```php
+$mailgun->suppressions()->complaints()->deleteAll('example.com');
+```
+
+## Unsubscribe API
+
+#### Get all unsubscriptions
+```php
+$mailgun->suppressions()->unsubscribes()->index('example.com');
+```
+
+#### Show unsubscriptions for a specific address
+```php
+$mailgun->suppressions()->unsubscribes()->show('example.com', 'alice@gmail.com');
+```
+
+#### Create an unsubscription
+```php
+$mailgun->suppressions()->unsubscribes()->create('example.com', 'alice@gmail.com');
+```
+
+#### Delete an unsubscription
+```php
+$mailgun->suppressions()->unsubscribes()->delete('example.com', 'alice@gmail.com');
+```
+
+#### Delete all unsubscriptions
+```php
+$mailgun->suppressions()->unsubscribes()->deleteAll('example.com');
+```
+
+## Tag API
+
+#### Show all tags
+```php
+$mailgun->tags()->index('example.com');
+```
+
+#### Show a single tag
+```php
+$mailgun->tags()->show('example.com', 'foo');
+```
+
+#### Update a tag
+```php
+$mailgun->tags()->update('example.com', 'foo', 'description');
+```
+
+#### Show stats for a tag
+```php
+$mailgun->tags()->stats('example.com', 'foo');
+```
+
+#### Delete a tag
+```php
+$mailgun->tags()->delete('example.com', 'foo');
+```
+
+## Webhook API
+#### Verify webhook signature
+```php
+
+$timestamp = $_POST['timestamp'];
+$token = $_POST['token'];
+$signature = $_POST['signature'];
+
+$mailgun = Mailgun::create('my_api_key');
+$valid = $mailgun->webhooks()->verifyWebhookSignature($timestamp, $token, $signature);
+
+if (!$valid) {
+    // Create a 403 response
+
+    exit();
+}
+
+// The signature is valid
+```
+
+#### Show all webhooks
+```php
+$mailgun->webhooks()->index('example.com');
+```
+
+#### Show a single webhooks
+```php
+$mailgun->webhooks()->show('example.com', 'accept');
+```
+
+#### Create a webhooks
+```php
+$mailgun->webhooks()->create('example.com', 'opened', [ 'https://www.exmple.com/webhook' ]);
+```
+
+#### Update a webhooks
+```php
+$mailgun->webhooks()->update('example.com', 4711, [ 'https://www.exmple.com/webhook' ]);
+```
+
+#### Delete a webhooks
+```php
+$mailgun->webhooks()->delete('example.com', 4711);
+```

+ 17 - 0
vendor/mailgun/mailgun-php/doc/pagination.md

@@ -0,0 +1,17 @@
+# Pagination
+
+Some API endpoints do support pagination. 
+
+```php
+
+/** @var Mailgun\Model\Tag\IndexReponse $response */
+$response = $mailgun->tags()->index('example.com');
+
+// Parse through the first response
+// ...
+
+$nextResponse = $mailgun->tags()->nextPage($response);
+$previousResponse = $mailgun->tags()->previousPage($response);
+$firstResponse = $mailgun->tags()->firstPage($response);
+$lastResponse = $mailgun->tags()->lastPage($response);
+```

+ 7 - 0
vendor/mailgun/mailgun-php/phpstan.neon

@@ -0,0 +1,7 @@
+parameters:
+    level: 5
+    paths:
+      - src
+
+    excludes_analyse:
+        - %currentWorkingDirectory%/src/HttpClient/Plugin/HistoryTrait.php

+ 21 - 0
vendor/mailgun/mailgun-php/phpunit.xml.dist.bak

@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<phpunit bootstrap="vendor/autoload.php"
+         colors="true"
+         processIsolation="false"
+         stopOnFailure="false"
+         convertErrorsToExceptions="true"
+         convertNoticesToExceptions="true"
+         convertWarningsToExceptions="true">
+
+    <testsuites>
+        <testsuite name="Mailgun test suite">
+            <directory>tests</directory>
+        </testsuite>
+    </testsuites>
+
+    <filter>
+        <whitelist processUncoveredFilesFromWhitelist="true">
+            <directory suffix=".php">src</directory>
+        </whitelist>
+    </filter>
+</phpunit>

+ 125 - 0
vendor/mailgun/mailgun-php/psalm.baseline.xml

@@ -0,0 +1,125 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<files psalm-version="4.8.1@f73f2299dbc59a3e6c4d66cff4605176e728ee69">
+  <file src="src/Api/Domain.php">
+    <PossiblyUndefinedVariable occurrences="1">
+      <code>$params</code>
+    </PossiblyUndefinedVariable>
+    <RedundantCondition occurrences="3">
+      <code>boolean</code>
+      <code>boolean</code>
+      <code>nullOrString</code>
+    </RedundantCondition>
+    <TypeDoesNotContainNull occurrences="1">
+      <code>nullOrString</code>
+    </TypeDoesNotContainNull>
+  </file>
+  <file src="src/Api/HttpApi.php">
+    <ParamNameMismatch occurrences="1">
+      <code>$class</code>
+    </ParamNameMismatch>
+  </file>
+  <file src="src/Api/Ip.php">
+    <RedundantCondition occurrences="1">
+      <code>boolean</code>
+    </RedundantCondition>
+  </file>
+  <file src="src/Api/MailingList.php">
+    <PossiblyNullArgument occurrences="1">
+      <code>$this-&gt;hydrator</code>
+    </PossiblyNullArgument>
+    <RedundantCondition occurrences="1">
+      <code>isArray</code>
+    </RedundantCondition>
+  </file>
+  <file src="src/Api/MailingList/Member.php">
+    <RedundantCondition occurrences="2">
+      <code>isArray</code>
+      <code>isArray</code>
+    </RedundantCondition>
+  </file>
+  <file src="src/Api/Message.php">
+    <RedundantCondition occurrences="3">
+      <code>nullOrIsArray</code>
+      <code>string</code>
+      <code>string</code>
+    </RedundantCondition>
+    <TypeDoesNotContainNull occurrences="1">
+      <code>nullOrIsArray</code>
+    </TypeDoesNotContainNull>
+  </file>
+  <file src="src/Api/Pagination.php">
+    <PossiblyNullArgument occurrences="4">
+      <code>$response-&gt;getFirstUrl()</code>
+      <code>$response-&gt;getLastUrl()</code>
+      <code>$response-&gt;getNextUrl()</code>
+      <code>$response-&gt;getPreviousUrl()</code>
+    </PossiblyNullArgument>
+  </file>
+  <file src="src/Api/Route.php">
+    <RedundantCondition occurrences="1">
+      <code>isArray</code>
+    </RedundantCondition>
+  </file>
+  <file src="src/Api/Suppression/Complaint.php">
+    <PossiblyUndefinedVariable occurrences="1">
+      <code>$params</code>
+    </PossiblyUndefinedVariable>
+  </file>
+  <file src="src/Assert.php">
+    <MissingImmutableAnnotation occurrences="1">
+      <code>protected static function reportInvalidArgument($message)</code>
+    </MissingImmutableAnnotation>
+  </file>
+  <file src="src/HttpClient/Plugin/HistoryTrait.php">
+    <UnrecognizedStatement occurrences="1"/>
+  </file>
+  <file src="src/Mailgun.php">
+    <PossiblyNullArgument occurrences="1">
+      <code>$this-&gt;apiKey</code>
+    </PossiblyNullArgument>
+  </file>
+  <file src="src/Message/BatchMessage.php">
+    <PossiblyInvalidDocblockTag occurrences="4">
+      <code>@var string $first</code>
+      <code>@var string $full_name</code>
+      <code>@var string $id</code>
+      <code>@var string $last</code>
+    </PossiblyInvalidDocblockTag>
+    <PossiblyUndefinedMethod occurrences="1">
+      <code>getId</code>
+    </PossiblyUndefinedMethod>
+  </file>
+  <file src="src/Message/MessageBuilder.php">
+    <PossiblyInvalidDocblockTag occurrences="29">
+      <code>@var string</code>
+      <code>@var string</code>
+      <code>@var string</code>
+      <code>@var string $first</code>
+      <code>@var string $first</code>
+      <code>@var string $first</code>
+      <code>@var string $first</code>
+      <code>@var string $first</code>
+      <code>@var string $first</code>
+      <code>@var string $first</code>
+      <code>@var string $full_name</code>
+      <code>@var string $full_name</code>
+      <code>@var string $full_name</code>
+      <code>@var string $full_name</code>
+      <code>@var string $full_name</code>
+      <code>@var string $full_name</code>
+      <code>@var string $full_name</code>
+      <code>@var string $id If used with BatchMessage</code>
+      <code>@var string $id If used with BatchMessage</code>
+      <code>@var string $id If used with BatchMessage</code>
+      <code>@var string $id If used with BatchMessage</code>
+      <code>@var string $id If used with BatchMessage</code>
+      <code>@var string $last</code>
+      <code>@var string $last</code>
+      <code>@var string $last</code>
+      <code>@var string $last</code>
+      <code>@var string $last</code>
+      <code>@var string $last</code>
+      <code>@var string $last</code>
+    </PossiblyInvalidDocblockTag>
+  </file>
+</files>

+ 16 - 0
vendor/mailgun/mailgun-php/psalm.xml

@@ -0,0 +1,16 @@
+<?xml version="1.0"?>
+<psalm
+    errorLevel="3"
+    resolveFromConfigFile="true"
+    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+    xmlns="https://getpsalm.org/schema/config"
+    xsi:schemaLocation="https://getpsalm.org/schema/config vendor/vimeo/psalm/config.xsd"
+    errorBaseline="psalm.baseline.xml"
+>
+    <projectFiles>
+        <directory name="src" />
+        <ignoreFiles>
+            <directory name="vendor" />
+        </ignoreFiles>
+    </projectFiles>
+</psalm>

+ 95 - 0
vendor/mailgun/mailgun-php/src/Api/AccountManagement.php

@@ -0,0 +1,95 @@
+<?php
+
+declare(strict_types=1);
+
+/*
+ * Copyright (C) 2013 Mailgun
+ *
+ * This software may be modified and distributed under the terms
+ * of the MIT license. See the LICENSE file for details.
+ */
+
+namespace Mailgun\Api;
+
+use Mailgun\Model\AccountManagement\AccountResponse;
+use Mailgun\Model\AccountManagement\HttpSigningKeyResponse;
+use Mailgun\Model\AccountManagement\SandboxAuthRecipientsResponse;
+use Psr\Http\Client\ClientExceptionInterface;
+use Psr\Http\Message\ResponseInterface;
+
+class AccountManagement extends HttpApi
+{
+    /**
+     * Updates account settings.
+     * @param array $params
+     * @param array $requestHeaders
+     * @return AccountResponse|array|ResponseInterface
+     * @throws ClientExceptionInterface|\JsonException
+     * @throws \Exception
+     */
+    public function updateAccountSettings(array $params, array $requestHeaders = [])
+    {
+        $response = $this->httpPut('/v5/accounts', $params, $requestHeaders);
+
+        return $this->hydrateResponse($response, AccountResponse::class);
+    }
+
+    /**
+     * Retrieves the HTTP signing key.
+     * @param array $requestHeaders
+     * @return HttpSigningKeyResponse|array|ResponseInterface
+     * @throws ClientExceptionInterface
+     * @throws \Exception
+     */
+    public function getHttpSigningKey(array $requestHeaders = [])
+    {
+        $response = $this->httpGet('/v5/accounts/http_signing_key', [], $requestHeaders);
+
+        return $this->hydrateResponse($response, HttpSigningKeyResponse::class);
+    }
+
+    /**
+     * Creates a new HTTP signing key.
+     * @param array $requestHeaders
+     * @return HttpSigningKeyResponse|array|ResponseInterface
+     * @throws ClientExceptionInterface
+     * @throws \JsonException
+     * @throws \Exception
+     */
+    public function createHttpSigningKey(array $requestHeaders = [])
+    {
+        $response = $this->httpPost('/v5/accounts/http_signing_key', [], $requestHeaders);
+
+        return $this->hydrateResponse($response, HttpSigningKeyResponse::class);
+    }
+
+    /**
+     * Retrieves the list of sandbox authorized recipients.
+     * @param array $requestHeaders
+     * @return SandboxAuthRecipientsResponse|array|ResponseInterface
+     * @throws ClientExceptionInterface
+     * @throws \Exception
+     */
+    public function getSandboxAuthRecipients(array $requestHeaders = [])
+    {
+        $response = $this->httpGet('/v5/sandbox/auth_recipients', [], $requestHeaders);
+
+        return $this->hydrateResponse($response, SandboxAuthRecipientsResponse::class);
+    }
+
+    /**
+     * Add authorized email recipient for a sandbox domain
+     * @param string $email
+     * @param array $requestHeaders
+     * @return SandboxAuthRecipientsResponse|array|ResponseInterface
+     * @throws ClientExceptionInterface
+     * @throws \JsonException
+     * @throws \Exception
+     */
+    public function addRecipientSandbox(string $email, array $requestHeaders = [])
+    {
+        $response = $this->httpPost('/v5/sandbox/auth_recipients', ['email' => $email], $requestHeaders);
+
+        return $this->hydrateResponse($response, SandboxAuthRecipientsResponse::class);
+    }
+}

+ 45 - 0
vendor/mailgun/mailgun-php/src/Api/Attachment.php

@@ -0,0 +1,45 @@
+<?php
+
+declare(strict_types=1);
+
+/*
+ * Copyright (C) 2013 Mailgun
+ *
+ * This software may be modified and distributed under the terms
+ * of the MIT license. See the LICENSE file for details.
+ */
+
+namespace Mailgun\Api;
+
+use Mailgun\Assert;
+use Mailgun\Exception\UnknownErrorException;
+use Psr\Http\Client\ClientExceptionInterface;
+use Psr\Http\Message\ResponseInterface;
+
+/**
+ * @author Tobias Nyholm <tobias.nyholm@gmail.com>
+ */
+class Attachment extends HttpApi
+{
+    /**
+     * @param  string                   $url
+     * @param  array                    $requestHeaders
+     * @return ResponseInterface
+     * @throws ClientExceptionInterface
+     * @throws UnknownErrorException
+     */
+    public function show(string $url, array $requestHeaders = []): ResponseInterface
+    {
+        Assert::stringNotEmpty($url);
+        Assert::regex($url, '@https://.*mailgun\.(net|org)/v.+@');
+        Assert::regex($url, '|/attachments/[0-9]+|');
+
+        $response = $this->httpGet($url, [], $requestHeaders);
+
+        if (200 !== $response->getStatusCode()) {
+            $this->handleErrors($response);
+        }
+
+        return $response;
+    }
+}

+ 549 - 0
vendor/mailgun/mailgun-php/src/Api/Domain.php

@@ -0,0 +1,549 @@
+<?php
+
+declare(strict_types=1);
+
+/*
+ * Copyright (C) 2013 Mailgun
+ *
+ * This software may be modified and distributed under the terms
+ * of the MIT license. See the LICENSE file for details.
+ */
+
+namespace Mailgun\Api;
+
+use Exception;
+use Mailgun\Assert;
+use Mailgun\Model\Domain\CertStatusResponse;
+use Mailgun\Model\Domain\ConnectionResponse;
+use Mailgun\Model\Domain\CreateCredentialResponse;
+use Mailgun\Model\Domain\CreateResponse;
+use Mailgun\Model\Domain\CredentialResponse;
+use Mailgun\Model\Domain\DeleteCredentialResponse;
+use Mailgun\Model\Domain\DeleteResponse;
+use Mailgun\Model\Domain\IndexResponse;
+use Mailgun\Model\Domain\ShowResponse;
+use Mailgun\Model\Domain\TrackingResponse;
+use Mailgun\Model\Domain\UpdateClickTrackingResponse;
+use Mailgun\Model\Domain\UpdateConnectionResponse;
+use Mailgun\Model\Domain\UpdateCredentialResponse;
+use Mailgun\Model\Domain\UpdateOpenTrackingResponse;
+use Mailgun\Model\Domain\UpdateUnsubscribeTrackingResponse;
+use Mailgun\Model\Domain\VerifyResponse;
+use Mailgun\Model\Domain\WebPrefixResponse;
+use Mailgun\Model\Domain\WebSchemeResponse;
+use Psr\Http\Client\ClientExceptionInterface;
+use Psr\Http\Message\ResponseInterface;
+
+/**
+ * @see https://documentation.mailgun.com/en/latest/api-domains.html
+ *
+ * @author Sean Johnson <sean@mailgun.com>
+ */
+class Domain extends HttpApi
+{
+    private const DKIM_SIZES = ['1024', '2048'];
+
+    /**
+     * Returns a list of domains on the account.
+     * @param  int                      $limit
+     * @param  int                      $skip
+     * @param  array                    $requestHeaders
+     * @return IndexResponse|array
+     * @throws ClientExceptionInterface
+     */
+    public function index(int $limit = 100, int $skip = 0, array $requestHeaders = [])
+    {
+        Assert::range($limit, 1, 1000);
+
+        $params = [
+            'limit' => $limit,
+            'skip' => $skip,
+        ];
+
+        $response = $this->httpGet('/v3/domains', $params, $requestHeaders);
+
+        return $this->hydrateResponse($response, IndexResponse::class);
+    }
+
+    /**
+     * Returns a single domain.
+     * @param  string                               $domain         name of the domain
+     * @param  array                                $requestHeaders
+     * @return ShowResponse|array|ResponseInterface
+     * @throws ClientExceptionInterface
+     */
+    public function show(string $domain, array $requestHeaders = [])
+    {
+        Assert::stringNotEmpty($domain);
+
+        $response = $this->httpGet(sprintf('/v3/domains/%s', $domain), [], $requestHeaders);
+
+        return $this->hydrateResponse($response, ShowResponse::class);
+    }
+
+    /**
+     * Creates a new domain for the account.
+     * See below for spam filtering parameter information.
+     * {@link https://documentation.mailgun.com/en/latest/user_manual.html#um-spam-filter}.
+     *
+     * @see    https://documentation.mailgun.com/en/latest/api-domains.html#domains
+     * @param  string                                 $domain             name of the domain
+     * @param  string|null                            $smtpPass           password for SMTP authentication
+     * @param  string|null                            $spamAction         `disable` or `tag` - inbound spam filtering
+     * @param  bool                                   $wildcard           domain will accept email for subdomains
+     * @param  bool                                   $forceDkimAuthority force DKIM authority
+     * @param  string[]                               $ips                an array of ips to be assigned to the domain
+     * @param  ?string                                $pool_id            pool id to assign to the domain
+     * @param  string                                 $webScheme          `http` or `https` - set your open, click and unsubscribe URLs to use http or https. The default is http
+     * @param  string                                 $dkimKeySize        Set length of your domain’s generated DKIM
+     *                                                                    key
+     * @return CreateResponse|array|ResponseInterface
+     * @throws Exception
+     */
+    public function create(
+        string $domain,
+        ?string $smtpPass = null,
+        ?string $spamAction = null,
+        ?bool $wildcard = null,
+        ?bool $forceDkimAuthority = null,
+        ?array $ips = null,
+        ?string $pool_id = null,
+        string $webScheme = 'http',
+        string $dkimKeySize = '1024',
+        array $requestHeaders = []
+    ) {
+        Assert::stringNotEmpty($domain);
+
+        $params['name'] = $domain;
+
+        if (!empty($smtpPass)) {
+            Assert::stringNotEmpty($smtpPass);
+
+            $params['smtp_password'] = $smtpPass;
+        }
+
+        if (!empty($spamAction)) {
+            Assert::stringNotEmpty($spamAction);
+
+            $params['spam_action'] = $spamAction;
+        }
+
+        if (null !== $wildcard) {
+            Assert::boolean($wildcard);
+
+            $params['wildcard'] = $wildcard ? 'true' : 'false';
+        }
+
+        if (null !== $forceDkimAuthority) {
+            Assert::boolean($forceDkimAuthority);
+
+            $params['force_dkim_authority'] = $forceDkimAuthority ? 'true' : 'false';
+        }
+
+        if (null !== $ips) {
+            Assert::isList($ips);
+            Assert::allString($ips);
+
+            $params['ips'] = join(',', $ips);
+        }
+
+        if (!empty($webScheme)) {
+            Assert::stringNotEmpty($webScheme);
+            Assert::oneOf($webScheme, ['https', 'http']);
+            $params['web_scheme'] = $webScheme;
+        }
+
+        if (null !== $pool_id) {
+            Assert::stringNotEmpty($pool_id);
+
+            $params['pool_id'] = $pool_id;
+        }
+        if (!empty($dkimKeySize)) {
+            Assert::oneOf(
+                $dkimKeySize,
+                self::DKIM_SIZES,
+                'Length of your domain’s generated DKIM key must be 1024 or 2048'
+            );
+            $params['dkim_key_size'] = $dkimKeySize;
+        }
+
+        $response = $this->httpPost('/v3/domains', $params, $requestHeaders);
+
+        return $this->hydrateResponse($response, CreateResponse::class);
+    }
+
+    /**
+     * Removes a domain from the account.
+     * WARNING: This action is irreversible! Be cautious!
+     * @param  string                                 $domain         name of the domain
+     * @param  array                                  $requestHeaders
+     * @return DeleteResponse|array|ResponseInterface
+     * @throws ClientExceptionInterface
+     */
+    public function delete(string $domain, array $requestHeaders = [])
+    {
+        Assert::stringNotEmpty($domain);
+
+        $response = $this->httpDelete(sprintf('/v3/domains/%s', $domain), [], $requestHeaders);
+
+        return $this->hydrateResponse($response, DeleteResponse::class);
+    }
+
+    /**
+     * Returns a list of SMTP credentials for the specified domain.
+     * @param  string                   $domain         name of the domain
+     * @param  int                      $limit          Number of credentials to return
+     * @param  int                      $skip           Number of credentials to omit from the list
+     * @param  array                    $requestHeaders
+     * @return CredentialResponse
+     * @throws ClientExceptionInterface
+     */
+    public function credentials(string $domain, int $limit = 100, int $skip = 0, array $requestHeaders = [])
+    {
+        Assert::stringNotEmpty($domain);
+        $params = [
+            'limit' => $limit,
+            'skip' => $skip,
+        ];
+
+        $response = $this->httpGet(sprintf('/v3/domains/%s/credentials', $domain), $params, $requestHeaders);
+
+        return $this->hydrateResponse($response, CredentialResponse::class);
+    }
+
+    /**
+     * Create a new SMTP credential pair for the specified domain.
+     * @param  string                                           $domain         name of the domain
+     * @param  string                                           $login          SMTP Username
+     * @param  string                                           $password       SMTP Password. Length min 5, max 32.
+     * @param  array                                            $requestHeaders
+     * @return CreateCredentialResponse|array|ResponseInterface
+     * @throws ClientExceptionInterface
+     */
+    public function createCredential(string $domain, string $login, string $password, array $requestHeaders = [])
+    {
+        Assert::stringNotEmpty($domain);
+        Assert::stringNotEmpty($login);
+        Assert::stringNotEmpty($password);
+        Assert::lengthBetween($password, 5, 32, 'SMTP password must be between 5 and 32 characters.');
+
+        $params = [
+            'login' => $login,
+            'password' => $password,
+        ];
+
+        $response = $this->httpPost(sprintf('/v3/domains/%s/credentials', $domain), $params, $requestHeaders);
+
+        return $this->hydrateResponse($response, CreateCredentialResponse::class);
+    }
+
+    /**
+     * Update a set of SMTP credentials for the specified domain.
+     *
+     * @param string $domain name of the domain
+     * @param string $login  SMTP Username
+     * @param string $pass   New SMTP Password. Length min 5, max 32.
+     *
+     * @return UpdateCredentialResponse|array|ResponseInterface
+     * @throws ClientExceptionInterface
+     */
+    public function updateCredential(string $domain, string $login, string $pass, array $requestHeaders = [])
+    {
+        Assert::stringNotEmpty($domain);
+        Assert::stringNotEmpty($login);
+        Assert::stringNotEmpty($pass);
+        Assert::lengthBetween($pass, 5, 32, 'SMTP password must be between 5 and 32 characters.');
+
+        $params = [
+            'password' => $pass,
+        ];
+
+        $response = $this->httpPut(sprintf('/v3/domains/%s/credentials/%s', $domain, $login), $params, $requestHeaders);
+
+        return $this->hydrateResponse($response, UpdateCredentialResponse::class);
+    }
+
+    /**
+     * Remove a set of SMTP credentials from the specified domain.
+     * @param  string                                           $domain         name of the domain
+     * @param  string                                           $login          SMTP Username
+     * @param  array                                            $requestHeaders
+     * @return DeleteCredentialResponse|array|ResponseInterface
+     * @throws ClientExceptionInterface
+     */
+    public function deleteCredential(string $domain, string $login, array $requestHeaders = [])
+    {
+        Assert::stringNotEmpty($domain);
+        Assert::stringNotEmpty($login);
+
+        $response = $this->httpDelete(
+            sprintf(
+                '/v3/domains/%s/credentials/%s',
+                $domain,
+                $login
+            ),
+            [],
+            $requestHeaders
+        );
+
+        return $this->hydrateResponse($response, DeleteCredentialResponse::class);
+    }
+
+    /**
+     * Returns delivery connection settings for the specified domain.
+     * @param  string                               $domain         name of the domain
+     * @param  array                                $requestHeaders
+     * @return ConnectionResponse|ResponseInterface
+     * @throws ClientExceptionInterface
+     */
+    public function connection(string $domain, array $requestHeaders = [])
+    {
+        Assert::stringNotEmpty($domain);
+
+        $response = $this->httpGet(sprintf('/v3/domains/%s/connection', $domain), [], $requestHeaders);
+
+        return $this->hydrateResponse($response, ConnectionResponse::class);
+    }
+
+    /**
+     * Updates the specified delivery connection settings for the specified domain.
+     * If a parameter is passed in as null, it will not be updated.
+     * @param  string                                           $domain         name of the domain
+     * @param  bool|null                                        $requireTLS     enforces that messages are sent only over a TLS connection
+     * @param  bool|null                                        $noVerify       disables TLS certificate and hostname verification
+     * @param  array                                            $requestHeaders
+     * @return UpdateConnectionResponse|array|ResponseInterface
+     * @throws ClientExceptionInterface
+     */
+    public function updateConnection(string $domain, ?bool $requireTLS, ?bool $noVerify, array $requestHeaders = [])
+    {
+        Assert::stringNotEmpty($domain);
+        $params = [];
+
+        if (null !== $requireTLS) {
+            $params['require_tls'] = $requireTLS ? 'true' : 'false';
+        }
+
+        if (null !== $noVerify) {
+            $params['skip_verification'] = $noVerify ? 'true' : 'false';
+        }
+
+        $response = $this->httpPut(sprintf('/v3/domains/%s/connection', $domain), $params, $requestHeaders);
+
+        return $this->hydrateResponse($response, UpdateConnectionResponse::class);
+    }
+
+    /**
+     * Update webScheme for existing domain
+     * See below for spam filtering parameter information.
+     * {@link https://documentation.mailgun.com/en/latest/user_manual.html#um-spam-filter}.
+     * @see https://documentation.mailgun.com/en/latest/api-domains.html#domains
+     * @param  string                                    $domain         name of the domain
+     * @param  string                                    $webScheme      `http` or `https` - set your open, click and unsubscribe URLs to use http or https. The default is http
+     * @param  array                                     $requestHeaders
+     * @return WebSchemeResponse|array|ResponseInterface
+     * @throws ClientExceptionInterface
+     */
+    public function updateWebScheme(string $domain, string $webScheme = 'http', array $requestHeaders = [])
+    {
+        $params = [];
+        Assert::stringNotEmpty($domain);
+        Assert::stringNotEmpty($webScheme);
+        Assert::oneOf($webScheme, ['https', 'http']);
+
+        $params['web_scheme'] = $webScheme;
+
+        $response = $this->httpPut(sprintf('/v3/domains/%s', $domain), $params, $requestHeaders);
+
+        return $this->hydrateResponse($response, WebSchemeResponse::class);
+    }
+
+    /**
+     * Returns a single domain.
+     * @param  string                                 $domain         name of the domain
+     * @param  array                                  $requestHeaders
+     * @return VerifyResponse|array|ResponseInterface
+     * @throws ClientExceptionInterface
+     */
+    public function verify(string $domain, array $requestHeaders = [])
+    {
+        Assert::stringNotEmpty($domain);
+
+        $response = $this->httpPut(sprintf('/v3/domains/%s/verify', $domain), [], $requestHeaders);
+
+        return $this->hydrateResponse($response, VerifyResponse::class);
+    }
+
+    /**
+     * Returns a domain tracking settings.
+     * @param  string                                   $domain         name of the domain
+     * @param  array                                    $requestHeaders
+     * @return TrackingResponse|array|ResponseInterface
+     * @throws ClientExceptionInterface
+     */
+    public function tracking(string $domain, array $requestHeaders = [])
+    {
+        Assert::stringNotEmpty($domain);
+
+        $response = $this->httpGet(sprintf('/v3/domains/%s/tracking', $domain), [], $requestHeaders);
+
+        return $this->hydrateResponse($response, TrackingResponse::class);
+    }
+
+    /**
+     * Updates a domain click tracking settings.
+     * @param  string                                              $domain         The name of the domain
+     * @param  string                                              $active         The status for this tracking (one of: yes, no)
+     * @param  array                                               $requestHeaders
+     * @return UpdateClickTrackingResponse|array|ResponseInterface
+     * @throws ClientExceptionInterface
+     */
+    public function updateClickTracking(string $domain, string $active, array $requestHeaders = [])
+    {
+        Assert::stringNotEmpty($domain);
+        Assert::stringNotEmpty($active);
+        Assert::oneOf($active, ['yes', 'no', 'htmlonly']);
+
+        $params = [
+            'active' => $active,
+        ];
+
+        $response = $this->httpPut(sprintf('/v3/domains/%s/tracking/click', $domain), $params, $requestHeaders);
+
+        return $this->hydrateResponse($response, UpdateClickTrackingResponse::class);
+    }
+
+    /**
+     * Updates a domain open tracking settings.
+     * @param  string                                             $domain         The name of the domain
+     * @param  string                                             $active         The status for this tracking (one of: yes, no)
+     * @param  array                                              $requestHeaders
+     * @return UpdateOpenTrackingResponse|array|ResponseInterface
+     * @throws ClientExceptionInterface
+     */
+    public function updateOpenTracking(string $domain, string $active, array $requestHeaders = [])
+    {
+        Assert::stringNotEmpty($domain);
+        Assert::stringNotEmpty($active);
+        Assert::oneOf($active, ['yes', 'no']);
+
+        $params = [
+            'active' => $active,
+        ];
+
+        $response = $this->httpPut(sprintf('/v3/domains/%s/tracking/open', $domain), $params, $requestHeaders);
+
+        return $this->hydrateResponse($response, UpdateOpenTrackingResponse::class);
+    }
+
+    /**
+     * Updates a domain unsubscribe tracking settings.
+     * @param  string                                                    $domain         The name of the domain
+     * @param  string                                                    $active         The status for this tracking (one of: yes, no)
+     * @param  string                                                    $htmlFooter     The footer for HTML emails
+     * @param  string                                                    $textFooter     The footer for plain text emails
+     * @param  array                                                     $requestHeaders
+     * @return UpdateUnsubscribeTrackingResponse|array|ResponseInterface
+     * @throws ClientExceptionInterface
+     */
+    public function updateUnsubscribeTracking(string $domain, string $active, string $htmlFooter, string $textFooter, array $requestHeaders = [])
+    {
+        Assert::stringNotEmpty($domain);
+        Assert::stringNotEmpty($active);
+        Assert::oneOf($active, ['yes', 'no', 'true', 'false']);
+        Assert::stringNotEmpty($htmlFooter);
+        Assert::nullOrString($textFooter);
+
+        $params = [
+            'active' => (in_array($active, ['yes', 'true'], true)) ? 'true' : 'false',
+            'html_footer' => $htmlFooter,
+            'text_footer' => $textFooter,
+        ];
+
+        $response = $this->httpPut(sprintf('/v3/domains/%s/tracking/unsubscribe', $domain), $params, $requestHeaders);
+
+        return $this->hydrateResponse($response, UpdateUnsubscribeTrackingResponse::class);
+    }
+
+    /**
+     * Updates a CNAME used for tracking opens and clicks.
+     *
+     * @param string $domain    The name of the domain
+     * @param string $webPrefix The tracking CNAME for a domain
+     *
+     * @return WebPrefixResponse|array|ResponseInterface
+     * @throws ClientExceptionInterface
+     */
+    public function updateWebPrefix(string $domain, string $webPrefix)
+    {
+        Assert::stringNotEmpty($domain);
+        Assert::stringNotEmpty($webPrefix);
+
+        $params = [
+            'web_prefix' => $webPrefix,
+        ];
+
+        $response = $this->httpPut(sprintf('/v3/domains/%s/web_prefix', $domain), $params);
+
+        return $this->hydrateResponse($response, WebPrefixResponse::class);
+    }
+
+    /**
+     * Status of x509 TLS certificate
+     * @link https://documentation.mailgun.com/docs/mailgun/api-reference/openapi-final/tag/Domain-Tracking/#tag/Domain-Tracking/operation/httpapi.(*HttpAPI).getStatusV2-fm-8
+     *
+     * @param string $domain
+     * @return mixed|ResponseInterface
+     * @throws ClientExceptionInterface
+     * @throws \JsonException
+     * @throws Exception
+     */
+    public function statusOf509Crt(string $domain)
+    {
+        Assert::stringNotEmpty($domain);
+
+        $response = $this->httpGet(sprintf('/v2/x509/%s}/status', $domain), []);
+
+        return $this->hydrateResponse($response, CertStatusResponse::class);
+    }
+
+    /**
+     * @param string $domain
+     * @return mixed|ResponseInterface
+     * @throws ClientExceptionInterface
+     * @throws \JsonException
+     * @throws Exception
+     */
+    public function generate509Tls(string $domain)
+    {
+        Assert::stringNotEmpty($domain);
+
+        $params = [
+            'domain' => $domain,
+        ];
+
+        $response = $this->httpPost(sprintf('/v2/x509/%s', $domain), $params);
+
+        return $this->hydrateResponse($response, CertStatusResponse::class);
+    }
+
+    /**
+     * @param string $domain
+     * @return mixed|ResponseInterface
+     * @throws ClientExceptionInterface
+     * @throws \JsonException
+     * @throws Exception
+     */
+    public function regenerateExpiredCrt(string $domain)
+    {
+        Assert::stringNotEmpty($domain);
+
+        $params = [
+            'domain' => $domain,
+        ];
+
+        $response = $this->httpPut(sprintf('/v2/x509/%s', $domain), $params);
+
+        return $this->hydrateResponse($response, CertStatusResponse::class);
+    }
+}

+ 142 - 0
vendor/mailgun/mailgun-php/src/Api/DomainKeys.php

@@ -0,0 +1,142 @@
+<?php
+
+declare(strict_types=1);
+
+/*
+ * Copyright (C) 2013 Mailgun
+ *
+ * This software may be modified and distributed under the terms
+ * of the MIT license. See the LICENSE file for details.
+ */
+
+namespace Mailgun\Api;
+
+use Exception;
+use Mailgun\Assert;
+use Mailgun\Model\Domain\DeleteResponse;
+use Mailgun\Model\Domain\DomainKeyResponse;
+use Mailgun\Model\Domain\IndexResponse;
+use Psr\Http\Client\ClientExceptionInterface;
+use Psr\Http\Message\ResponseInterface;
+
+/**
+ * @see https://documentation.mailgun.com/docs/mailgun/api-reference/openapi-final/tag/Domain-Keys/
+ *
+ */
+class DomainKeys extends HttpApi
+{
+    private const BITS_SIZE = ['1024', '2048'];
+
+    /**
+     * Returns a list of domains on the account.
+     * @param int|null $limit
+     * @param string|null $page
+     * @param string|null $signingDomain
+     * @param string|null $selector
+     * @param array $requestHeaders
+     * @return IndexResponse|array
+     * @throws ClientExceptionInterface
+     * @throws \JsonException
+     * @throws Exception
+     */
+    public function listKeysForDomains(?int $limit = null, ?string $page = null, ?string $signingDomain = null, ?string $selector = null, array $requestHeaders = [])
+    {
+        $params = [];
+        if (isset($limit)) {
+            Assert::range($limit, 1, 1000);
+            $params['limit'] = $limit;
+        }
+
+        if (isset($page)) {
+            Assert::stringNotEmpty($page);
+            $params['page'] = $page;
+        }
+
+        if (isset($signingDomain)) {
+            $params['signing_domain'] = $signingDomain;
+        }
+
+        if (isset($selector)) {
+            $params['selector'] = $signingDomain;
+        }
+
+        $response = $this->httpGet('/v1/dkim/keys', $params, $requestHeaders);
+
+        return $this->hydrateResponse($response, IndexResponse::class);
+    }
+
+    /**
+     * Returns a list of domains on the account.
+     * @param string $authorityName
+     * @param array $requestHeaders
+     * @return IndexResponse|array
+     * @throws ClientExceptionInterface
+     * @throws \JsonException
+     * @throws Exception
+     */
+    public function listDomainKeys(string $authorityName, array $requestHeaders = [])
+    {
+        Assert::stringNotEmpty($authorityName);
+
+        $response = $this->httpGet(sprintf('/v4/domains/%s/keys', $authorityName), [], $requestHeaders);
+
+        return $this->hydrateResponse($response, DomainKeyResponse::class);
+    }
+
+    /**
+     * @param string $signingDomain
+     * @param string $selector
+     * @param string|null $bits
+     * @param array $requestHeaders
+     * @return mixed|ResponseInterface
+     * @throws ClientExceptionInterface
+     * @throws \JsonException
+     * @throws Exception
+     */
+    public function createDomainKey(string $signingDomain, string $selector, ?string $bits = null, array $requestHeaders = [])
+    {
+        Assert::stringNotEmpty($signingDomain);
+        Assert::stringNotEmpty($selector);
+
+        $params = [
+            'signing_domain' => $signingDomain,
+            'selector' => $selector,
+        ];
+
+        if (!empty($bits)) {
+            Assert::oneOf(
+                $bits,
+                self::BITS_SIZE,
+                'Length of your domain’s generated DKIM key must be 1024 or 2048'
+            );
+            $params['bits'] = $bits;
+        }
+
+        $response = $this->httpPost('/v1/dkim/keys', $params, $requestHeaders);
+
+        return $this->hydrateResponse($response, DomainKeyResponse::class);
+    }
+
+    /**
+     * @param string $signingDomain
+     * @param string $selector
+     * @param array $requestHeaders
+     * @return mixed|ResponseInterface
+     * @throws ClientExceptionInterface
+     * @throws \JsonException
+     */
+    public function deleteDomainKey(string $signingDomain, string $selector, array $requestHeaders = [])
+    {
+        Assert::stringNotEmpty($signingDomain);
+        Assert::stringNotEmpty($selector);
+
+        $params = [
+            'signing_domain' => $signingDomain,
+            'selector' => $selector,
+        ];
+
+        $response = $this->httpDelete('/v1/dkim/keys', $params, $requestHeaders);
+
+        return $this->hydrateResponse($response, DeleteResponse::class);
+    }
+}

+ 524 - 0
vendor/mailgun/mailgun-php/src/Api/DomainV4.php

@@ -0,0 +1,524 @@
+<?php
+
+declare(strict_types=1);
+
+/*
+ * Copyright (C) 2013 Mailgun
+ *
+ * This software may be modified and distributed under the terms
+ * of the MIT license. See the LICENSE file for details.
+ */
+
+namespace Mailgun\Api;
+
+use Mailgun\Assert;
+use Mailgun\Model\Domain\ConnectionResponse;
+use Mailgun\Model\Domain\CreateCredentialResponse;
+use Mailgun\Model\Domain\CreateResponse;
+use Mailgun\Model\Domain\CredentialResponse;
+use Mailgun\Model\Domain\DeleteCredentialResponse;
+use Mailgun\Model\Domain\DeleteResponse;
+use Mailgun\Model\Domain\IndexResponse;
+use Mailgun\Model\Domain\ShowResponse;
+use Mailgun\Model\Domain\TrackingResponse;
+use Mailgun\Model\Domain\UpdateClickTrackingResponse;
+use Mailgun\Model\Domain\UpdateConnectionResponse;
+use Mailgun\Model\Domain\UpdateCredentialResponse;
+use Mailgun\Model\Domain\UpdateOpenTrackingResponse;
+use Mailgun\Model\Domain\UpdateUnsubscribeTrackingResponse;
+use Mailgun\Model\Domain\UseAutomaticSenderSecurity;
+use Mailgun\Model\Domain\VerifyResponse;
+use Mailgun\Model\Domain\WebPrefixResponse;
+use Mailgun\Model\Domain\WebSchemeResponse;
+use Psr\Http\Client\ClientExceptionInterface;
+use Psr\Http\Message\ResponseInterface;
+
+/**
+ * @see https://documentation.mailgun.com/en/latest/api-domains.html
+ */
+class DomainV4 extends HttpApi
+{
+    private const DKIM_SIZES = ['1024', '2048'];
+
+    /**
+     * Returns a list of domains on the account.
+     * @param  int                      $limit
+     * @param  int                      $skip
+     * @param  array                    $requestHeaders
+     * @return IndexResponse|array
+     * @throws ClientExceptionInterface
+     */
+    public function index(int $limit = 100, int $skip = 0, array $requestHeaders = [])
+    {
+        Assert::range($limit, 1, 1000);
+
+        $params = [
+            'limit' => $limit,
+            'skip' => $skip,
+        ];
+
+        $response = $this->httpGet('/v4/domains', $params, $requestHeaders);
+
+        return $this->hydrateResponse($response, IndexResponse::class);
+    }
+
+    /**
+     * Returns a single domain.
+     * @param  string                               $domain         name of the domain
+     * @param  array                                $requestHeaders
+     * @return ShowResponse|array|ResponseInterface
+     * @throws ClientExceptionInterface
+     */
+    public function show(string $domain, array $requestHeaders = [])
+    {
+        Assert::stringNotEmpty($domain);
+
+        $response = $this->httpGet(sprintf('/v4/domains/%s', $domain), [], $requestHeaders);
+
+        return $this->hydrateResponse($response, ShowResponse::class);
+    }
+
+    /**
+     * Creates a new domain for the account.
+     * See below for spam filtering parameter information.
+     * {@link https://documentation.mailgun.com/en/latest/user_manual.html#um-spam-filter}.
+     * @see    https://documentation.mailgun.com/en/latest/api-domains.html#domains
+     * @param string $domain name of the domain
+     * @param string|null $smtpPass password for SMTP authentication
+     * @param string|null $spamAction `disable` or `tag` - inbound spam filtering
+     * @param bool|null $wildcard
+     * @param bool|null $forceDkimAuthority
+     * @param string[] $ips an array of ips to be assigned to the domain
+     * @param ?string $pool_id pool id to assign to the domain
+     * @param string $webScheme `http` or `https` - set your open, click and unsubscribe URLs to use http or https. The default is http
+     * @param string $dkimKeySize Set length of your domain’s generated DKIM key
+     * @param array $requestHeaders
+     * @param string|null $dkimHostName
+     * @param string|null $dkimSelector
+     * @param bool|null $useAutomaticSenderSecurity
+     * @return CreateResponse|array|ResponseInterface
+     * @throws ClientExceptionInterface
+     */
+    public function create(
+        string  $domain,
+        ?string  $smtpPass = null,
+        ?string  $spamAction = null,
+        ?bool $wildcard = null,
+        ?bool $forceDkimAuthority = null,
+        ?array  $ips = null,
+        ?string $pool_id = null,
+        string  $webScheme = 'http',
+        string  $dkimKeySize = '1024',
+        array   $requestHeaders = [],
+        ?string $dkimHostName = null,
+        ?string $dkimSelector = null,
+        ?bool $useAutomaticSenderSecurity = null
+    ) {
+        Assert::stringNotEmpty($domain);
+
+        $params = [];
+
+        $params['name'] = $domain;
+
+        if (!empty($smtpPass)) {
+            Assert::stringNotEmpty($smtpPass);
+
+            $params['smtp_password'] = $smtpPass;
+        }
+
+        if (!empty($spamAction)) {
+            Assert::stringNotEmpty($spamAction);
+
+            $params['spam_action'] = $spamAction;
+        }
+
+        if (null !== $wildcard) {
+            $params['wildcard'] = $wildcard ? 'true' : 'false';
+        }
+
+        if (null !== $forceDkimAuthority) {
+            $params['force_dkim_authority'] = $forceDkimAuthority ? 'true' : 'false';
+        }
+
+        if (null !== $ips) {
+            Assert::isList($ips);
+            Assert::allString($ips);
+
+            $params['ips'] = implode(',', $ips);
+        }
+
+        if (!empty($webScheme)) {
+            Assert::stringNotEmpty($webScheme);
+            Assert::oneOf($webScheme, ['https', 'http']);
+            $params['web_scheme'] = $webScheme;
+        }
+
+        if (null !== $pool_id) {
+            Assert::stringNotEmpty($pool_id);
+
+            $params['pool_id'] = $pool_id;
+        }
+        if (!empty($dkimKeySize)) {
+            Assert::oneOf(
+                $dkimKeySize,
+                self::DKIM_SIZES,
+                'Length of your domain’s generated DKIM key must be 1024 or 2048'
+            );
+            $params['dkim_key_size'] = $dkimKeySize;
+        }
+
+        if (!empty($dkimHostName)) {
+            Assert::stringNotEmpty($dkimHostName);
+            $params['dkim_host_name'] = $dkimHostName;
+        }
+
+        if (!empty($dkimSelector)) {
+            Assert::stringNotEmpty($dkimSelector);
+            $params['dkim_selector'] = $dkimSelector;
+        }
+
+        if (null !== $useAutomaticSenderSecurity) {
+            $params['use_automatic_sender_security'] = $useAutomaticSenderSecurity ? 'true' : 'false';
+        }
+
+        $response = $this->httpPost('/v4/domains', $params, $requestHeaders);
+
+        return $this->hydrateResponse($response, CreateResponse::class);
+    }
+
+    /**
+     * Removes a domain from the account.
+     * WARNING: This action is irreversible! Be cautious!
+     * @param string $domain name of the domain
+     * @param array $requestHeaders
+     * @return DeleteResponse|array|ResponseInterface
+     * @throws ClientExceptionInterface
+     */
+    public function delete(string $domain, array $requestHeaders = [])
+    {
+        Assert::stringNotEmpty($domain);
+
+        $response = $this->httpDelete(sprintf('/v4/domains/%s', $domain), [], $requestHeaders);
+
+        return $this->hydrateResponse($response, DeleteResponse::class);
+    }
+
+    /**
+     * Returns a list of SMTP credentials for the specified domain.
+     * @param  string                   $domain         name of the domain
+     * @param  int                      $limit          Number of credentials to return
+     * @param  int                      $skip           Number of credentials to omit from the list
+     * @param  array                    $requestHeaders
+     * @return CredentialResponse
+     * @throws ClientExceptionInterface
+     */
+    public function credentials(string $domain, int $limit = 100, int $skip = 0, array $requestHeaders = [])
+    {
+        Assert::stringNotEmpty($domain);
+        $params = [
+            'limit' => $limit,
+            'skip' => $skip,
+        ];
+
+        $response = $this->httpGet(sprintf('/v3/domains/%s/credentials', $domain), $params, $requestHeaders);
+
+        return $this->hydrateResponse($response, CredentialResponse::class);
+    }
+
+    /**
+     * Create a new SMTP credential pair for the specified domain.
+     * @param  string                                           $domain         name of the domain
+     * @param  string                                           $login          SMTP Username
+     * @param  string                                           $password       SMTP Password. Length min 5, max 32.
+     * @param  array                                            $requestHeaders
+     * @return CreateCredentialResponse|array|ResponseInterface
+     * @throws ClientExceptionInterface
+     */
+    public function createCredential(string $domain, string $login, string $password, array $requestHeaders = [])
+    {
+        Assert::stringNotEmpty($domain);
+        Assert::stringNotEmpty($login);
+        Assert::stringNotEmpty($password);
+        Assert::lengthBetween($password, 5, 32, 'SMTP password must be between 5 and 32 characters.');
+
+        $params = [
+            'login' => $login,
+            'password' => $password,
+        ];
+
+        $response = $this->httpPost(sprintf('/v3/domains/%s/credentials', $domain), $params, $requestHeaders);
+
+        return $this->hydrateResponse($response, CreateCredentialResponse::class);
+    }
+
+    /**
+     * Update a set of SMTP credentials for the specified domain.
+     *
+     * @param string $domain name of the domain
+     * @param string $login  SMTP Username
+     * @param string $pass   New SMTP Password. Length min 5, max 32.
+     *
+     * @return UpdateCredentialResponse|array|ResponseInterface
+     * @throws ClientExceptionInterface
+     */
+    public function updateCredential(string $domain, string $login, string $pass, array $requestHeaders = [])
+    {
+        Assert::stringNotEmpty($domain);
+        Assert::stringNotEmpty($login);
+        Assert::stringNotEmpty($pass);
+        Assert::lengthBetween($pass, 5, 32, 'SMTP password must be between 5 and 32 characters.');
+
+        $params = [
+            'password' => $pass,
+        ];
+
+        $response = $this->httpPut(sprintf('/v3/domains/%s/credentials/%s', $domain, $login), $params, $requestHeaders);
+
+        return $this->hydrateResponse($response, UpdateCredentialResponse::class);
+    }
+
+    /**
+     * Remove a set of SMTP credentials from the specified domain.
+     * @param  string                                           $domain         name of the domain
+     * @param  string                                           $login          SMTP Username
+     * @param  array                                            $requestHeaders
+     * @return DeleteCredentialResponse|array|ResponseInterface
+     * @throws ClientExceptionInterface
+     */
+    public function deleteCredential(string $domain, string $login, array $requestHeaders = [])
+    {
+        Assert::stringNotEmpty($domain);
+        Assert::stringNotEmpty($login);
+
+        $response = $this->httpDelete(
+            sprintf(
+                '/v3/domains/%s/credentials/%s',
+                $domain,
+                $login
+            ),
+            [],
+            $requestHeaders
+        );
+
+        return $this->hydrateResponse($response, DeleteCredentialResponse::class);
+    }
+
+    /**
+     * Returns delivery connection settings for the specified domain.
+     * @param  string                               $domain         name of the domain
+     * @param  array                                $requestHeaders
+     * @return ConnectionResponse|ResponseInterface
+     * @throws ClientExceptionInterface
+     */
+    public function connection(string $domain, array $requestHeaders = [])
+    {
+        Assert::stringNotEmpty($domain);
+
+        $response = $this->httpGet(sprintf('/v3/domains/%s/connection', $domain), [], $requestHeaders);
+
+        return $this->hydrateResponse($response, ConnectionResponse::class);
+    }
+
+    /**
+     * Updates the specified delivery connection settings for the specified domain.
+     * If a parameter is passed in as null, it will not be updated.
+     * @param  string                                           $domain         name of the domain
+     * @param  bool|null                                        $requireTLS     enforces that messages are sent only over a TLS connection
+     * @param  bool|null                                        $noVerify       disables TLS certificate and hostname verification
+     * @param  array                                            $requestHeaders
+     * @return UpdateConnectionResponse|array|ResponseInterface
+     * @throws ClientExceptionInterface
+     */
+    public function updateConnection(string $domain, ?bool $requireTLS, ?bool $noVerify, array $requestHeaders = [])
+    {
+        Assert::stringNotEmpty($domain);
+        $params = [];
+
+        if (null !== $requireTLS) {
+            $params['require_tls'] = $requireTLS ? 'true' : 'false';
+        }
+
+        if (null !== $noVerify) {
+            $params['skip_verification'] = $noVerify ? 'true' : 'false';
+        }
+
+        $response = $this->httpPut(sprintf('/v3/domains/%s/connection', $domain), $params, $requestHeaders);
+
+        return $this->hydrateResponse($response, UpdateConnectionResponse::class);
+    }
+
+    /**
+     * Update webScheme for existing domain
+     * See below for spam filtering parameter information.
+     * {@link https://documentation.mailgun.com/en/latest/user_manual.html#um-spam-filter}.
+     * @see https://documentation.mailgun.com/en/latest/api-domains.html#domains
+     * @param  string                                    $domain         name of the domain
+     * @param  string                                    $webScheme      `http` or `https` - set your open, click and unsubscribe URLs to use http or https. The default is http
+     * @param  array                                     $requestHeaders
+     * @return WebSchemeResponse|array|ResponseInterface
+     * @throws ClientExceptionInterface
+     */
+    public function updateWebScheme(string $domain, string $webScheme = 'http', array $requestHeaders = [])
+    {
+        $params = [];
+        Assert::stringNotEmpty($domain);
+        Assert::stringNotEmpty($webScheme);
+        Assert::oneOf($webScheme, ['https', 'http']);
+
+        $params['web_scheme'] = $webScheme;
+
+        $response = $this->httpPut(sprintf('/v4/domains/%s', $domain), $params, $requestHeaders);
+
+        return $this->hydrateResponse($response, WebSchemeResponse::class);
+    }
+
+    /**
+     * Update useAutomaticSenderSecurity for existing domain
+     * @see https://documentation.mailgun.com/docs/mailgun/api-reference/openapi-final/tag/Domains/#tag/Domains/operation/PUT-v4-domains--name-
+     * @param  string                                    $domain         name of the domain
+     * @param  bool                                      $useAutomaticSenderSecurity      If enabled, requires setting DNS CNAME entries for DKIM keys instead of a TXT record. Defaults to false.
+     * @param  array                                     $requestHeaders
+     * @return UseAutomaticSenderSecurity|array|ResponseInterface
+     * @throws ClientExceptionInterface
+     */
+    public function updateUseAutomaticSenderSecurity(string $domain, bool $useAutomaticSenderSecurity = false, array $requestHeaders = [])
+    {
+    $params = [];
+    Assert::stringNotEmpty($domain);
+
+    $params['use_automatic_sender_security'] = $useAutomaticSenderSecurity;
+
+    $response = $this->httpPut(sprintf('/v4/domains/%s', $domain), $params, $requestHeaders);
+
+    return $this->hydrateResponse($response, UseAutomaticSenderSecurity::class);
+    }
+
+    /**
+     * Returns a single domain.
+     * @param  string                                 $domain         name of the domain
+     * @param  array                                  $requestHeaders
+     * @return VerifyResponse|array|ResponseInterface
+     * @throws ClientExceptionInterface
+     */
+    public function verify(string $domain, array $requestHeaders = [])
+    {
+        Assert::stringNotEmpty($domain);
+
+        $response = $this->httpPut(sprintf('/v4/domains/%s/verify', $domain), [], $requestHeaders);
+
+        return $this->hydrateResponse($response, VerifyResponse::class);
+    }
+
+    /**
+     * Returns a domain tracking settings.
+     * @param  string                                   $domain         name of the domain
+     * @param  array                                    $requestHeaders
+     * @return TrackingResponse|array|ResponseInterface
+     * @throws ClientExceptionInterface
+     */
+    public function tracking(string $domain, array $requestHeaders = [])
+    {
+        Assert::stringNotEmpty($domain);
+
+        $response = $this->httpGet(sprintf('/v3/domains/%s/tracking', $domain), [], $requestHeaders);
+
+        return $this->hydrateResponse($response, TrackingResponse::class);
+    }
+
+    /**
+     * Updates a domain click tracking settings.
+     * @param  string                                              $domain         The name of the domain
+     * @param  string                                              $active         The status for this tracking (one of: yes, no)
+     * @param  array                                               $requestHeaders
+     * @return UpdateClickTrackingResponse|array|ResponseInterface
+     * @throws ClientExceptionInterface
+     */
+    public function updateClickTracking(string $domain, string $active, array $requestHeaders = [])
+    {
+        Assert::stringNotEmpty($domain);
+        Assert::stringNotEmpty($active);
+        Assert::oneOf($active, ['yes', 'no', 'htmlonly']);
+
+        $params = [
+            'active' => $active,
+        ];
+
+        $response = $this->httpPut(sprintf('/v3/domains/%s/tracking/click', $domain), $params, $requestHeaders);
+
+        return $this->hydrateResponse($response, UpdateClickTrackingResponse::class);
+    }
+
+    /**
+     * Updates a domain open tracking settings.
+     * @param  string                                             $domain         The name of the domain
+     * @param  string                                             $active         The status for this tracking (one of: yes, no)
+     * @param  array                                              $requestHeaders
+     * @return UpdateOpenTrackingResponse|array|ResponseInterface
+     * @throws ClientExceptionInterface
+     */
+    public function updateOpenTracking(string $domain, string $active, array $requestHeaders = [])
+    {
+        Assert::stringNotEmpty($domain);
+        Assert::stringNotEmpty($active);
+        Assert::oneOf($active, ['yes', 'no']);
+
+        $params = [
+            'active' => $active,
+        ];
+
+        $response = $this->httpPut(sprintf('/v3/domains/%s/tracking/open', $domain), $params, $requestHeaders);
+
+        return $this->hydrateResponse($response, UpdateOpenTrackingResponse::class);
+    }
+
+    /**
+     * Updates a domain unsubscribe tracking settings.
+     * @param  string                                                    $domain         The name of the domain
+     * @param  string                                                    $active         The status for this tracking (one of: yes, no)
+     * @param  string                                                    $htmlFooter     The footer for HTML emails
+     * @param  string                                                    $textFooter     The footer for plain text emails
+     * @param  array                                                     $requestHeaders
+     * @return UpdateUnsubscribeTrackingResponse|array|ResponseInterface
+     * @throws ClientExceptionInterface
+     */
+    public function updateUnsubscribeTracking(string $domain, string $active, string $htmlFooter, string $textFooter, array $requestHeaders = [])
+    {
+        Assert::stringNotEmpty($domain);
+        Assert::stringNotEmpty($active);
+        Assert::oneOf($active, ['yes', 'no', 'true', 'false']);
+        Assert::stringNotEmpty($htmlFooter);
+
+        $params = [
+            'active' => (in_array($active, ['yes', 'true'], true)) ? 'true' : 'false',
+            'html_footer' => $htmlFooter,
+            'text_footer' => $textFooter,
+        ];
+
+        $response = $this->httpPut(sprintf('/v3/domains/%s/tracking/unsubscribe', $domain), $params, $requestHeaders);
+
+        return $this->hydrateResponse($response, UpdateUnsubscribeTrackingResponse::class);
+    }
+
+    /**
+     * Updates a CNAME used for tracking opens and clicks.
+     *
+     * @param string $domain    The name of the domain
+     * @param string $webPrefix The tracking CNAME for a domain
+     *
+     * @return WebPrefixResponse|array|ResponseInterface
+     * @throws ClientExceptionInterface
+     */
+    public function updateWebPrefix(string $domain, string $webPrefix)
+    {
+        Assert::stringNotEmpty($domain);
+        Assert::stringNotEmpty($webPrefix);
+
+        $params = [
+            'web_prefix' => $webPrefix,
+        ];
+
+        $response = $this->httpPut(sprintf('/v3/domains/%s/web_prefix', $domain), $params);
+
+        return $this->hydrateResponse($response, WebPrefixResponse::class);
+    }
+}

+ 80 - 0
vendor/mailgun/mailgun-php/src/Api/EmailValidation.php

@@ -0,0 +1,80 @@
+<?php
+
+declare(strict_types=1);
+
+/*
+ * Copyright (C) 2013 Mailgun
+ *
+ * This software may be modified and distributed under the terms
+ * of the MIT license. See the LICENSE file for details.
+ */
+
+namespace Mailgun\Api;
+
+use Mailgun\Assert;
+use Mailgun\Model\EmailValidation\ParseResponse;
+use Mailgun\Model\EmailValidation\ValidateResponse;
+use Psr\Http\Client\ClientExceptionInterface;
+use Psr\Http\Message\ResponseInterface;
+
+/**
+ * @see https://documentation.mailgun.com/en/latest/api-email-validation.html
+ *
+ * @author David Garcia <me@davidgarcia.cat>
+ */
+class EmailValidation extends HttpApi
+{
+    /**
+     * Addresses are validated based off defined checks.
+     * This operation is only accessible with the private API key and not subject to the daily usage limits.
+     * @param  string                             $address             An email address to validate. Maximum: 512 characters.
+     * @param  bool                               $mailboxVerification If set to true, a mailbox verification check will be performed
+     *                                                                 against the address. The default is False.
+     * @param  array                              $requestHeaders
+     * @return ValidateResponse|ResponseInterface
+     * @throws ClientExceptionInterface           Thrown when we don't catch a Client or Server side Exception
+     */
+    public function validate(string $address, bool $mailboxVerification = false, array $requestHeaders = [])
+    {
+        Assert::stringNotEmpty($address);
+
+        $params = [
+            'address' => $address,
+            'mailbox_verification' => $mailboxVerification,
+        ];
+
+        $response = $this->httpGet('/v3/address/private/validate', $params, $requestHeaders);
+
+        return $this->hydrateResponse($response, ValidateResponse::class);
+    }
+
+    /**
+     * Parses a delimiter-separated list of email addresses into two lists: parsed addresses and unparsable portions.
+     * The parsed addresses are a list of addresses that are syntactically valid
+     * (and optionally pass DNS and ESP specific grammar checks).
+     * The unparsable list is a list of character sequences that could not be parsed
+     * (or optionally failed DNS or ESP specific grammar checks).
+     * Delimiter characters are comma (,) and semicolon (;).
+     * This operation is only accessible with the private API key and not subject to the daily usage limits.
+     * @param  string                          $addresses      A delimiter separated list of addresses. Maximum: 8000 characters.
+     * @param  bool                            $syntaxOnly     Perform only syntax checks or DNS and ESP specific validation as well.
+     *                                                         The default is True.
+     * @param  array                           $requestHeaders
+     * @return ParseResponse|ResponseInterface
+     * @throws ClientExceptionInterface        Thrown when we don't catch a Client or Server side Exception
+     */
+    public function parse(string $addresses, bool $syntaxOnly = true, array $requestHeaders = [])
+    {
+        Assert::stringNotEmpty($addresses);
+        Assert::maxLength($addresses, 8000);
+
+        $params = [
+            'addresses' => $addresses,
+            'syntax_only' => $syntaxOnly,
+        ];
+
+        $response = $this->httpGet('/v3/address/private/parse', $params, $requestHeaders);
+
+        return $this->hydrateResponse($response, ParseResponse::class);
+    }
+}

+ 284 - 0
vendor/mailgun/mailgun-php/src/Api/EmailValidationV4.php

@@ -0,0 +1,284 @@
+<?php
+
+declare(strict_types=1);
+
+/*
+ * Copyright (C) 2013 Mailgun
+ *
+ * This software may be modified and distributed under the terms
+ * of the MIT license. See the LICENSE file for details.
+ */
+
+namespace Mailgun\Api;
+
+use Exception;
+use Mailgun\Assert;
+use Mailgun\Model\EmailValidationV4\CreateBulkJobResponse;
+use Mailgun\Model\EmailValidationV4\CreateBulkPreviewResponse;
+use Mailgun\Model\EmailValidationV4\DeleteBulkJobResponse;
+use Mailgun\Model\EmailValidationV4\GetBulkJobResponse;
+use Mailgun\Model\EmailValidationV4\GetBulkJobsResponse;
+use Mailgun\Model\EmailValidationV4\GetBulkPreviewResponse;
+use Mailgun\Model\EmailValidationV4\GetBulkPreviewsResponse;
+use Mailgun\Model\EmailValidationV4\PromoteBulkPreviewResponse;
+use Mailgun\Model\EmailValidationV4\ValidateResponse;
+use Psr\Http\Client\ClientExceptionInterface;
+use Psr\Http\Message\ResponseInterface;
+use RuntimeException;
+
+/**
+ * @see https://documentation.mailgun.com/en/latest/api-email-validation.html
+ */
+class EmailValidationV4 extends HttpApi
+{
+    /**
+     * Addresses are validated based off defined checks.
+     * @param  string                             $address        An email address to validate. Maximum: 512 characters.
+     * @param  bool                               $providerLookup A provider lookup will be performed if Mailgun’s internal analysis is
+     *                                                            insufficient
+     * @param  array                              $requestHeaders
+     * @return ValidateResponse|ResponseInterface
+     * @throws ClientExceptionInterface           Thrown when we don't catch a Client or Server side Exception
+     */
+    public function validate(string $address, bool $providerLookup = true, array $requestHeaders = [])
+    {
+        Assert::stringNotEmpty($address);
+
+        $params = [
+            'address' => $address,
+            'provider_lookup' => $providerLookup,
+        ];
+
+        $response = $this->httpGet('/v4/address/validate', $params, $requestHeaders);
+
+        return $this->hydrateResponse($response, ValidateResponse::class);
+    }
+
+    /**
+     * @param  string                             $listId   ID given when the list created
+     * @param  mixed                              $filePath File path or file content
+     * @return mixed|ResponseInterface
+     * @throws Exception|ClientExceptionInterface
+     */
+    public function createBulkJob(string $listId, $filePath, array $requestHeaders = [])
+    {
+        Assert::stringNotEmpty($listId);
+
+        if (strlen($filePath) < PHP_MAXPATHLEN && is_file($filePath)) {
+            $fileData = ['filePath' => $filePath];
+        } else {
+            $fileData = [
+                'fileContent' => $filePath,
+                'filename' => 'file',
+            ];
+        }
+
+        $postDataMultipart = [];
+        $postDataMultipart[] = $this->prepareFile('file', $fileData);
+
+        try {
+            $response = $this->httpPostRaw(sprintf('/v4/address/validate/bulk/%s', $listId), $postDataMultipart, $requestHeaders);
+        } catch (Exception $exception) {
+            throw new RuntimeException($exception->getMessage());
+        } finally {
+            $this->closeResources($postDataMultipart);
+        }
+
+        return $this->hydrateResponse($response, CreateBulkJobResponse::class);
+    }
+
+    /**
+     * @param  string                                  $listId         ID given when the list created
+     * @param  array                                   $requestHeaders
+     * @return DeleteBulkJobResponse|ResponseInterface
+     * @throws ClientExceptionInterface
+     */
+    public function deleteBulkJob(string $listId, array $requestHeaders = [])
+    {
+        Assert::stringNotEmpty($listId);
+
+        $response = $this->httpDelete(sprintf('/v4/address/validate/bulk/%s', $listId), [], $requestHeaders);
+
+        return $this->hydrateResponse($response, DeleteBulkJobResponse::class);
+    }
+
+    /**
+     * @param  string                               $listId         ID given when the list created
+     * @param  array                                $requestHeaders
+     * @return GetBulkJobResponse|ResponseInterface
+     * @throws ClientExceptionInterface
+     */
+    public function getBulkJob(string $listId, array $requestHeaders = [])
+    {
+        Assert::stringNotEmpty($listId);
+
+        $response = $this->httpGet(sprintf('/v4/address/validate/bulk/%s', $listId), [], $requestHeaders);
+
+        return $this->hydrateResponse($response, GetBulkJobResponse::class);
+    }
+
+    /**
+     * @param  int                                   $limit          Jobs limit
+     * @param  array                                 $requestHeaders
+     * @return GetBulkJobsResponse|ResponseInterface
+     * @throws ClientExceptionInterface
+     */
+    public function getBulkJobs(int $limit = 500, array $requestHeaders = [])
+    {
+        Assert::greaterThan($limit, 0);
+
+        $response = $this->httpGet(
+            '/v4/address/validate/bulk',
+            [
+                'limit' => $limit,
+            ],
+            $requestHeaders
+        );
+
+        return $this->hydrateResponse($response, GetBulkJobsResponse::class);
+    }
+
+    /**
+     * @param  int                      $limit          Previews Limit
+     * @param  array                    $requestHeaders
+     * @return mixed|ResponseInterface
+     * @throws ClientExceptionInterface
+     */
+    public function getBulkPreviews(int $limit = 500, array $requestHeaders = [])
+    {
+        Assert::greaterThan($limit, 0);
+
+        $response = $this->httpGet(
+            '/v4/address/validate/preview',
+            [
+                'limit' => $limit,
+            ],
+            $requestHeaders
+        );
+
+        return $this->hydrateResponse($response, GetBulkPreviewsResponse::class);
+    }
+
+    /**
+     * @param  string                   $previewId      ID given when the list created
+     * @param  mixed                    $filePath       File path or file content
+     * @param  array                    $requestHeaders
+     * @return mixed|ResponseInterface
+     * @throws ClientExceptionInterface
+     */
+    public function createBulkPreview(string $previewId, $filePath, array $requestHeaders = [])
+    {
+        Assert::stringNotEmpty($previewId);
+
+        if (strlen($filePath) < PHP_MAXPATHLEN && is_file($filePath)) {
+            $fileData = ['filePath' => $filePath];
+        } else {
+            $fileData = [
+                'fileContent' => $filePath,
+                'filename' => 'file',
+            ];
+        }
+
+        $postDataMultipart = [];
+        $postDataMultipart[] = $this->prepareFile('file', $fileData);
+
+        try {
+            $response = $this->httpPostRaw(sprintf('/v4/address/validate/preview/%s', $previewId), $postDataMultipart, $requestHeaders);
+        } catch (Exception $exception) {
+            throw new RuntimeException($exception->getMessage());
+        } finally {
+            $this->closeResources($postDataMultipart);
+        }
+
+        return $this->hydrateResponse($response, CreateBulkPreviewResponse::class);
+    }
+
+    /**
+     * @param  string                   $previewId      ID given when the list created
+     * @param  array                    $requestHeaders
+     * @return mixed|ResponseInterface
+     * @throws ClientExceptionInterface
+     */
+    public function getBulkPreview(string $previewId, array $requestHeaders = [])
+    {
+        Assert::stringNotEmpty($previewId);
+
+        $response = $this->httpGet(sprintf('/v4/address/validate/preview/%s', $previewId), [], $requestHeaders);
+
+        return $this->hydrateResponse($response, GetBulkPreviewResponse::class);
+    }
+
+    /**
+     * @param  string                   $previewId      ID given when the list created
+     * @param  array                    $requestHeaders
+     * @return bool
+     * @throws ClientExceptionInterface
+     */
+    public function deleteBulkPreview(string $previewId, array $requestHeaders = []): bool
+    {
+        Assert::stringNotEmpty($previewId);
+
+        $response = $this->httpDelete(sprintf('/v4/address/validate/preview/%s', $previewId), [], $requestHeaders);
+
+        return 204 === $response->getStatusCode();
+    }
+
+    /**
+     * @param  string                   $previewId      ID given when the list created
+     * @param  array                    $requestHeaders
+     * @return mixed|ResponseInterface
+     * @throws ClientExceptionInterface
+     */
+    public function promoteBulkPreview(string $previewId, array $requestHeaders = [])
+    {
+        Assert::stringNotEmpty($previewId);
+
+        $response = $this->httpPut(sprintf('/v4/address/validate/preview/%s', $previewId), [], $requestHeaders);
+
+        return $this->hydrateResponse($response, PromoteBulkPreviewResponse::class);
+    }
+
+    /**
+     * @param string $fieldName Field Name
+     * @param array  $filePath  ['fileContent' => 'content'] or ['filePath' => '/foo/bar']
+     *
+     * @return array File Data
+     */
+    private function prepareFile(string $fieldName, array $filePath): array
+    {
+        $filename = $filePath['filename'] ?? null;
+
+        $resource = null;
+
+        if (isset($filePath['fileContent'])) {
+            // File from memory
+            $resource = fopen('php://temp', 'rb+');
+            fwrite($resource, $filePath['fileContent']);
+            rewind($resource);
+        } elseif (isset($filePath['filePath'])) {
+            // File form path
+            $path = $filePath['filePath'];
+            $resource = fopen($path, 'rb');
+        }
+
+        return [
+            'name' => $fieldName,
+            'content' => $resource,
+            'filename' => $filename,
+        ];
+    }
+
+    /**
+     * Close open resources.
+     *
+     * @param array $params Resource params
+     */
+    private function closeResources(array $params): void
+    {
+        foreach ($params as $param) {
+            if (is_array($param) && array_key_exists('content', $param) && is_resource($param['content'])) {
+                fclose($param['content']);
+            }
+        }
+    }
+}

+ 46 - 0
vendor/mailgun/mailgun-php/src/Api/Event.php

@@ -0,0 +1,46 @@
+<?php
+
+declare(strict_types=1);
+
+/*
+ * Copyright (C) 2013 Mailgun
+ *
+ * This software may be modified and distributed under the terms
+ * of the MIT license. See the LICENSE file for details.
+ */
+
+namespace Mailgun\Api;
+
+use Mailgun\Assert;
+use Mailgun\Model\Event\EventResponse;
+use Psr\Http\Client\ClientExceptionInterface;
+
+/**
+ * @see https://documentation.mailgun.com/en/latest/api-events.html
+ *
+ * @author Tobias Nyholm <tobias.nyholm@gmail.com>
+ */
+class Event extends HttpApi
+{
+    use Pagination;
+
+    /**
+     * @param  string                   $domain
+     * @param  array                    $params
+     * @param  array                    $requestHeaders
+     * @return EventResponse
+     * @throws ClientExceptionInterface
+     */
+    public function get(string $domain, array $params = [], array $requestHeaders = [])
+    {
+        Assert::stringNotEmpty($domain);
+
+        if (array_key_exists('limit', $params)) {
+            Assert::range($params['limit'], 1, 300);
+        }
+
+        $response = $this->httpGet(sprintf('/v3/%s/events', $domain), $params, $requestHeaders);
+
+        return $this->hydrateResponse($response, EventResponse::class);
+    }
+}

+ 241 - 0
vendor/mailgun/mailgun-php/src/Api/HttpApi.php

@@ -0,0 +1,241 @@
+<?php
+
+declare(strict_types=1);
+
+/*
+ * Copyright (C) 2013 Mailgun
+ *
+ * This software may be modified and distributed under the terms
+ * of the MIT license. See the LICENSE file for details.
+ */
+
+namespace Mailgun\Api;
+
+use Exception;
+use Mailgun\Exception\HttpClientException;
+use Mailgun\Exception\HttpServerException;
+use Mailgun\Exception\UnknownErrorException;
+use Mailgun\HttpClient\RequestBuilder;
+use Mailgun\Hydrator\Hydrator;
+use Mailgun\Hydrator\NoopHydrator;
+use Psr\Http\Client as Psr18;
+use Psr\Http\Client\ClientExceptionInterface;
+use Psr\Http\Client\ClientInterface;
+use Psr\Http\Message\ResponseInterface;
+
+/**
+ * @author Tobias Nyholm <tobias.nyholm@gmail.com>
+ */
+abstract class HttpApi
+{
+    /**
+     * The HTTP client.
+     *
+     * @var ClientInterface
+     */
+    protected ClientInterface $httpClient;
+
+    /**
+     * @var Hydrator|null
+     */
+    protected ?Hydrator $hydrator;
+
+    /**
+     * @var RequestBuilder
+     */
+    protected RequestBuilder $requestBuilder;
+
+    /**
+     * @param ClientInterface $httpClient
+     * @param RequestBuilder  $requestBuilder
+     * @param Hydrator        $hydrator
+     */
+    public function __construct(ClientInterface $httpClient, RequestBuilder $requestBuilder, Hydrator $hydrator)
+    {
+        $this->httpClient = $httpClient;
+        $this->requestBuilder = $requestBuilder;
+        if (!$hydrator instanceof NoopHydrator) {
+            $this->hydrator = $hydrator;
+        }
+    }
+
+    /**
+     * @param class-string $class
+     *
+     * @return mixed|ResponseInterface
+     *
+     * @throws Exception
+     */
+    protected function hydrateResponse(ResponseInterface $response, string $class)
+    {
+        if (null === $this->hydrator) {
+            return $response;
+        }
+
+        if (!in_array($response->getStatusCode(), [200, 201, 202], true)) {
+            $this->handleErrors($response);
+        }
+
+        return $this->hydrator->hydrate($response, $class);
+    }
+
+    /**
+     * Throw the correct exception for this error.
+     *
+     * @throws Exception|UnknownErrorException
+     */
+    protected function handleErrors(ResponseInterface $response): void
+    {
+        $statusCode = $response->getStatusCode();
+        switch ($statusCode) {
+            case 400:
+                throw HttpClientException::badRequest($response);
+            case 401:
+                throw HttpClientException::unauthorized($response);
+            case 402:
+                throw HttpClientException::requestFailed($response);
+            case 403:
+                throw HttpClientException::forbidden($response);
+            case 404:
+                throw HttpClientException::notFound($response);
+            case 409:
+                throw HttpClientException::conflict($response);
+            case 413:
+                throw HttpClientException::payloadTooLarge($response);
+            case 429:
+                throw HttpClientException::tooManyRequests($response);
+            case 500 <= $statusCode:
+                throw HttpServerException::serverError($statusCode);
+            default:
+                throw new UnknownErrorException();
+        }
+    }
+
+    /**
+     * Send a GET request with query parameters.
+     *
+     * @param  string                   $path           Request path
+     * @param  array                    $parameters     GET parameters
+     * @param  array                    $requestHeaders Request Headers
+     * @throws ClientExceptionInterface|\JsonException
+     */
+    protected function httpGet(string $path, array $parameters = [], array $requestHeaders = []): ResponseInterface
+    {
+        if (count($parameters) > 0) {
+            $path .= '?'.http_build_query($parameters);
+        }
+
+        try {
+            $response = $this->httpClient->sendRequest(
+                $this->requestBuilder->create('GET', $path, $requestHeaders)
+            );
+        } catch (Psr18\NetworkExceptionInterface $e) {
+            throw HttpServerException::networkError($e);
+        }
+
+        return $response;
+    }
+
+    /**
+     * Send a POST request with parameters.
+     *
+     * @param  string                   $path           Request path
+     * @param  array                    $parameters     POST parameters
+     * @param  array                    $requestHeaders Request headers
+     * @throws ClientExceptionInterface|\JsonException
+     */
+    protected function httpPost(string $path, array $parameters = [], array $requestHeaders = []): ResponseInterface
+    {
+        if (isset($requestHeaders['Content-Type']) && $requestHeaders['Content-Type'] === 'application/json') {
+            return $this->httpPostRaw($path, $parameters, $requestHeaders);
+        }
+
+        return $this->httpPostRaw($path, $this->createRequestBody($parameters), $requestHeaders);
+    }
+
+    /**
+     * Send a POST request with raw data.
+     *
+     * @param  string                   $path           Request path
+     * @param  array|string             $body           Request body
+     * @param  array                    $requestHeaders Request headers
+     * @throws ClientExceptionInterface|\JsonException
+     */
+    protected function httpPostRaw(string $path, $body, array $requestHeaders = []): ResponseInterface
+    {
+        try {
+            $response = $this->httpClient->sendRequest(
+                $this->requestBuilder->create('POST', $path, $requestHeaders, $body)
+            );
+        } catch (Psr18\NetworkExceptionInterface $e) {
+            throw HttpServerException::networkError($e);
+        }
+
+        return $response;
+    }
+
+    /**
+     * Send a PUT request.
+     *
+     * @param  string                   $path           Request path
+     * @param  array                    $parameters     PUT parameters
+     * @param  array                    $requestHeaders Request headers
+     * @throws ClientExceptionInterface|\JsonException
+     */
+    protected function httpPut(string $path, array $parameters = [], array $requestHeaders = []): ResponseInterface
+    {
+        try {
+            $response = $this->httpClient->sendRequest(
+                $this->requestBuilder->create('PUT', $path, $requestHeaders, $this->createRequestBody($parameters))
+            );
+        } catch (Psr18\NetworkExceptionInterface $e) {
+            throw HttpServerException::networkError($e);
+        }
+
+        return $response;
+    }
+
+    /**
+     * Send a DELETE request.
+     *
+     * @param  string                   $path           Request path
+     * @param  array                    $parameters     DELETE parameters
+     * @param  array                    $requestHeaders Request headers
+     * @throws ClientExceptionInterface
+     */
+    protected function httpDelete(string $path, array $parameters = [], array $requestHeaders = []): ResponseInterface
+    {
+        try {
+            $response = $this->httpClient->sendRequest(
+                $this->requestBuilder->create('DELETE', $path, $requestHeaders, $this->createRequestBody($parameters))
+            );
+        } catch (Psr18\NetworkExceptionInterface $e) {
+            throw HttpServerException::networkError($e);
+        }
+
+        return $response;
+    }
+
+    /**
+     * Prepare a set of key-value-pairs to be encoded as multipart/form-data.
+     *
+     * @param array $parameters Request parameters
+     */
+    private function createRequestBody(array $parameters): array
+    {
+        $resources = [];
+        foreach ($parameters as $key => $values) {
+            if (!is_array($values)) {
+                $values = [$values];
+            }
+            foreach ($values as $value) {
+                $resources[] = [
+                    'name' => $key,
+                    'content' => $value,
+                ];
+            }
+        }
+
+        return $resources;
+    }
+}

+ 102 - 0
vendor/mailgun/mailgun-php/src/Api/HttpClient.php

@@ -0,0 +1,102 @@
+<?php
+
+declare(strict_types=1);
+
+/*
+ * Copyright (C) 2013 Mailgun
+ *
+ * This software may be modified and distributed under the terms
+ * of the MIT license. See the LICENSE file for details.
+ */
+
+namespace Mailgun\Api;
+
+use Http\Client\Common\PluginClient;
+use Mailgun\HttpClient\RequestBuilder;
+use Psr\Http\Client\ClientExceptionInterface;
+use Psr\Http\Client\ClientInterface;
+use Psr\Http\Message\ResponseInterface;
+
+/**
+ * @see https://documentation.mailgun.com/en/latest/api-domains.html
+ *
+ * @author Sean Johnson <sean@mailgun.com>
+ */
+class HttpClient extends HttpApi
+{
+    /**
+     * @return PluginClient|ClientInterface
+     */
+    public function getHttpClient()
+    {
+        return $this->httpClient;
+    }
+
+    /**
+     * @return RequestBuilder
+     */
+    public function getRequestBuilder(): RequestBuilder
+    {
+        return $this->requestBuilder;
+    }
+
+    /**
+     * @param  string                   $path
+     * @param  array                    $parameters
+     * @param  array                    $requestHeaders
+     * @return ResponseInterface
+     * @throws ClientExceptionInterface
+     */
+    public function httpDelete(string $path, array $parameters = [], array $requestHeaders = []): ResponseInterface
+    {
+        return parent::httpDelete($path, $parameters, $requestHeaders);
+    }
+
+    /**
+     * @param  string                   $path
+     * @param  array                    $parameters
+     * @param  array                    $requestHeaders
+     * @return ResponseInterface
+     * @throws ClientExceptionInterface
+     */
+    public function httpGet(string $path, array $parameters = [], array $requestHeaders = []): ResponseInterface
+    {
+        return parent::httpGet($path, $parameters, $requestHeaders);
+    }
+
+    /**
+     * @param  string                   $path
+     * @param  array                    $parameters
+     * @param  array                    $requestHeaders
+     * @return ResponseInterface
+     * @throws ClientExceptionInterface
+     */
+    public function httpPost(string $path, array $parameters = [], array $requestHeaders = []): ResponseInterface
+    {
+        return parent::httpPost($path, $parameters, $requestHeaders);
+    }
+
+    /**
+     * @param  string                   $path
+     * @param  array                    $parameters
+     * @param  array                    $requestHeaders
+     * @return ResponseInterface
+     * @throws ClientExceptionInterface
+     */
+    public function httpPut(string $path, array $parameters = [], array $requestHeaders = []): ResponseInterface
+    {
+        return parent::httpPut($path, $parameters, $requestHeaders);
+    }
+
+    /**
+     * @param  string                   $path
+     * @param  array|string             $body
+     * @param  array                    $requestHeaders
+     * @return ResponseInterface
+     * @throws ClientExceptionInterface
+     */
+    public function httpPostRaw(string $path, $body, array $requestHeaders = []): ResponseInterface
+    {
+        return parent::httpPostRaw($path, $body, $requestHeaders);
+    }
+}

+ 271 - 0
vendor/mailgun/mailgun-php/src/Api/Ip.php

@@ -0,0 +1,271 @@
+<?php
+
+declare(strict_types=1);
+
+/*
+ * Copyright (C) 2013 Mailgun
+ *
+ * This software may be modified and distributed under the terms
+ * of the MIT license. See the LICENSE file for details.
+ */
+
+namespace Mailgun\Api;
+
+use Exception;
+use Mailgun\Assert;
+use Mailgun\Model\Ip\AvailableIpsResponse;
+use Mailgun\Model\Ip\IndexResponse;
+use Mailgun\Model\Ip\ShowResponse;
+use Mailgun\Model\Ip\UpdateResponse;
+use Psr\Http\Client\ClientExceptionInterface;
+use Psr\Http\Message\ResponseInterface;
+
+/**
+ * @see https://documentation.mailgun.com/en/latest/api-ips.html
+ *
+ * @author Tobias Nyholm <tobias.nyholm@gmail.com>
+ */
+class Ip extends HttpApi
+{
+    /**
+     * Returns a list of IPs.
+     * @param bool|null $dedicated
+     * @param array $requestHeaders
+     * @return IndexResponse|ResponseInterface
+     * @throws ClientExceptionInterface
+     * @throws Exception
+     */
+    public function index(?bool $dedicated = null, array $requestHeaders = [])
+    {
+        $params = [];
+        if (null !== $dedicated) {
+            Assert::boolean($dedicated);
+            $params['dedicated'] = $dedicated;
+        }
+
+        $response = $this->httpGet('/v3/ips', $params, $requestHeaders);
+
+        return $this->hydrateResponse($response, IndexResponse::class);
+    }
+
+    /**
+     * Returns a list of IPs assigned to a domain.
+     * @param string $domain
+     * @param array $requestHeaders
+     * @return IndexResponse|ResponseInterface
+     * @throws ClientExceptionInterface
+     * @throws Exception
+     */
+    public function domainIndex(string $domain, array $requestHeaders = [])
+    {
+        Assert::stringNotEmpty($domain);
+
+        $response = $this->httpGet(sprintf('/v3/domains/%s/ips', $domain), [], $requestHeaders);
+
+        return $this->hydrateResponse($response, IndexResponse::class);
+    }
+
+    /**
+     * Returns a single ip.
+     * @param string $ip
+     * @param array $requestHeaders
+     * @return ShowResponse|ResponseInterface
+     * @throws ClientExceptionInterface
+     * @throws Exception
+     */
+    public function show(string $ip, array $requestHeaders = [])
+    {
+        Assert::ip($ip);
+
+        $response = $this->httpGet(sprintf('/v3/ips/%s', $ip), [], $requestHeaders);
+
+        return $this->hydrateResponse($response, ShowResponse::class);
+    }
+
+    /**
+     * Assign a dedicated IP to the domain specified.
+     * @param string $domain
+     * @param string $ip
+     * @param array $requestHeaders
+     * @return UpdateResponse|ResponseInterface
+     * @throws ClientExceptionInterface
+     * @throws Exception
+     */
+    public function assign(string $domain, string $ip, array $requestHeaders = [])
+    {
+        Assert::stringNotEmpty($domain);
+        Assert::ip($ip);
+
+        $params = [
+            'ip' => $ip,
+        ];
+
+        $response = $this->httpPost(sprintf('/v3/domains/%s/ips', $domain), $params, $requestHeaders);
+
+        return $this->hydrateResponse($response, UpdateResponse::class);
+    }
+
+    /**
+     * Unassign an IP from the domain specified.
+     * @param string $domain
+     * @param string $ip
+     * @param array $requestHeaders
+     * @return UpdateResponse|ResponseInterface
+     * @throws ClientExceptionInterface
+     * @throws Exception
+     */
+    public function unassign(string $domain, string $ip, array $requestHeaders = [])
+    {
+        Assert::stringNotEmpty($domain);
+        Assert::ip($ip);
+
+        $response = $this->httpDelete(sprintf('/v3/domains/%s/ips/%s', $domain, $ip), [], $requestHeaders);
+
+        return $this->hydrateResponse($response, UpdateResponse::class);
+    }
+
+    /**
+     * Get all domains of an account where a specific IP is assigned
+     * @param string $ip
+     * @param int $limit
+     * @param int $skip
+     * @param string|null $search
+     * @param array $requestHeaders
+     * @return IndexResponse|ResponseInterface
+     * @throws ClientExceptionInterface
+     * @throws Exception
+     */
+    public function domainsByIp(string $ip, int $limit = 10, int $skip = 0, ?string $search = null, array $requestHeaders = [])
+    {
+        Assert::stringNotEmpty($ip);
+
+        $params = [
+            'limit' => $limit,
+            'skip' => $skip,
+        ];
+        if (null !== $search) {
+            $params['search'] = $search;
+        }
+
+        $response = $this->httpGet(sprintf('/v3/ips/%s/domains', $ip), $params, $requestHeaders);
+
+        return $this->hydrateResponse($response, IndexResponse::class);
+    }
+
+    /**
+     * Place account IP into a dedicated IP band
+     * @param string $ip
+     * @param array $requestHeaders
+     * @return UpdateResponse|ResponseInterface
+     * @throws ClientExceptionInterface
+     * @throws Exception
+     */
+    public function placeAccountIpToBand(string $ip, array $requestHeaders = [])
+    {
+        Assert::stringNotEmpty($ip);
+        Assert::ip($ip);
+
+        $payload = [
+            'ip_band' => $ip,
+        ];
+
+        $response = $this->httpPost(sprintf('/v3/ips/%s/ip_band', $ip), $payload, $requestHeaders);
+
+        return $this->hydrateResponse($response, UpdateResponse::class);
+    }
+
+    /**
+     * Return the number of IPs available to the account per its billing plan
+     * @param array $requestHeaders
+     * @return AvailableIpsResponse|ResponseInterface
+     * @throws ClientExceptionInterface
+     * @throws Exception
+     */
+    public function numberOfIps(array $requestHeaders = [])
+    {
+        $response = $this->httpGet('v3/ips/request/new', [], $requestHeaders);
+
+        return $this->hydrateResponse($response, AvailableIpsResponse::class);
+    }
+
+    /**
+     * Add a new dedicated IP to the account
+     * @param array $requestHeaders
+     * @return UpdateResponse|ResponseInterface
+     * @throws ClientExceptionInterface
+     * @throws Exception
+     */
+    public function addDedicatedIp(array $requestHeaders = [])
+    {
+        $response = $this->httpPost('v3/ips/request/new', [], $requestHeaders);
+
+        return $this->hydrateResponse($response, UpdateResponse::class);
+    }
+
+    /**
+     * Remove an IP from the domain pool, unlink a DIPP or remove the domain pool
+     * @param string $domain
+     * @param string $ip
+     * @param array $requestHeaders
+     * @return UpdateResponse|ResponseInterface
+     * @throws ClientExceptionInterface
+     * @throws Exception
+     */
+    public function removeIpOrUnlink(string $domain, string $ip, array $requestHeaders = [])
+    {
+        Assert::stringNotEmpty($domain);
+        Assert::ip($ip);
+
+        $response = $this->httpDelete(sprintf('/v3/domains/%s/pool/%s', $domain, $ip), [], $requestHeaders);
+
+        return $this->hydrateResponse($response, UpdateResponse::class);
+    }
+
+    /**
+     * @param array $data
+     * @param array $requestHeaders
+     * @return mixed|ResponseInterface
+     * @throws ClientExceptionInterface
+     */
+    public function addNewDIPPIntoAccount(array $data, array $requestHeaders = [])
+    {
+        Assert::stringNotEmpty($data['description']);
+        Assert::stringNotEmpty($data['name']);
+
+        $response = $this->httpPost('/v3/ip_pools', $data, $requestHeaders);
+
+        return $this->hydrateResponse($response, UpdateResponse::class);
+    }
+
+    /**
+     * @param string $poolId
+     * @param array $requestHeaders
+     * @return mixed|ResponseInterface
+     * @throws ClientExceptionInterface
+     */
+    public function loadDIPPInformation(string $poolId, array $requestHeaders = [])
+    {
+        $response = $this->httpGet(sprintf('/v3/ip_pools/%s', $poolId), [], $requestHeaders);
+
+        return $this->hydrateResponse($response, UpdateResponse::class);
+    }
+
+    /**
+     * @param string $poolId
+     * @param string $ip
+     * @param string $repPoolId
+     * @param array $requestHeaders
+     * @return mixed|ResponseInterface
+     * @throws ClientExceptionInterface
+     */
+    public function deleteDIPP(string $poolId, string $ip, string $repPoolId, array $requestHeaders = [])
+    {
+        $response = $this->httpDelete(
+            sprintf('/v3/ip_pools/%s?', $poolId,) . http_build_query(['ip' => $ip, 'pool_id' => $repPoolId]),
+            [],
+            $requestHeaders
+        );
+
+        return $this->hydrateResponse($response, UpdateResponse::class);
+    }
+}

+ 94 - 0
vendor/mailgun/mailgun-php/src/Api/Mailboxes.php

@@ -0,0 +1,94 @@
+<?php
+
+declare(strict_types=1);
+
+/*
+ * Copyright (C) 2013 Mailgun
+ *
+ * This software may be modified and distributed under the terms
+ * of the MIT license. See the LICENSE file for details.
+ */
+
+namespace Mailgun\Api;
+
+use Mailgun\Assert;
+use Mailgun\Model\Mailboxes\CreateResponse;
+use Mailgun\Model\Mailboxes\DeleteResponse;
+use Mailgun\Model\Mailboxes\ShowResponse;
+use Mailgun\Model\Mailboxes\UpdateResponse;
+use Psr\Http\Client\ClientExceptionInterface;
+
+class Mailboxes extends HttpApi
+{
+    private const MIN_PASSWORD_LENGTH = 5;
+
+    /**
+     * @param  string                   $domain
+     * @param  array                    $parameters
+     * @param  array                    $requestHeaders
+     * @return CreateResponse
+     * @throws ClientExceptionInterface
+     */
+    public function create(string $domain, array $parameters = [], array $requestHeaders = [])
+    {
+        Assert::stringNotEmpty($domain);
+        Assert::keyExists($parameters, 'mailbox');
+        Assert::keyExists($parameters, 'password');
+        Assert::minLength($parameters['password'], self::MIN_PASSWORD_LENGTH);
+
+        $response = $this->httpPost(sprintf('/v3/%s/mailboxes', $domain), $parameters, $requestHeaders);
+
+        return $this->hydrateResponse($response, CreateResponse::class);
+    }
+
+    /**
+     * @param  string                   $domain
+     * @param  array                    $parameters
+     * @param  array                    $requestHeaders
+     * @return ShowResponse
+     * @throws ClientExceptionInterface
+     */
+    public function show(string $domain, array $parameters = [], array $requestHeaders = [])
+    {
+        Assert::stringNotEmpty($domain);
+
+        $response = $this->httpGet(sprintf('/v3/%s/mailboxes', $domain), $parameters, $requestHeaders);
+
+        return $this->hydrateResponse($response, ShowResponse::class);
+    }
+
+    /**
+     * @param  string                   $domain
+     * @param  string                   $mailbox
+     * @param  array                    $parameters
+     * @param  array                    $requestHeaders
+     * @return UpdateResponse
+     * @throws ClientExceptionInterface
+     */
+    public function update(string $domain, string $mailbox, array $parameters = [], array $requestHeaders = [])
+    {
+        Assert::stringNotEmpty($domain);
+        Assert::stringNotEmpty($mailbox);
+
+        $response = $this->httpPut(sprintf('/v3/%s/mailboxes/%s', $domain, $mailbox), $parameters, $requestHeaders);
+
+        return $this->hydrateResponse($response, UpdateResponse::class);
+    }
+
+    /**
+     * @param  string                   $domain
+     * @param  string                   $mailbox
+     * @param  array                    $requestHeaders
+     * @return DeleteResponse
+     * @throws ClientExceptionInterface
+     */
+    public function delete(string $domain, string $mailbox, array $requestHeaders = [])
+    {
+        Assert::stringNotEmpty($domain);
+        Assert::stringNotEmpty($mailbox);
+
+        $response = $this->httpDelete(sprintf('/v3/%s/mailboxes/%s', $domain, $mailbox), [], $requestHeaders);
+
+        return $this->hydrateResponse($response, DeleteResponse::class);
+    }
+}

+ 266 - 0
vendor/mailgun/mailgun-php/src/Api/MailingList.php

@@ -0,0 +1,266 @@
+<?php
+
+declare(strict_types=1);
+
+/*
+ * Copyright (C) 2013 Mailgun
+ *
+ * This software may be modified and distributed under the terms
+ * of the MIT license. See the LICENSE file for details.
+ */
+
+namespace Mailgun\Api;
+
+use Exception;
+use Mailgun\Api\MailingList\Member;
+use Mailgun\Assert;
+use Mailgun\Model\EmailValidation\ValidateResponse;
+use Mailgun\Model\MailingList\BulkResponse;
+use Mailgun\Model\MailingList\CreateResponse;
+use Mailgun\Model\MailingList\DeleteResponse;
+use Mailgun\Model\MailingList\PagesResponse;
+use Mailgun\Model\MailingList\ShowResponse;
+use Mailgun\Model\MailingList\UpdateResponse;
+use Mailgun\Model\MailingList\ValidationCancelResponse;
+use Mailgun\Model\MailingList\ValidationStatusResponse;
+use Psr\Http\Client\ClientExceptionInterface;
+
+/**
+ * @see https://documentation.mailgun.com/en/latest/api-mailinglists.html
+ */
+class MailingList extends HttpApi
+{
+    /**
+     * @return Member
+     */
+    public function member(): Member
+    {
+        return new Member($this->httpClient, $this->requestBuilder, $this->hydrator);
+    }
+
+    /**
+     * Returns a paginated list of mailing lists on the domain.
+     * @param  int                      $limit          Maximum number of records to return (optional: 100 by default)
+     * @param  array                    $requestHeaders
+     * @return PagesResponse
+     * @throws ClientExceptionInterface
+     */
+    public function pages(int $limit = 100, array $requestHeaders = [])
+    {
+        Assert::range($limit, 1, 1000);
+
+        $params = [
+            'limit' => $limit,
+        ];
+
+        $response = $this->httpGet('/v3/lists/pages', $params, $requestHeaders);
+
+        return $this->hydrateResponse($response, PagesResponse::class);
+    }
+
+    /**
+     * Creates a new mailing list on the current domain.
+     * @param  string                   $address         Address for the new mailing list
+     * @param  string|null              $name            Name for the new mailing list (optional)
+     * @param  string|null              $description     Description for the new mailing list (optional)
+     * @param  string                   $accessLevel     List access level, one of: readonly (default), members, everyone
+     * @param  string                   $replyPreference Set where replies should go: list (default) | sender (optional)
+     * @param  array                    $requestHeaders
+     * @return CreateResponse
+     * @throws ClientExceptionInterface
+     */
+    public function create(
+        string $address,
+        ?string $name = null,
+        ?string $description = null,
+        string $accessLevel = 'readonly',
+        string $replyPreference = 'list',
+        array $requestHeaders = []
+    ) {
+        Assert::stringNotEmpty($address);
+        Assert::nullOrStringNotEmpty($name);
+        Assert::nullOrStringNotEmpty($description);
+        Assert::oneOf($accessLevel, ['readonly', 'members', 'everyone']);
+        Assert::oneOf($replyPreference, ['list', 'sender']);
+
+        $params = [
+            'address' => $address,
+            'access_level' => $accessLevel,
+            'reply_preference' => $replyPreference,
+        ];
+        $description ? $params['description'] = $description : false;
+        $name ? $params['name'] = $name : false;
+
+        $response = $this->httpPost('/v3/lists', $params, $requestHeaders);
+
+        return $this->hydrateResponse($response, CreateResponse::class);
+    }
+
+    /**
+     * Returns a single mailing list.
+     * @param  string                   $address        Address of the mailing list
+     * @param  array                    $requestHeaders
+     * @return ShowResponse
+     * @throws ClientExceptionInterface
+     */
+    public function show(string $address, array $requestHeaders = [])
+    {
+        Assert::stringNotEmpty($address);
+
+        $response = $this->httpGet(sprintf('/v3/lists/%s', $address), [], $requestHeaders);
+
+        return $this->hydrateResponse($response, ShowResponse::class);
+    }
+
+    /**
+     * Updates a mailing list.
+     * @param  string                   $address        Address of the mailing list
+     * @param  array                    $parameters     Array of field => value pairs to update
+     * @param  array                    $requestHeaders
+     * @return UpdateResponse
+     * @throws ClientExceptionInterface
+     */
+    public function update(string $address, array $parameters = [], array $requestHeaders = [])
+    {
+        Assert::stringNotEmpty($address);
+        Assert::isArray($parameters);
+
+        foreach ($parameters as $field => $value) {
+            switch ($field) {
+                case 'address':
+                case 'name':
+                case 'description':
+                    Assert::stringNotEmpty($value);
+
+                    break;
+                case 'access_level':
+                    Assert::oneOf($value, ['readonly', 'members', 'everyone']);
+
+                    break;
+            }
+        }
+
+        $response = $this->httpPut(sprintf('/v3/lists/%s', $address), $parameters, $requestHeaders);
+
+        return $this->hydrateResponse($response, UpdateResponse::class);
+    }
+
+    /**
+     * Removes a mailing list from the domain.
+     *
+     * @param  string                             $address Address of the mailing list
+     * @return DeleteResponse
+     * @throws Exception|ClientExceptionInterface
+     */
+    public function delete(string $address, array $requestHeaders = [])
+    {
+        Assert::stringNotEmpty($address);
+
+        $response = $this->httpDelete(sprintf('/v3/lists/%s', $address), [], $requestHeaders);
+
+        return $this->hydrateResponse($response, DeleteResponse::class);
+    }
+
+    /**
+     * Validates mailing list.
+     * @param  string                   $address        Address of the mailing list
+     * @param  array                    $requestHeaders
+     * @return ValidateResponse
+     * @throws ClientExceptionInterface
+     */
+    public function validate(string $address, array $requestHeaders = [])
+    {
+        Assert::stringNotEmpty($address);
+
+        $response = $this->httpPost(sprintf('/v3/lists/%s/validate', $address), [], $requestHeaders);
+
+        return $this->hydrateResponse($response, ValidateResponse::class);
+    }
+
+    /**
+     * Get mailing list validation status.
+     * @param  string                   $address        Address of the mailing list
+     * @param  array                    $requestHeaders
+     * @return ValidationStatusResponse
+     * @throws ClientExceptionInterface
+     */
+    public function getValidationStatus(string $address, array $requestHeaders = [])
+    {
+        Assert::stringNotEmpty($address);
+
+        $response = $this->httpGet(sprintf('/v3/lists/%s/validate', $address), [], $requestHeaders);
+
+        return $this->hydrateResponse($response, ValidationStatusResponse::class);
+    }
+
+    /**
+     * Cancel mailing list validation.
+     * @param  string                   $address        Address of the mailing list
+     * @param  array                    $requestHeaders
+     * @return ValidationCancelResponse
+     * @throws ClientExceptionInterface
+     */
+    public function cancelValidation(string $address, array $requestHeaders = [])
+    {
+        Assert::stringNotEmpty($address);
+
+        $response = $this->httpDelete(sprintf('/v3/lists/%s/validate', $address), [], $requestHeaders);
+
+        return $this->hydrateResponse($response, ValidationCancelResponse::class);
+    }
+
+    /**
+     * Bulk upload members to a mailing list (JSON)
+     * @param string $mailList
+     * @param array $members
+     * @param bool $isUpsert
+     * @param array $requestHeaders
+     * @return BulkResponse
+     * @throws ClientExceptionInterface
+     */
+    public function bulkUploadJson(string $mailList, array $members, bool $isUpsert = false, array $requestHeaders = [])
+    {
+        Assert::stringNotEmpty($mailList);
+
+        $query = [
+            'members' => json_encode($members),
+            'upsert' => $isUpsert,
+        ];
+
+        $response = $this->httpPost(
+            sprintf('/v3/lists/%s/members.json?%s', $mailList, http_build_query($query)),
+            [],
+            $requestHeaders
+        );
+
+        return $this->hydrateResponse($response, BulkResponse::class);
+    }
+
+    /**
+     * Bulk upload members to a mailing list (CSV)
+     * //TODO
+     * @param string $mailList
+     * @param array $members
+     * @param bool $isUpsert
+     * @param array $requestHeaders
+     * @return BulkResponse
+     * @throws ClientExceptionInterface
+     */
+    public function bulkUploadCsv(string $mailList, array $members, bool $isUpsert = false, array $requestHeaders = [])
+    {
+        Assert::stringNotEmpty($mailList);
+
+        $payload = [
+            'members' => implode(",", $members),
+            'upsert' => $isUpsert ? 'true' : 'false'
+        ];
+
+        $response = $this->httpPost(
+            sprintf('/v3/lists/%s/members.csv', $mailList),
+            $payload,
+            $requestHeaders
+        );
+
+        return $this->hydrateResponse($response, BulkResponse::class);
+    }
+}

+ 251 - 0
vendor/mailgun/mailgun-php/src/Api/MailingList/Member.php

@@ -0,0 +1,251 @@
+<?php
+
+declare(strict_types=1);
+
+/*
+ * Copyright (C) 2013 Mailgun
+ *
+ * This software may be modified and distributed under the terms
+ * of the MIT license. See the LICENSE file for details.
+ */
+
+namespace Mailgun\Api\MailingList;
+
+use Mailgun\Api\HttpApi;
+use Mailgun\Api\Pagination;
+use Mailgun\Assert;
+use Mailgun\Exception\InvalidArgumentException;
+use Mailgun\Model\MailingList\Member\CreateResponse;
+use Mailgun\Model\MailingList\Member\DeleteResponse;
+use Mailgun\Model\MailingList\Member\IndexResponse;
+use Mailgun\Model\MailingList\Member\ShowResponse;
+use Mailgun\Model\MailingList\Member\UpdateResponse;
+use Mailgun\Model\MailingList\UpdateResponse as MailingListUpdateResponse;
+use Psr\Http\Client\ClientExceptionInterface;
+
+/**
+ * @see https://documentation.mailgun.com/en/latest/api-mailinglists.html
+ */
+class Member extends HttpApi
+{
+    use Pagination;
+
+    /**
+     * Returns a paginated list of members of the mailing list.
+     * @param  string                   $address        Address of the mailing list
+     * @param  int                      $limit          Maximum number of records to return (optional: 100 by default)
+     * @param  bool|null                $subscribed     `true` to lists subscribed, `false` for unsubscribed. list all if null
+     * @param  array                    $requestHeaders
+     * @return IndexResponse
+     * @throws ClientExceptionInterface
+     */
+    public function index(string $address, int $limit = 100, ?bool $subscribed = null, array $requestHeaders = [])
+    {
+        Assert::stringNotEmpty($address);
+        Assert::greaterThan($limit, 0);
+
+        $params = [
+            'limit' => $limit,
+        ];
+
+        if (true === $subscribed) {
+            $params['subscribed'] = 'yes';
+        } elseif (false === $subscribed) {
+            $params['subscribed'] = 'no';
+        }
+
+        $response = $this->httpGet(sprintf('/v3/lists/%s/members/pages', $address), $params, $requestHeaders);
+
+        return $this->hydrateResponse($response, IndexResponse::class);
+    }
+
+    /**
+     * Shows a single member of the mailing list.
+     * @param  string                   $list           Address of the mailing list
+     * @param  string                   $address        Address of the member
+     * @param  array                    $requestHeaders
+     * @return ShowResponse
+     * @throws ClientExceptionInterface
+     */
+    public function show(string $list, string $address, array $requestHeaders = [])
+    {
+        Assert::stringNotEmpty($list);
+        Assert::stringNotEmpty($address);
+
+        $response = $this->httpGet(sprintf('/v3/lists/%s/members/%s', $list, $address), [], $requestHeaders);
+
+        return $this->hydrateResponse($response, ShowResponse::class);
+    }
+
+    /**
+     * Creates (or updates) a member of the mailing list.
+     * @param  string                   $list           Address of the mailing list
+     * @param  string                   $address        Address for the member
+     * @param  string|null              $name           Name for the member (optional)
+     * @param  array                    $vars           Array of field => value pairs to store additional data
+     * @param  bool                     $subscribed     `true` to add as subscribed (default), `false` as unsubscribed
+     * @param  bool                     $upsert         `true` to update member if present, `false` to raise error in case of a duplicate member (default)
+     * @param  array                    $requestHeaders
+     * @return CreateResponse
+     * @throws ClientExceptionInterface
+     */
+    public function create(
+        string $list,
+        string $address,
+        ?string $name = null,
+        array $vars = [],
+        ?bool $subscribed = true,
+        ?bool $upsert = false,
+        array $requestHeaders = []
+    ) {
+        Assert::stringNotEmpty($list);
+        Assert::stringNotEmpty($address);
+        Assert::nullOrStringNotEmpty($name);
+
+        $params = [
+            'address' => $address,
+            'vars' => \json_encode($vars),
+            'subscribed' => $subscribed ? 'yes' : 'no',
+            'upsert' => $upsert ? 'yes' : 'no',
+        ];
+
+        if (null !== $name) {
+            $params['name'] = $name;
+        }
+
+        $response = $this->httpPost(sprintf('/v3/lists/%s/members', $list), $params, $requestHeaders);
+
+        return $this->hydrateResponse($response, CreateResponse::class);
+    }
+
+    /**
+     * Adds multiple members (up to 1000) to the mailing list.
+     * @param string $list Address of the mailing list
+     * @param array $members Array of members, each item should be either a single string address or an array of member properties
+     * @param bool $upsert `true` to update existing members, `false` (default) to ignore duplicates
+     * @param array $requestHeaders
+     * @return MailingListUpdateResponse|null
+     * @throws ClientExceptionInterface
+     */
+    public function createMultiple(string $list, array $members, bool $upsert = false, array $requestHeaders = []): ?MailingListUpdateResponse
+    {
+        Assert::stringNotEmpty($list);
+        Assert::isArray($members);
+
+        // workaround for webmozart/asserts <= 1.2
+        if (count($members) > 1000) {
+            throw new InvalidArgumentException(sprintf('Expected an Array to contain at most %2$d elements. Got: %d', 1000, count($members)));
+        }
+
+        foreach ($members as $data) {
+            if (is_string($data)) {
+                Assert::stringNotEmpty($data);
+                // single address - no additional validation required
+                continue;
+            }
+
+            Assert::isArray($data);
+
+            foreach ($data as $field => &$value) {
+                switch ($field) {
+                    case 'address':
+                        Assert::stringNotEmpty($value);
+
+                        break;
+                    case 'vars':
+                        if (is_array($value)) {
+                            $value = json_encode($value);
+                        }
+                        break;
+                    // We should assert that "vars"'s $value is a string.
+                        // no break
+                    case 'name':
+                        Assert::string($value);
+                        break;
+                    case 'subscribed':
+                        Assert::oneOf($value, ['yes', 'no', true, false]);
+
+                        break;
+                }
+            }
+            unset($value);
+        }
+
+        $params = [
+            'members' => json_encode($members),
+            'upsert' => $upsert ? 'yes' : 'no',
+        ];
+
+        $response = $this->httpPost(sprintf('/v3/lists/%s/members.json', $list), $params, $requestHeaders);
+
+        return $this->hydrateResponse($response, MailingListUpdateResponse::class);
+    }
+
+    /**
+     * Updates a member on the mailing list.
+     *
+     * @param string $list       Address of the mailing list
+     * @param string $address    Address of the member
+     * @param array  $parameters Array of key => value pairs to update
+     *
+     * @return UpdateResponse
+     *
+     * @throws \Exception
+     */
+    public function update(string $list, string $address, array $parameters = [], array $requestHeaders = [])
+    {
+        Assert::stringNotEmpty($list);
+        Assert::stringNotEmpty($address);
+        Assert::isArray($parameters);
+
+        foreach ($parameters as $field => &$value) {
+            switch ($field) {
+                case 'vars':
+                    if (is_array($value)) {
+                        $value = json_encode($value);
+                    }
+                    // We should assert that "vars"'s $value is a string.
+                    // no break
+                case 'address':
+                    Assert::stringNotEmpty($value);
+
+                    break;
+                case 'name':
+                    Assert::nullOrStringNotEmpty($value);
+
+                    break;
+                case 'subscribed':
+                    Assert::oneOf($value, ['yes', 'no']);
+
+                    break;
+            }
+        }
+        unset($value);
+
+        if (array_key_exists('name', $parameters) && null === $parameters['name']) {
+            unset($parameters['name']);
+        }
+
+        $response = $this->httpPut(sprintf('/v3/lists/%s/members/%s', $list, $address), $parameters, $requestHeaders);
+
+        return $this->hydrateResponse($response, UpdateResponse::class);
+    }
+
+    /**
+     * Removes a member from the mailing list.
+     * @param  string                   $list           Address of the mailing list
+     * @param  string                   $address        Address of the member
+     * @param  array                    $requestHeaders
+     * @return DeleteResponse
+     * @throws ClientExceptionInterface
+     */
+    public function delete(string $list, string $address, array $requestHeaders = [])
+    {
+        Assert::stringNotEmpty($list);
+        Assert::stringNotEmpty($address);
+
+        $response = $this->httpDelete(sprintf('/v3/lists/%s/members/%s', $list, $address), [], $requestHeaders);
+
+        return $this->hydrateResponse($response, DeleteResponse::class);
+    }
+}

+ 250 - 0
vendor/mailgun/mailgun-php/src/Api/Message.php

@@ -0,0 +1,250 @@
+<?php
+
+declare(strict_types=1);
+
+/*
+ * Copyright (C) 2013 Mailgun
+ *
+ * This software may be modified and distributed under the terms
+ * of the MIT license. See the LICENSE file for details.
+ */
+
+namespace Mailgun\Api;
+
+use Exception;
+use Mailgun\Assert;
+use Mailgun\Exception\InvalidArgumentException;
+use Mailgun\Message\BatchMessage;
+use Mailgun\Model\Message\QueueStatusResponse;
+use Mailgun\Model\Message\SendResponse;
+use Mailgun\Model\Message\ShowResponse;
+use Psr\Http\Client\ClientExceptionInterface;
+use Psr\Http\Message\ResponseInterface;
+use RuntimeException;
+
+/**
+ * @see https://documentation.mailgun.com/en/latest/api-sending.html
+ *
+ * @author Tobias Nyholm <tobias.nyholm@gmail.com>
+ */
+class Message extends HttpApi
+{
+    /**
+     * @param  string       $domain
+     * @param  bool         $autoSend
+     * @return BatchMessage
+     */
+    public function getBatchMessage(string $domain, bool $autoSend = true): BatchMessage
+    {
+        return new BatchMessage($this, $domain, $autoSend);
+    }
+
+    /**
+     * @see https://documentation.mailgun.com/en/latest/api-sending.html#sending
+     * @param  string                         $domain
+     * @param  array                          $params
+     * @param  array                          $requestHeaders
+     * @return SendResponse|ResponseInterface
+     * @throws ClientExceptionInterface
+     */
+    public function send(string $domain, array $params, array $requestHeaders = [])
+    {
+        Assert::string($domain);
+        Assert::notEmpty($domain);
+        Assert::notEmpty($params);
+
+        $postDataMultipart = [];
+        $fields = ['attachment', 'inline'];
+        foreach ($fields as $fieldName) {
+            if (!isset($params[$fieldName])) {
+                continue;
+            }
+
+            Assert::isArray($params[$fieldName]);
+            foreach ($params[$fieldName] as $file) {
+                $postDataMultipart[] = $this->prepareFile($fieldName, $file);
+            }
+
+            unset($params[$fieldName]);
+        }
+
+        $postDataMultipart = array_merge($this->prepareMultipartParameters($params), $postDataMultipart);
+        try {
+            $response = $this->httpPostRaw(sprintf('/v3/%s/messages', $domain), $postDataMultipart, $requestHeaders);
+        } catch (Exception $exception) {
+            throw new RuntimeException($exception->getMessage());
+        } finally {
+            $this->closeResources($postDataMultipart);
+        }
+
+        return $this->hydrateResponse($response, SendResponse::class);
+    }
+
+    /**
+     * @see https://documentation.mailgun.com/en/latest/api-sending.html#sending
+     * @param  string                         $domain
+     * @param  array                          $recipients     with all you send emails to. Including bcc and cc
+     * @param  string                         $message        Message filepath or content
+     * @param  array                          $params
+     * @param  array                          $requestHeaders
+     * @return SendResponse|ResponseInterface
+     * @throws ClientExceptionInterface
+     */
+    public function sendMime(string $domain, array $recipients, string $message, array $params, array $requestHeaders = [])
+    {
+        Assert::string($domain);
+        Assert::notEmpty($domain);
+        Assert::notEmpty($recipients);
+        Assert::notEmpty($message);
+        Assert::nullOrIsArray($params);
+
+        $params['to'] = $recipients;
+        $postDataMultipart = $this->prepareMultipartParameters($params);
+
+        if (strlen($message) < PHP_MAXPATHLEN && is_file($message)) {
+            $fileData = ['filePath' => $message];
+        } else {
+            $fileData = [
+                'fileContent' => $message,
+                'filename' => 'message',
+            ];
+        }
+        $postDataMultipart[] = $this->prepareFile('message', $fileData);
+        try {
+            $response = $this->httpPostRaw(sprintf('/v3/%s/messages.mime', $domain), $postDataMultipart, $requestHeaders);
+        } catch (Exception $exception) {
+            throw new RuntimeException($exception->getMessage());
+        } finally {
+            $this->closeResources($postDataMultipart);
+        }
+
+        return $this->hydrateResponse($response, SendResponse::class);
+    }
+
+    /**
+     * Get stored message.
+     * @see https://documentation.mailgun.com/en/latest/api-sending.html#retrieving-stored-messages
+     * @param  string                         $url
+     * @param  bool                           $rawMessage     if true we will use "Accept: message/rfc2822" header
+     * @param  array                          $requestHeaders
+     * @return ShowResponse|ResponseInterface
+     * @throws ClientExceptionInterface
+     */
+    public function show(string $url, bool $rawMessage = false, array $requestHeaders = [])
+    {
+        Assert::notEmpty($url);
+
+        $headers = [];
+        if ($rawMessage) {
+            $headers['Accept'] = 'message/rfc2822';
+        }
+        if (!empty($requestHeaders)) {
+            $headers = array_merge($headers, $requestHeaders);
+        }
+
+        $response = $this->httpGet($url, [], $headers);
+
+        return $this->hydrateResponse($response, ShowResponse::class);
+    }
+
+    /**
+     * Get messages queue status
+     * @see https://documentation.mailgun.com/docs/mailgun/api-reference/openapi-final/tag/Messages/#tag/Messages/operation/httpapi.(*LegacyHttpApi).GetDomainSendingQueues-fm-70
+     * @param string $domain
+     * @param array $requestHeaders
+     * @return QueueStatusResponse
+     * @throws ClientExceptionInterface
+     */
+    public function getMessageQueueStatus(string $domain, array $requestHeaders = [])
+    {
+        Assert::notEmpty($domain);
+        $response = $this->httpGet(sprintf('/v3/domains/%s/sending_queues', $domain), [], $requestHeaders);
+
+        return $this->hydrateResponse($response, QueueStatusResponse::class);
+    }
+
+    /**
+     * @param string $domain
+     * @param string $storageId
+     * @param array $requestHeaders
+     * @return ShowResponse
+     * @throws ClientExceptionInterface
+     */
+    public function retrieveStoredMessage(string $domain, string $storageId, array $requestHeaders = []): ShowResponse
+    {
+        Assert::notEmpty($domain);
+        Assert::notEmpty($storageId);
+        $response = $this->httpGet(sprintf('/v3/domains/%s/messages/%s', $domain, $storageId), [], $requestHeaders);
+
+        return $this->hydrateResponse($response, ShowResponse::class);
+    }
+
+    /**
+     * @param array $filePath array('fileContent' => 'content') or array('filePath' => '/foo/bar')
+     *
+     * @throws InvalidArgumentException
+     */
+    private function prepareFile(string $fieldName, array $filePath): array
+    {
+        $filename = $filePath['filename'] ?? null;
+
+        if (isset($filePath['fileContent'])) {
+            // File from memory
+            $resource = fopen('php://temp', 'rb+');
+            fwrite($resource, $filePath['fileContent']);
+            rewind($resource);
+        } elseif (isset($filePath['filePath'])) {
+            // File form path
+            $path = $filePath['filePath'];
+
+            // Remove leading @ symbol
+            if (0 === strpos($path, '@')) {
+                $path = substr($path, 1);
+            }
+
+            $resource = fopen($path, 'rb');
+        } else {
+            throw new InvalidArgumentException('When using a file you need to specify parameter "fileContent" or "filePath"');
+        }
+
+        return [
+            'name' => $fieldName,
+            'content' => $resource,
+            'filename' => $filename,
+        ];
+    }
+
+    /**
+     * Prepare multipart parameters. Make sure each POST parameter is split into an array with 'name' and 'content' keys.
+     */
+    private function prepareMultipartParameters(array $params): array
+    {
+        $postDataMultipart = [];
+        foreach ($params as $key => $value) {
+            // If $value is not an array we cast it to an array
+            foreach ((array) $value as $subValue) {
+                if (is_int($subValue)) {
+                    $subValue = (string) $subValue;
+                }
+                $postDataMultipart[] = [
+                    'name' => $key,
+                    'content' => $subValue,
+                ];
+            }
+        }
+
+        return $postDataMultipart;
+    }
+
+    /**
+     * Close open resources.
+     */
+    private function closeResources(array $params): void
+    {
+        foreach ($params as $param) {
+            if (is_array($param) && array_key_exists('content', $param) && is_resource($param['content'])) {
+                fclose($param['content']);
+            }
+        }
+    }
+}

+ 73 - 0
vendor/mailgun/mailgun-php/src/Api/Metrics.php

@@ -0,0 +1,73 @@
+<?php
+
+declare(strict_types=1);
+
+/*
+ * Copyright (C) 2013 Mailgun
+ *
+ * This software may be modified and distributed under the terms
+ * of the MIT license. See the LICENSE file for details.
+ */
+
+namespace Mailgun\Api;
+
+use Exception;
+use Mailgun\Assert;
+use Mailgun\Model\Metrics\MetricsResponse;
+use Psr\Http\Client\ClientExceptionInterface;
+
+/**
+ * @see https://documentation.mailgun.com/docs/mailgun/api-reference/openapi-final/tag/Metrics/
+ */
+class Metrics extends HttpApi
+{
+    /**
+     * Query metrics for the total account.
+     *
+     * @param array $payload
+     * @param array $requestHeaders
+     * @return MetricsResponse
+     * @throws ClientExceptionInterface
+     * @throws Exception
+     */
+    public function loadMetrics(array $payload = [], array $requestHeaders = []): MetricsResponse
+    {
+        // Validating required params
+        if (!isset($payload['start']) || !isset($payload['end'])) {
+            throw new Exception("The 'start' and 'end' parameters are required.");
+        }
+
+        // Ensure start and end date are in RFC 2822 format
+        Assert::string($payload['start'], "Start date must be in RFC 2822 format");
+        Assert::stringNotEmpty($payload['end'], "End date must be in RFC 2822 format");
+
+        // Ensure resolution is valid (day, hour, month)
+        if (!empty($payload['resolution'])) {
+            Assert::oneOf($payload['resolution'], ['day', 'hour', 'month'], 'Invalid resolution format');
+        }
+
+        // Check if filters are properly set up
+        if (!empty($payload['filter']['AND'])) {
+            foreach ($payload['filter']['AND'] as $filter) {
+                Assert::stringNotEmpty($filter['attribute'], "Filter attribute must be specified");
+                Assert::stringNotEmpty($filter['comparator'], "Comparator must be specified");
+                Assert::isArray($filter['values'], "Filter values must be an array");
+            }
+        }
+
+        // Validate dimensions (must be an array and contain only valid values)
+        if (isset($payload['dimensions'])) {
+            Assert::isArray($payload['dimensions'], 'Dimensions must be an array');
+            $validDimensions = ['time', 'domain', 'ip', 'ip_pool', 'recipient_domain', 'tag', 'country', 'subaccount'];
+            foreach ($payload['dimensions'] as $dimension) {
+                Assert::oneOf($dimension, $validDimensions, "Invalid dimension: $dimension");
+            }
+        }
+
+        $requestHeaders['Content-Type'] = 'application/json';
+
+        $response = $this->httpPost('/v1/analytics/metrics', $payload, $requestHeaders);
+
+        return $this->hydrateResponse($response, MetricsResponse::class);
+    }
+}

+ 98 - 0
vendor/mailgun/mailgun-php/src/Api/Pagination.php

@@ -0,0 +1,98 @@
+<?php
+
+declare(strict_types=1);
+
+/*
+ * Copyright (C) 2013 Mailgun
+ *
+ * This software may be modified and distributed under the terms
+ * of the MIT license. See the LICENSE file for details.
+ */
+
+namespace Mailgun\Api;
+
+use Mailgun\Assert;
+use Mailgun\Model\PagingProvider;
+use Psr\Http\Client\ClientExceptionInterface;
+use Psr\Http\Message\ResponseInterface;
+
+/**
+ * @author Tobias Nyholm <tobias.nyholm@gmail.com>
+ */
+trait Pagination
+{
+    /**
+     * @param string $path
+     * @param array $parameters
+     * @param array $requestHeaders
+     * @return ResponseInterface
+     */
+    abstract protected function httpGet(string $path, array $parameters = [], array $requestHeaders = []): ResponseInterface;
+
+
+    /**
+     * @param class-string $className
+     * @param ResponseInterface $response
+     * @return mixed
+     */
+    abstract protected function hydrateResponse(ResponseInterface $response, string $className);
+
+    /**
+     * @param  PagingProvider           $response
+     * @return PagingProvider|null
+     * @throws ClientExceptionInterface
+     */
+    public function nextPage(PagingProvider $response): ?PagingProvider
+    {
+        return $this->getPaginationUrl($response->getNextUrl(), get_class($response));
+    }
+
+    /**
+     * @param  PagingProvider           $response
+     * @return PagingProvider|null
+     * @throws ClientExceptionInterface
+     */
+    public function previousPage(PagingProvider $response): ?PagingProvider
+    {
+        return $this->getPaginationUrl($response->getPreviousUrl(), get_class($response));
+    }
+
+    /**
+     * @param PagingProvider $response
+     * @return PagingProvider|null
+     * @throws ClientExceptionInterface
+     */
+    public function firstPage(PagingProvider $response): ?PagingProvider
+    {
+        return $this->getPaginationUrl($response->getFirstUrl(), get_class($response));
+    }
+
+    /**
+     * @param  PagingProvider           $response
+     * @return PagingProvider|null
+     * @throws ClientExceptionInterface
+     */
+    public function lastPage(PagingProvider $response): ?PagingProvider
+    {
+        return $this->getPaginationUrl($response->getLastUrl(), get_class($response));
+    }
+
+    /**
+     * @param  string                   $url
+     * @param  class-string             $class
+     * @return PagingProvider|null
+     * @throws ClientExceptionInterface
+     */
+    private function getPaginationUrl(string $url, string $class): ?PagingProvider
+    {
+        Assert::stringNotEmpty($class);
+
+        if (empty($url)) {
+            return null;
+        }
+
+        $response = $this->httpGet($url);
+
+        return $this->hydrateResponse($response, $class);
+    }
+}

+ 178 - 0
vendor/mailgun/mailgun-php/src/Api/Route.php

@@ -0,0 +1,178 @@
+<?php
+
+declare(strict_types=1);
+
+/*
+ * Copyright (C) 2013 Mailgun
+ *
+ * This software may be modified and distributed under the terms
+ * of the MIT license. See the LICENSE file for details.
+ */
+
+namespace Mailgun\Api;
+
+use Mailgun\Assert;
+use Mailgun\Model\Route\CreateResponse;
+use Mailgun\Model\Route\DeleteResponse;
+use Mailgun\Model\Route\IndexResponse;
+use Mailgun\Model\Route\MatchRouteResponse;
+use Mailgun\Model\Route\ShowResponse;
+use Mailgun\Model\Route\UpdateResponse;
+use Psr\Http\Client\ClientExceptionInterface;
+
+/**
+ * @see https://documentation.mailgun.com/en/latest/api-routes.html
+ *
+ * @author David Garcia <me@davidgarcia.cat>
+ */
+class Route extends HttpApi
+{
+    /**
+     * Fetches the list of Routes.
+     * @param  int                      $limit          Maximum number of records to return. (100 by default)
+     * @param  int                      $skip           Number of records to skip. (0 by default)
+     * @param  array                    $requestHeaders
+     * @return IndexResponse
+     * @throws ClientExceptionInterface
+     */
+    public function index(int $limit = 100, int $skip = 0, array $requestHeaders = [])
+    {
+        Assert::greaterThan($limit, 0);
+        Assert::greaterThanEq($skip, 0);
+        Assert::range($limit, 1, 1000);
+
+        $params = [
+            'limit' => $limit,
+            'skip' => $skip,
+        ];
+
+        $response = $this->httpGet('/v3/routes', $params, $requestHeaders);
+
+        return $this->hydrateResponse($response, IndexResponse::class);
+    }
+
+    /**
+     * Returns a single Route object based on its ID.
+     * @param  string                   $routeId        Route ID returned by the Routes::index() method
+     * @param  array                    $requestHeaders
+     * @return ShowResponse
+     * @throws ClientExceptionInterface
+     */
+    public function show(string $routeId, array $requestHeaders = [])
+    {
+        Assert::stringNotEmpty($routeId);
+
+        $response = $this->httpGet(sprintf('/v3/routes/%s', $routeId), [], $requestHeaders);
+
+        return $this->hydrateResponse($response, ShowResponse::class);
+    }
+
+    /**
+     * Creates a new Route.
+     * @param  string                   $expression     A filter expression like "match_recipient('.*@gmail.com')"
+     * @param  array                    $actions        Route action. This action is executed when the expression evaluates to True. Example: "forward('alice@example.com')"
+     * @param  string                   $description    An arbitrary string
+     * @param  int                      $priority       Integer: smaller number indicates higher priority. Higher priority routes are handled first. Defaults to 0.
+     * @param  array                    $requestHeaders
+     * @return CreateResponse
+     * @throws ClientExceptionInterface
+     */
+    public function create(string $expression, array $actions, string $description, int $priority = 0, array $requestHeaders = [])
+    {
+        Assert::isArray($actions);
+
+        $params = [
+            'priority' => (string) $priority,
+            'expression' => $expression,
+            'action' => $actions,
+            'description' => $description,
+        ];
+
+        $response = $this->httpPost('/v3/routes', $params, $requestHeaders);
+
+        return $this->hydrateResponse($response, CreateResponse::class);
+    }
+
+    /**
+     * Updates a given Route by ID. All parameters are optional.
+     * This API call only updates the specified fields leaving others unchanged.
+     * @param  string                   $routeId        Route ID returned by the Routes::index() method
+     * @param  string|null              $expression     A filter expression like "match_recipient('.*@gmail.com')"
+     * @param  array                    $actions        Route action. This action is executed when the expression evaluates to True. Example: "forward('alice@example.com')"
+     * @param  string|null              $description    An arbitrary string
+     * @param  int|null                 $priority       Integer: smaller number indicates higher priority. Higher priority routes are handled first. Defaults to 0.
+     * @param  array                    $requestHeaders
+     * @return UpdateResponse
+     * @throws ClientExceptionInterface
+     */
+    public function update(
+        string $routeId,
+        ?string $expression = null,
+        array $actions = [],
+        ?string $description = null,
+        ?int $priority = null,
+        array $requestHeaders = []
+    ) {
+        Assert::stringNotEmpty($routeId);
+        $params = [];
+
+        if (!empty($expression)) {
+            $params['expression'] = trim($expression);
+        }
+
+        foreach ($actions as $action) {
+            Assert::stringNotEmpty($action);
+
+            $params['action'] = isset($params['action']) ? $params['action'] : [];
+            $params['action'][] = $action;
+        }
+
+        if (!empty($description)) {
+            $params['description'] = trim($description);
+        }
+
+        if (!empty($priority)) {
+            $params['priority'] = (string) $priority;
+        }
+
+        $response = $this->httpPut(sprintf('/v3/routes/%s', $routeId), $params, $requestHeaders);
+
+        return $this->hydrateResponse($response, UpdateResponse::class);
+    }
+
+    /**
+     * Deletes a Route based on the ID.
+     * @param  string                   $routeId        Route ID returned by the Routes::index() method
+     * @param  array                    $requestHeaders
+     * @return DeleteResponse
+     * @throws ClientExceptionInterface
+     */
+    public function delete(string $routeId, array $requestHeaders = [])
+    {
+        Assert::stringNotEmpty($routeId);
+
+        $response = $this->httpDelete(sprintf('/v3/routes/%s', $routeId), [], $requestHeaders);
+
+        return $this->hydrateResponse($response, DeleteResponse::class);
+    }
+
+    /**
+     * Match address to route
+     * @param string $address
+     * @param array $requestHeaders
+     * @return MatchRouteResponse
+     * @throws ClientExceptionInterface
+     */
+    public function matchAddressToRoute(string $address, array $requestHeaders = [])
+    {
+        Assert::stringNotEmpty($address);
+
+        $query = [
+            'address' => $address,
+        ];
+
+        $response = $this->httpGet('/v3/routes/match', $query, $requestHeaders);
+
+        return $this->hydrateResponse($response, MatchRouteResponse::class);
+    }
+}

+ 112 - 0
vendor/mailgun/mailgun-php/src/Api/Stats.php

@@ -0,0 +1,112 @@
+<?php
+
+declare(strict_types=1);
+
+/*
+ * Copyright (C) 2013 Mailgun
+ *
+ * This software may be modified and distributed under the terms
+ * of the MIT license. See the LICENSE file for details.
+ */
+
+namespace Mailgun\Api;
+
+use Exception;
+use Mailgun\Assert;
+use Mailgun\Model\Stats\AggregateCountriesResponse;
+use Mailgun\Model\Stats\AggregateDevicesResponse;
+use Mailgun\Model\Stats\AggregateResponse;
+use Mailgun\Model\Stats\TotalResponse;
+use Psr\Http\Client\ClientExceptionInterface;
+use Psr\Http\Message\ResponseInterface;
+
+/**
+ * @see https://documentation.mailgun.com/en/latest/api-stats.html
+ * @author Tobias Nyholm <tobias.nyholm@gmail.com>
+ */
+class Stats extends HttpApi
+{
+    public const EVENT_ACCEPTED = 'accepted';
+    public const EVENT_DELIVERED = 'delivered';
+    public const EVENT_FAILED = 'failed';
+    public const EVENT_OPENED = 'opened';
+    public const EVENT_CLICKED = 'clicked';
+    public const EVENT_UNSUBSCRIBED = 'unsubscribed';
+    public const EVENT_COMPLAINED = 'complained';
+    public const EVENT_STORED = 'stored';
+
+    /**
+     * @param string $domain
+     * @param array $params
+     * @param array $requestHeaders
+     * @return TotalResponse|array
+     * @throws ClientExceptionInterface
+     * @throws Exception
+     */
+    public function total(string $domain, array $params = [], array $requestHeaders = [])
+    {
+        Assert::stringNotEmpty($domain);
+
+        $response = $this->httpGet(sprintf('/v3/%s/stats/total', rawurlencode($domain)), $params, $requestHeaders);
+
+        return $this->hydrateResponse($response, TotalResponse::class);
+    }
+
+    /**
+     * @param array $params
+     * @param array $requestHeaders
+     * @return TotalResponse|array
+     * @throws ClientExceptionInterface
+     * @throws Exception
+     */
+    public function totalAccount(array $params = [], array $requestHeaders = [])
+    {
+        Assert::keyExists($params, 'event', 'You must specify an event');
+
+        $response = $this->httpGet('/v3/stats/total', $params, $requestHeaders);
+
+        return $this->hydrateResponse($response, TotalResponse::class);
+    }
+
+    /**
+     * @param string $domain
+     * @param array $requestHeaders
+     * @return AggregateResponse
+     * @throws ClientExceptionInterface
+     * @throws Exception
+     */
+    public function aggregateCountsByESP(string $domain, array $requestHeaders = []): AggregateResponse
+    {
+        $response = $this->httpGet(sprintf('/v3/%s/aggregates/providers', rawurlencode($domain)), [], $requestHeaders);
+
+        return $this->hydrateResponse($response, AggregateResponse::class);
+    }
+
+    /**
+     * @param string $domain
+     * @param array $requestHeaders
+     * @return AggregateDevicesResponse
+     * @throws ClientExceptionInterface
+     * @throws Exception
+     */
+    public function aggregateByDevices(string $domain, array $requestHeaders = []): AggregateDevicesResponse
+    {
+        $response = $this->httpGet(sprintf('/v3/%s/aggregates/devices', rawurlencode($domain)), [], $requestHeaders);
+
+        return $this->hydrateResponse($response, AggregateDevicesResponse::class);
+    }
+
+    /**
+     * @param string $domain
+     * @param array $requestHeaders
+     * @return AggregateCountriesResponse
+     * @throws ClientExceptionInterface
+     * @throws Exception
+     */
+    public function aggregateByCountry(string $domain, array $requestHeaders = []): AggregateCountriesResponse
+    {
+        $response = $this->httpGet(sprintf('/v3/%s/aggregates/countries', rawurlencode($domain)), [], $requestHeaders);
+
+        return $this->hydrateResponse($response, AggregateCountriesResponse::class);
+    }
+}

+ 103 - 0
vendor/mailgun/mailgun-php/src/Api/SubAccounts.php

@@ -0,0 +1,103 @@
+<?php
+
+declare(strict_types=1);
+
+/*
+ * Copyright (C) 2013 Mailgun
+ *
+ * This software may be modified and distributed under the terms
+ * of the MIT license. See the LICENSE file for details.
+ */
+
+namespace Mailgun\Api;
+
+use Mailgun\Assert;
+use Mailgun\Model\SubAccounts\CreateResponse;
+use Mailgun\Model\SubAccounts\IndexResponse;
+use Mailgun\Model\SubAccounts\ShowResponse;
+use Psr\Http\Client\ClientExceptionInterface;
+
+class SubAccounts extends HttpApi
+{
+    use Pagination;
+
+    private const ENTITY_API_URL = '/v5/accounts/subaccounts';
+
+    /**
+     * @param  string                   $name
+     * @return CreateResponse|null
+     * @throws ClientExceptionInterface
+     */
+    public function create(string $name): ?CreateResponse
+    {
+        Assert::stringNotEmpty($name);
+
+        $response = $this->httpPost(self::ENTITY_API_URL, ['name' => $name]);
+
+        return $this->hydrateResponse($response, CreateResponse::class);
+    }
+
+    /**
+     * @param  array                    $params
+     * @return IndexResponse|null
+     * @throws ClientExceptionInterface
+     */
+    public function index(array $params = []): ?IndexResponse
+    {
+        if (isset($params['limit'])) {
+            Assert::range($params['limit'], 1, 10);
+        }
+        if (isset($params['sort'])) {
+            Assert::isArray($params['sort']);
+        }
+        if (isset($params['enabled'])) {
+            Assert::boolean($params['enabled']);
+        }
+
+        $response = $this->httpGet(self::ENTITY_API_URL, $params);
+
+        return $this->hydrateResponse($response, IndexResponse::class);
+    }
+
+    /**
+     * @param  string                   $id
+     * @return ShowResponse|null
+     * @throws ClientExceptionInterface
+     */
+    public function show(string $id): ?ShowResponse
+    {
+        Assert::notEmpty($id);
+
+        $response = $this->httpGet(sprintf(self::ENTITY_API_URL.'/%s', $id));
+
+        return $this->hydrateResponse($response, ShowResponse::class);
+    }
+
+    /**
+     * @param  string                   $id
+     * @return ShowResponse|null
+     * @throws ClientExceptionInterface
+     */
+    public function disable(string $id): ?ShowResponse
+    {
+        Assert::notEmpty($id);
+
+        $response = $this->httpPost(sprintf(self::ENTITY_API_URL.'/%s/disable', $id));
+
+        return $this->hydrateResponse($response, ShowResponse::class);
+    }
+
+    /**
+     * @param  string                   $id
+     * @return ShowResponse|null
+     * @throws ClientExceptionInterface
+     */
+    public function enable(string $id): ?ShowResponse
+    {
+        Assert::notEmpty($id);
+
+        $response = $this->httpPost(sprintf(self::ENTITY_API_URL.'/%s/enable', $id), ['id' => $id]);
+
+        return $this->hydrateResponse($response, ShowResponse::class);
+    }
+}

+ 87 - 0
vendor/mailgun/mailgun-php/src/Api/Suppression.php

@@ -0,0 +1,87 @@
+<?php
+
+declare(strict_types=1);
+
+/*
+ * Copyright (C) 2013 Mailgun
+ *
+ * This software may be modified and distributed under the terms
+ * of the MIT license. See the LICENSE file for details.
+ */
+
+namespace Mailgun\Api;
+
+use Mailgun\Api\Suppression\Bounce;
+use Mailgun\Api\Suppression\Complaint;
+use Mailgun\Api\Suppression\Unsubscribe;
+use Mailgun\Api\Suppression\Whitelist;
+use Mailgun\HttpClient\RequestBuilder;
+use Mailgun\Hydrator\Hydrator;
+use Psr\Http\Client\ClientInterface;
+
+/**
+ * @see https://documentation.mailgun.com/en/latest/api-suppressions.html
+ *
+ * @author Sean Johnson <sean@mailgun.com>
+ */
+class Suppression
+{
+    /**
+     * @var ClientInterface
+     */
+    private $httpClient;
+
+    /**
+     * @var RequestBuilder
+     */
+    private $requestBuilder;
+
+    /**
+     * @var Hydrator
+     */
+    private $hydrator;
+
+    /**
+     * @param ClientInterface $httpClient
+     * @param RequestBuilder  $requestBuilder
+     * @param Hydrator        $hydrator
+     */
+    public function __construct($httpClient, RequestBuilder $requestBuilder, Hydrator $hydrator)
+    {
+        $this->httpClient = $httpClient;
+        $this->requestBuilder = $requestBuilder;
+        $this->hydrator = $hydrator;
+    }
+
+    /**
+     * @return Bounce
+     */
+    public function bounces(): Bounce
+    {
+        return new Bounce($this->httpClient, $this->requestBuilder, $this->hydrator);
+    }
+
+    /**
+     * @return Complaint
+     */
+    public function complaints(): Complaint
+    {
+        return new Complaint($this->httpClient, $this->requestBuilder, $this->hydrator);
+    }
+
+    /**
+     * @return Unsubscribe
+     */
+    public function unsubscribes(): Unsubscribe
+    {
+        return new Unsubscribe($this->httpClient, $this->requestBuilder, $this->hydrator);
+    }
+
+    /**
+     * @return Whitelist
+     */
+    public function whitelists(): Whitelist
+    {
+        return new Whitelist($this->httpClient, $this->requestBuilder, $this->hydrator);
+    }
+}

+ 143 - 0
vendor/mailgun/mailgun-php/src/Api/Suppression/Bounce.php

@@ -0,0 +1,143 @@
+<?php
+
+declare(strict_types=1);
+
+/*
+ * Copyright (C) 2013 Mailgun
+ *
+ * This software may be modified and distributed under the terms
+ * of the MIT license. See the LICENSE file for details.
+ */
+
+namespace Mailgun\Api\Suppression;
+
+use Mailgun\Api\HttpApi;
+use Mailgun\Api\Pagination;
+use Mailgun\Assert;
+use Mailgun\Model\Suppression\Bounce\CreateResponse;
+use Mailgun\Model\Suppression\Bounce\DeleteResponse;
+use Mailgun\Model\Suppression\Bounce\IndexResponse;
+use Mailgun\Model\Suppression\Bounce\ShowResponse;
+use Psr\Http\Client\ClientExceptionInterface;
+use RuntimeException;
+use Throwable;
+
+/**
+ * @see https://documentation.mailgun.com/en/latest/api-suppressions.html#bounces
+ * @author Sean Johnson <sean@mailgun.com>
+ */
+class Bounce extends HttpApi
+{
+    use Pagination;
+
+    /**
+     * @param  string                   $domain         Domain to list bounces for
+     * @param  int                      $limit          optional
+     * @param  array                    $requestHeaders
+     * @return IndexResponse|null
+     * @throws ClientExceptionInterface
+     */
+    public function index(string $domain, int $limit = 100, array $requestHeaders = []): ?IndexResponse
+    {
+        Assert::stringNotEmpty($domain);
+        Assert::range($limit, 1, 10000, '"Limit" parameter must be between 1 and 10000');
+
+        $params = [
+            'limit' => $limit,
+        ];
+
+        $response = $this->httpGet(sprintf('/v3/%s/bounces', $domain), $params, $requestHeaders);
+
+        return $this->hydrateResponse($response, IndexResponse::class);
+    }
+
+    /**
+     * @param  string                   $domain         Domain to show bounce from
+     * @param  string                   $address        Bounce address to show
+     * @param  array                    $requestHeaders
+     * @return ShowResponse|null
+     * @throws ClientExceptionInterface
+     */
+    public function show(string $domain, string $address, array $requestHeaders = []): ?ShowResponse
+    {
+        Assert::stringNotEmpty($domain);
+        Assert::stringNotEmpty($address);
+
+        $response = $this->httpGet(sprintf('/v3/%s/bounces/%s', $domain, $address), [], $requestHeaders);
+
+        return $this->hydrateResponse($response, ShowResponse::class);
+    }
+
+    /**
+     * @param  string                   $domain         Domain to create a bounce for
+     * @param  string                   $address        Address to create a bounce for
+     * @param  array                    $params         optional
+     * @param  array                    $requestHeaders
+     * @return CreateResponse|null
+     * @throws ClientExceptionInterface
+     */
+    public function create(string $domain, string $address, array $params = [], array $requestHeaders = []): ?CreateResponse
+    {
+        Assert::stringNotEmpty($domain);
+        Assert::stringNotEmpty($address);
+
+        $params['address'] = $address;
+
+        $response = $this->httpPost(sprintf('/v3/%s/bounces', $domain), $params, $requestHeaders);
+
+        return $this->hydrateResponse($response, CreateResponse::class);
+    }
+
+    /**
+     * @param  string                   $domain         Domain to delete a bounce for
+     * @param  string                   $address        Bounce address to delete
+     * @param  array                    $requestHeaders
+     * @return DeleteResponse|null
+     * @throws ClientExceptionInterface
+     */
+    public function delete(string $domain, string $address, array $requestHeaders = []): ?DeleteResponse
+    {
+        Assert::stringNotEmpty($domain);
+        Assert::stringNotEmpty($address);
+
+        $response = $this->httpDelete(sprintf('/v3/%s/bounces/%s', $domain, $address), [], $requestHeaders);
+
+        return $this->hydrateResponse($response, DeleteResponse::class);
+    }
+
+    /**
+     * @param  string                   $domain         Domain to delete all bounces for
+     * @param  array                    $requestHeaders
+     * @return DeleteResponse|null
+     * @throws ClientExceptionInterface
+     */
+    public function deleteAll(string $domain, array $requestHeaders = []): ?DeleteResponse
+    {
+        Assert::stringNotEmpty($domain);
+
+        $response = $this->httpDelete(sprintf('/v3/%s/bounces', $domain), [], $requestHeaders);
+
+        return $this->hydrateResponse($response, DeleteResponse::class);
+    }
+
+    /**
+     * @param string $domainId
+     * @param array $bounces
+     * @param array $requestHeaders
+     * @return mixed
+     */
+    public function importBouncesList(string $domainId, array $bounces, array $requestHeaders = [])
+    {
+        try {
+            $response = $this->httpPostRaw(
+                sprintf('/v3/%s/bounces/import', $domainId),
+                $bounces,
+                $requestHeaders
+            );
+        } catch (Throwable $throwable) {
+            throw new RuntimeException($throwable->getMessage());
+        }
+
+        return $this->hydrateResponse($response, CreateResponse::class);
+    }
+}

+ 146 - 0
vendor/mailgun/mailgun-php/src/Api/Suppression/Complaint.php

@@ -0,0 +1,146 @@
+<?php
+
+declare(strict_types=1);
+
+/*
+ * Copyright (C) 2013 Mailgun
+ *
+ * This software may be modified and distributed under the terms
+ * of the MIT license. See the LICENSE file for details.
+ */
+
+namespace Mailgun\Api\Suppression;
+
+use Mailgun\Api\HttpApi;
+use Mailgun\Api\Pagination;
+use Mailgun\Assert;
+use Mailgun\Model\Suppression\Complaint\CreateResponse;
+use Mailgun\Model\Suppression\Complaint\DeleteResponse;
+use Mailgun\Model\Suppression\Complaint\IndexResponse;
+use Mailgun\Model\Suppression\Complaint\ShowResponse;
+use Psr\Http\Client\ClientExceptionInterface;
+use RuntimeException;
+use Throwable;
+
+/**
+ * @see https://documentation.mailgun.com/en/latest/api-suppressions.html#complaints
+ *
+ * @author Sean Johnson <sean@mailgun.com>
+ */
+class Complaint extends HttpApi
+{
+    use Pagination;
+
+    /**
+     * @param  string                   $domain         Domain to get complaints for
+     * @param  int                      $limit          optional
+     * @param  array                    $requestHeaders
+     * @return IndexResponse
+     * @throws ClientExceptionInterface
+     */
+    public function index(string $domain, int $limit = 100, array $requestHeaders = [])
+    {
+        Assert::stringNotEmpty($domain);
+        Assert::range($limit, 1, 10000, 'Limit parameter must be between 1 and 10000');
+
+        $params = [
+            'limit' => $limit,
+        ];
+
+        $response = $this->httpGet(sprintf('/v3/%s/complaints', $domain), $params, $requestHeaders);
+
+        return $this->hydrateResponse($response, IndexResponse::class);
+    }
+
+    /**
+     * @param string $domain  Domain to show complaint for
+     * @param string $address Complaint address
+     *
+     * @return ShowResponse
+     */
+    public function show(string $domain, string $address, array $requestHeaders = [])
+    {
+        Assert::stringNotEmpty($domain);
+        Assert::stringNotEmpty($address);
+        $response = $this->httpGet(sprintf('/v3/%s/complaints/%s', $domain, $address), [], $requestHeaders);
+
+        return $this->hydrateResponse($response, ShowResponse::class);
+    }
+
+    /**
+     * @param  string                   $domain         Domain to create complaint for
+     * @param  string                   $address        Complaint address
+     * @param  string|null              $createdAt      (optional) rfc2822 compliant format. (new \DateTime())->format('r')
+     * @param  array                    $requestHeaders
+     * @return CreateResponse
+     * @throws ClientExceptionInterface
+     */
+    public function create(string $domain, string $address, ?string $createdAt = null, array $requestHeaders = [])
+    {
+        Assert::stringNotEmpty($domain);
+        Assert::stringNotEmpty($address);
+
+        $params['address'] = $address;
+        if (null !== $createdAt) {
+            Assert::stringNotEmpty($createdAt);
+            $params['created_at'] = $createdAt;
+        }
+
+        $response = $this->httpPost(sprintf('/v3/%s/complaints', $domain), $params, $requestHeaders);
+
+        return $this->hydrateResponse($response, CreateResponse::class);
+    }
+
+    /**
+     * @param  string                   $domain         Domain to delete complaint for
+     * @param  string                   $address        Complaint address
+     * @param  array                    $requestHeaders
+     * @return DeleteResponse
+     * @throws ClientExceptionInterface
+     */
+    public function delete(string $domain, string $address, array $requestHeaders = [])
+    {
+        Assert::stringNotEmpty($domain);
+        Assert::stringNotEmpty($address);
+
+        $response = $this->httpDelete(sprintf('/v3/%s/complaints/%s', $domain, $address), [], $requestHeaders);
+
+        return $this->hydrateResponse($response, DeleteResponse::class);
+    }
+
+    /**
+     * @param  string                   $domain         Domain to delete all bounces for
+     * @param  array                    $requestHeaders
+     * @return DeleteResponse
+     * @throws ClientExceptionInterface
+     */
+    public function deleteAll(string $domain, array $requestHeaders = [])
+    {
+        Assert::stringNotEmpty($domain);
+
+        $response = $this->httpDelete(sprintf('/v3/%s/complaints', $domain), [], $requestHeaders);
+
+        return $this->hydrateResponse($response, DeleteResponse::class);
+    }
+
+    /**
+     * @param string $domainId
+     * @param array $complaints
+     * @param array $requestHeaders
+     * @return mixed
+     */
+    public function importComplaints(string $domainId, array $complaints, array $requestHeaders = [])
+    {
+        try {
+            $response = $this->httpPostRaw(
+                sprintf('/v3/%s/complaints/import', $domainId),
+                $complaints,
+                $requestHeaders
+            );
+        } catch (Throwable $throwable) {
+            throw new RuntimeException($throwable->getMessage());
+        }
+
+        return $this->hydrateResponse($response, ShowResponse::class);
+    }
+}

+ 151 - 0
vendor/mailgun/mailgun-php/src/Api/Suppression/Unsubscribe.php

@@ -0,0 +1,151 @@
+<?php
+
+declare(strict_types=1);
+
+/*
+ * Copyright (C) 2013 Mailgun
+ *
+ * This software may be modified and distributed under the terms
+ * of the MIT license. See the LICENSE file for details.
+ */
+
+namespace Mailgun\Api\Suppression;
+
+use Mailgun\Api\HttpApi;
+use Mailgun\Api\Pagination;
+use Mailgun\Assert;
+use Mailgun\Model\Suppression\Unsubscribe\CreateResponse;
+use Mailgun\Model\Suppression\Unsubscribe\DeleteResponse;
+use Mailgun\Model\Suppression\Unsubscribe\IndexResponse;
+use Mailgun\Model\Suppression\Unsubscribe\ShowResponse;
+use Psr\Http\Client\ClientExceptionInterface;
+
+/**
+ * @see https://documentation.mailgun.com/en/latest/api-suppressions.html#unsubscribes
+ *
+ * @author Sean Johnson <sean@mailgun.com>
+ */
+class Unsubscribe extends HttpApi
+{
+    use Pagination;
+
+    /**
+     * @param  string                   $domain         Domain to get unsubscribes for
+     * @param  int                      $limit          optional
+     * @param  array                    $requestHeaders
+     * @return IndexResponse
+     * @throws ClientExceptionInterface
+     */
+    public function index(string $domain, int $limit = 100, array $requestHeaders = [])
+    {
+        Assert::stringNotEmpty($domain);
+        Assert::range($limit, 1, 10000, 'Limit parameter must be between 1 and 10000');
+
+        $params = [
+            'limit' => $limit,
+        ];
+
+        $response = $this->httpGet(sprintf('/v3/%s/unsubscribes', $domain), $params, $requestHeaders);
+
+        return $this->hydrateResponse($response, IndexResponse::class);
+    }
+
+    /**
+     * @param  string                   $domain         Domain to show unsubscribe for
+     * @param  string                   $address        Unsubscribe address
+     * @param  array                    $requestHeaders
+     * @return ShowResponse
+     * @throws ClientExceptionInterface
+     */
+    public function show(string $domain, string $address, array $requestHeaders = [])
+    {
+        Assert::stringNotEmpty($domain);
+        Assert::stringNotEmpty($address);
+
+        $response = $this->httpGet(sprintf('/v3/%s/unsubscribes/%s', $domain, $address), [], $requestHeaders);
+
+        return $this->hydrateResponse($response, ShowResponse::class);
+    }
+
+    /**
+     * @param  string                   $domain         Domain to create unsubscribe for
+     * @param  string                   $address        Unsubscribe address
+     * @param  array                    $params         optional
+     * @param  array                    $requestHeaders
+     * @return CreateResponse
+     * @throws ClientExceptionInterface
+     */
+    public function create(string $domain, string $address, array $params = [], array $requestHeaders = [])
+    {
+        Assert::stringNotEmpty($domain);
+        Assert::stringNotEmpty($address);
+
+        $params['address'] = $address;
+
+        $response = $this->httpPost(sprintf('/v3/%s/unsubscribes', $domain), $params, $requestHeaders);
+
+        return $this->hydrateResponse($response, CreateResponse::class);
+    }
+
+    /**
+     * @param  string                   $domain         Domain to delete unsubscribe for
+     * @param  string                   $address        Unsubscribe address
+     * @param  string|null              $tag            Unsubscribe tag
+     * @param  array                    $requestHeaders
+     * @return DeleteResponse
+     * @throws ClientExceptionInterface
+     */
+    public function delete(string $domain, string $address, ?string $tag = null, array $requestHeaders = [])
+    {
+        Assert::stringNotEmpty($domain);
+        Assert::stringNotEmpty($address);
+        Assert::nullOrStringNotEmpty($tag);
+
+        $params = [];
+        if (!is_null($tag)) {
+            $params['tag'] = $tag;
+        }
+
+        $response = $this->httpDelete(sprintf('/v3/%s/unsubscribes/%s', $domain, $address), $params, $requestHeaders);
+
+        return $this->hydrateResponse($response, DeleteResponse::class);
+    }
+
+    /**
+     * @param  string                   $domain         Domain to delete all unsubscribes for
+     * @param  array                    $requestHeaders
+     * @return DeleteResponse
+     * @throws ClientExceptionInterface
+     */
+    public function deleteAll(string $domain, array $requestHeaders = [])
+    {
+        Assert::stringNotEmpty($domain);
+
+        $response = $this->httpDelete(sprintf('/v3/%s/unsubscribes', $domain), [], $requestHeaders);
+
+        return $this->hydrateResponse($response, DeleteResponse::class);
+    }
+
+    /**
+     * @param string $domain
+     * @param string $filePath
+     * @return mixed
+     * @throws ClientExceptionInterface
+     */
+    public function import(string $domain, string $filePath)
+    {
+        Assert::stringNotEmpty($domain);
+        Assert::stringNotEmpty($filePath);
+        Assert::fileExists($filePath);
+
+        $response = $this->httpPost(
+            sprintf('/v3/%s/unsubscribes/import', $domain),
+            ['file' => fopen($filePath, 'r')],
+            [
+                'filename' => basename($filePath),
+            ]
+        );
+
+        return $this->hydrateResponse($response, ShowResponse::class);
+    }
+}

+ 154 - 0
vendor/mailgun/mailgun-php/src/Api/Suppression/Whitelist.php

@@ -0,0 +1,154 @@
+<?php
+
+declare(strict_types=1);
+
+/*
+ * Copyright (C) 2013 Mailgun
+ *
+ * This software may be modified and distributed under the terms
+ * of the MIT license. See the LICENSE file for details.
+ */
+
+namespace Mailgun\Api\Suppression;
+
+use InvalidArgumentException;
+use Mailgun\Api\HttpApi;
+use Mailgun\Api\Pagination;
+use Mailgun\Assert;
+use Mailgun\Model\Suppression\Whitelist\CreateResponse;
+use Mailgun\Model\Suppression\Whitelist\DeleteAllResponse;
+use Mailgun\Model\Suppression\Whitelist\DeleteResponse;
+use Mailgun\Model\Suppression\Whitelist\ImportResponse;
+use Mailgun\Model\Suppression\Whitelist\IndexResponse;
+use Mailgun\Model\Suppression\Whitelist\ShowResponse;
+use Psr\Http\Client\ClientExceptionInterface;
+
+/**
+ * @see https://documentation.mailgun.com/en/latest/api-suppressions.html#whitelists
+ *
+ * @author Artem Bondarenko <artem@uartema.com>
+ */
+class Whitelist extends HttpApi
+{
+    use Pagination;
+
+    /**
+     * @param  string                   $domain         Domain to get whitelist for
+     * @param  int                      $limit          optional
+     * @param  array                    $requestHeaders
+     * @return IndexResponse
+     * @throws ClientExceptionInterface
+     */
+    public function index(string $domain, int $limit = 100, array $requestHeaders = [])
+    {
+        Assert::stringNotEmpty($domain);
+        Assert::range($limit, 1, 10000, 'Limit parameter must be between 1 and 10000');
+
+        $params = [
+            'limit' => $limit,
+        ];
+
+        $response = $this->httpGet(sprintf('/v3/%s/whitelists', $domain), $params, $requestHeaders);
+
+        return $this->hydrateResponse($response, IndexResponse::class);
+    }
+
+    /**
+     * @param  string                   $domain         Domain to show whitelist for
+     * @param  string                   $address        whitelist address
+     * @param  array                    $requestHeaders
+     * @return ShowResponse
+     * @throws ClientExceptionInterface
+     */
+    public function show(string $domain, string $address, array $requestHeaders = [])
+    {
+        Assert::stringNotEmpty($domain);
+        Assert::stringNotEmpty($address);
+
+        $response = $this->httpGet(sprintf('/v3/%s/whitelists/%s', $domain, $address), [], $requestHeaders);
+
+        return $this->hydrateResponse($response, ShowResponse::class);
+    }
+
+    /**
+     * @param  string                   $domain         Domain to create whitelist for
+     * @param  string                   $address        whitelist email address or domain name
+     * @param  array                    $requestHeaders
+     * @return CreateResponse
+     * @throws ClientExceptionInterface
+     */
+    public function create(string $domain, string $address, array $requestHeaders = [])
+    {
+        Assert::stringNotEmpty($domain);
+        Assert::stringNotEmpty($address);
+
+        $params = [];
+
+        if (false !== filter_var($address, FILTER_VALIDATE_EMAIL)) {
+            $params['address'] = $address;
+        } elseif (false !== filter_var($address, FILTER_VALIDATE_DOMAIN, FILTER_FLAG_HOSTNAME)) {
+            $params['domain'] = $address;
+        } else {
+            throw new InvalidArgumentException('Address should be valid email or domain name');
+        }
+
+        $response = $this->httpPost(sprintf('/v3/%s/whitelists', $domain), $params, $requestHeaders);
+
+        return $this->hydrateResponse($response, CreateResponse::class);
+    }
+
+    /**
+     * @param string $domain   Domain to create whitelist for
+     * @param string $filePath csv file path
+     *
+     * @return ImportResponse
+     */
+    public function import(string $domain, string $filePath)
+    {
+        Assert::stringNotEmpty($domain);
+        Assert::stringNotEmpty($filePath);
+        Assert::fileExists($filePath);
+
+        $response = $this->httpPost(
+            sprintf('/v3/%s/whitelists/import', $domain),
+            ['file' => fopen($filePath, 'r')],
+            [
+                'filename' => basename($filePath),
+            ]
+        );
+
+        return $this->hydrateResponse($response, ImportResponse::class);
+    }
+
+    /**
+     * @param  string                   $domain         Domain to delete whitelist for
+     * @param  string                   $address        whitelist address
+     * @param  array                    $requestHeaders
+     * @return DeleteResponse
+     * @throws ClientExceptionInterface
+     */
+    public function delete(string $domain, string $address, array $requestHeaders = [])
+    {
+        Assert::stringNotEmpty($domain);
+        Assert::stringNotEmpty($address);
+
+        $response = $this->httpDelete(sprintf('/v3/%s/whitelists/%s', $domain, $address), [], $requestHeaders);
+
+        return $this->hydrateResponse($response, DeleteResponse::class);
+    }
+
+    /**
+     * @param  string                   $domain         Domain to delete all whitelists for
+     * @param  array                    $requestHeaders
+     * @return DeleteAllResponse
+     * @throws ClientExceptionInterface
+     */
+    public function deleteAll(string $domain, array $requestHeaders = [])
+    {
+        Assert::stringNotEmpty($domain);
+
+        $response = $this->httpDelete(sprintf('/v3/%s/whitelists', $domain), [], $requestHeaders);
+
+        return $this->hydrateResponse($response, DeleteAllResponse::class);
+    }
+}

+ 200 - 0
vendor/mailgun/mailgun-php/src/Api/Tag.php

@@ -0,0 +1,200 @@
+<?php
+
+declare(strict_types=1);
+
+/*
+ * Copyright (C) 2013 Mailgun
+ *
+ * This software may be modified and distributed under the terms
+ * of the MIT license. See the LICENSE file for details.
+ */
+
+namespace Mailgun\Api;
+
+use Mailgun\Assert;
+use Mailgun\Model\Tag\CountryResponse;
+use Mailgun\Model\Tag\DeleteResponse;
+use Mailgun\Model\Tag\DeviceResponse;
+use Mailgun\Model\Tag\IndexResponse;
+use Mailgun\Model\Tag\ProviderResponse;
+use Mailgun\Model\Tag\ShowResponse;
+use Mailgun\Model\Tag\StatisticsResponse;
+use Mailgun\Model\Tag\TagLimitResponse;
+use Mailgun\Model\Tag\UpdateResponse;
+use Psr\Http\Client\ClientExceptionInterface;
+use Psr\Http\Message\ResponseInterface;
+
+/**
+ * @see https://documentation.mailgun.com/en/latest/api-tags.html
+ *
+ * @author Tobias Nyholm <tobias.nyholm@gmail.com>
+ */
+class Tag extends HttpApi
+{
+    use Pagination;
+
+    /**
+     * Returns a list of tags.
+     * @param  string                          $domain
+     * @param  int                             $limit
+     * @param  array                           $requestHeaders
+     * @return IndexResponse|ResponseInterface
+     * @throws ClientExceptionInterface
+     */
+    public function index(string $domain, int $limit = 100, array $requestHeaders = [])
+    {
+        Assert::stringNotEmpty($domain);
+        Assert::range($limit, 1, 1000);
+
+        $params = [
+            'limit' => $limit,
+        ];
+
+        $response = $this->httpGet(sprintf('/v3/%s/tags', $domain), $params, $requestHeaders);
+
+        return $this->hydrateResponse($response, IndexResponse::class);
+    }
+
+    /**
+     * Returns a single tag.
+     * @param  string                         $domain
+     * @param  string                         $tag
+     * @param  array                          $requestHeaders
+     * @return ShowResponse|ResponseInterface
+     * @throws ClientExceptionInterface
+     */
+    public function show(string $domain, string $tag, array $requestHeaders = [])
+    {
+        Assert::stringNotEmpty($domain);
+        Assert::stringNotEmpty($tag);
+
+        $response = $this->httpGet(sprintf('/v3/%s/tags/%s', $domain, $tag), [], $requestHeaders);
+
+        return $this->hydrateResponse($response, ShowResponse::class);
+    }
+
+    /**
+     * Update a tag.
+     * @param  string                           $domain
+     * @param  string                           $tag
+     * @param  string                           $description
+     * @param  array                            $requestHeaders
+     * @return UpdateResponse|ResponseInterface
+     * @throws ClientExceptionInterface
+     */
+    public function update(string $domain, string $tag, string $description, array $requestHeaders = [])
+    {
+        Assert::stringNotEmpty($domain);
+        Assert::stringNotEmpty($tag);
+
+        $params = [
+            'description' => $description,
+        ];
+
+        $response = $this->httpPut(sprintf('/v3/%s/tags/%s', $domain, $tag), $params, $requestHeaders);
+
+        return $this->hydrateResponse($response, UpdateResponse::class);
+    }
+
+    /**
+     * Returns statistics for a single tag.
+     * @param string $domain
+     * @param array $params
+     * @param array $requestHeaders
+     * @return StatisticsResponse|ResponseInterface
+     * @throws ClientExceptionInterface
+     */
+    public function stats(string $domain, array $params, array $requestHeaders = [])
+    {
+        Assert::stringNotEmpty($domain);
+        Assert::stringNotEmpty($params['tag']);
+
+        $response = $this->httpGet(sprintf('/v3/%s/tag/stats', $domain), $params, $requestHeaders);
+
+        return $this->hydrateResponse($response, StatisticsResponse::class);
+    }
+
+    /**
+     * Removes a tag from the account.
+     * @param  string                           $domain
+     * @param  string                           $tag
+     * @param  array                            $requestHeaders
+     * @return DeleteResponse|ResponseInterface
+     * @throws ClientExceptionInterface
+     */
+    public function delete(string $domain, string $tag, array $requestHeaders = [])
+    {
+        Assert::stringNotEmpty($domain);
+        Assert::stringNotEmpty($tag);
+
+        $response = $this->httpDelete(sprintf('/v3/%s/tag', $domain), ['tag' => $tag], $requestHeaders);
+
+        return $this->hydrateResponse($response, DeleteResponse::class);
+    }
+
+    /**
+     * @param  string                            $domain
+     * @param  string                            $tag
+     * @param  array                             $requestHeaders
+     * @return CountryResponse|ResponseInterface
+     * @throws ClientExceptionInterface
+     */
+    public function countries(string $domain, string $tag, array $requestHeaders = [])
+    {
+        Assert::stringNotEmpty($domain);
+        Assert::stringNotEmpty($tag);
+
+        $response = $this->httpGet(sprintf('/v3/%s/tags/%s/stats/aggregates/countries', $domain, $tag), [], $requestHeaders);
+
+        return $this->hydrateResponse($response, CountryResponse::class);
+    }
+
+    /**
+     * @param  string                             $domain
+     * @param  string                             $tag
+     * @param  array                              $requestHeaders
+     * @return ProviderResponse|ResponseInterface
+     * @throws ClientExceptionInterface
+     */
+    public function providers(string $domain, string $tag, array $requestHeaders = [])
+    {
+        Assert::stringNotEmpty($domain);
+        Assert::stringNotEmpty($tag);
+
+        $response = $this->httpGet(sprintf('/v3/%s/tags/%s/stats/aggregates/providers', $domain, $tag), [], $requestHeaders);
+
+        return $this->hydrateResponse($response, ProviderResponse::class);
+    }
+
+    /**
+     * @param  string                           $domain
+     * @param  string                           $tag
+     * @param  array                            $requestHeaders
+     * @return DeviceResponse|ResponseInterface
+     * @throws ClientExceptionInterface
+     */
+    public function devices(string $domain, string $tag, array $requestHeaders = [])
+    {
+        Assert::stringNotEmpty($domain);
+        Assert::stringNotEmpty($tag);
+
+        $response = $this->httpGet(sprintf('/v3/%s/tags/%s/stats/aggregates/devices', $domain, $tag), [], $requestHeaders);
+
+        return $this->hydrateResponse($response, DeviceResponse::class);
+    }
+
+    /**
+     * @param string $domain
+     * @param array $requestHeaders
+     * @return TagLimitResponse
+     * @throws ClientExceptionInterface
+     */
+    public function getTagLimits(string $domain, array $requestHeaders = []): TagLimitResponse
+    {
+        Assert::stringNotEmpty($domain);
+
+        $response = $this->httpGet(sprintf('/v3/domains/%s/limits/tag', $domain), [], $requestHeaders);
+
+        return $this->hydrateResponse($response, TagLimitResponse::class);
+    }
+}

+ 151 - 0
vendor/mailgun/mailgun-php/src/Api/Templates.php

@@ -0,0 +1,151 @@
+<?php
+
+declare(strict_types=1);
+
+/*
+ * Copyright (C) 2013 Mailgun
+ *
+ * This software may be modified and distributed under the terms
+ * of the MIT license. See the LICENSE file for details.
+ */
+
+namespace Mailgun\Api;
+
+use Exception;
+use Mailgun\Assert;
+use Mailgun\Model\Templates\CreateResponse;
+use Mailgun\Model\Templates\IndexResponse;
+use Mailgun\Model\Templates\ShowResponse;
+use Psr\Http\Client\ClientExceptionInterface;
+use Psr\Http\Message\ResponseInterface;
+
+/**
+ * @see https://documentation.mailgun.com/docs/mailgun/api-reference/openapi-final/tag/Templates/#tag/Templates/operation/httpapi.(*TemplateAPIControler).GetPage-fm-9
+ *
+ * @author Sean Johnson <sean@mailgun.com>
+ */
+class Templates extends HttpApi
+{
+    private const PAGE_NEXT = 'next';
+    private const PAGE_FIRST = 'first';
+    private const PAGE_PREVIOUS = 'previous';
+    private const PAGE_LAST = 'last';
+
+    /**
+     * @param  string                          $domain
+     * @param  int                             $limit
+     * @param  string                          $page
+     * @param  string                          $pivot
+     * @param  array                           $requestHeaders
+     * @return IndexResponse|ResponseInterface
+     * @throws ClientExceptionInterface
+     * @throws Exception
+     */
+    public function index(string $domain, int $limit, string $page, string $pivot, array $requestHeaders = [])
+    {
+        Assert::inArray($page, [self::PAGE_LAST, self::PAGE_FIRST, self::PAGE_PREVIOUS, self::PAGE_NEXT]);
+
+        $params = [
+            'limit' => $limit,
+            'page' => $page,
+            'p' => $pivot,
+        ];
+
+        $response = $this->httpGet(sprintf('/v3/%s/templates', $domain), $params, $requestHeaders);
+
+        return $this->hydrateResponse($response, IndexResponse::class);
+    }
+
+    /**
+     * @param  string                   $domain
+     * @param  string                   $templateId
+     * @param  array                    $requestHeaders
+     * @return mixed|ResponseInterface
+     * @throws ClientExceptionInterface
+     * @throws Exception
+     */
+    public function show(string $domain, string $templateId, array $requestHeaders = [])
+    {
+        Assert::notEmpty($domain);
+        Assert::notEmpty($templateId);
+
+        $response = $this->httpGet(sprintf('/v3/%s/templates/%s', $domain, $templateId), [], $requestHeaders);
+
+        return $this->hydrateResponse($response, ShowResponse::class);
+    }
+
+    /**
+     * @param  string                           $domain
+     * @param  string                           $name
+     * @param  string|null                      $template
+     * @param  array|null                       $headers
+     * @param  string|null                      $tag
+     * @param  string|null                      $comment
+     * @param  string|null                      $createdBy
+     * @param  string|null                      $description
+     * @param  string|null                      $engine
+     * @param  array                            $requestHeaders
+     * @return CreateResponse|ResponseInterface
+     * @throws ClientExceptionInterface
+     * @throws Exception
+     */
+    public function create(
+        string $domain,
+        string $name,
+        ?string $template = null,
+        ?array $headers = null,
+        ?string $tag = null,
+        ?string $comment = null,
+        ?string $createdBy = null,
+        ?string $description = null,
+        ?string $engine = null,
+        array $requestHeaders = []
+    ) {
+        Assert::stringNotEmpty($domain);
+        Assert::stringNotEmpty($name);
+
+        $body = [
+            'name' => $name,
+        ];
+
+        if (!empty($template)) {
+            $body['template'] = $template;
+        }
+        if (!empty($tag)) {
+            $body['tag'] = $tag;
+        }
+        if (!empty($comment)) {
+            $body['comment'] = $comment;
+        }
+        if (!empty($createdBy)) {
+            $body['createdBy'] = $createdBy;
+        }
+        if (!empty($headers)) {
+            $body['headers'] = json_encode($headers);
+        }
+        if (!empty($description)) {
+            $body['description'] = $description;
+        }
+        if (!empty($engine)) {
+            $body['engine'] = $engine;
+        }
+
+        $response = $this->httpPost(sprintf('/v3/%s/templates', $domain), $body, $requestHeaders);
+
+        return $this->hydrateResponse($response, CreateResponse::class);
+    }
+
+    /**
+     * @param  string                   $domain
+     * @param  string                   $templateName
+     * @param  array                    $requestHeaders
+     * @return mixed|ResponseInterface
+     * @throws ClientExceptionInterface
+     */
+    public function deleteTemplate(string $domain, string $templateName, array $requestHeaders = [])
+    {
+        $response = $this->httpDelete(sprintf('/v3/%s/templates/%s', $domain, $templateName), [], $requestHeaders);
+
+        return $this->hydrateResponse($response, ShowResponse::class);
+    }
+}

+ 169 - 0
vendor/mailgun/mailgun-php/src/Api/Webhook.php

@@ -0,0 +1,169 @@
+<?php
+
+declare(strict_types=1);
+
+/*
+ * Copyright (C) 2013 Mailgun
+ *
+ * This software may be modified and distributed under the terms
+ * of the MIT license. See the LICENSE file for details.
+ */
+
+namespace Mailgun\Api;
+
+use Mailgun\Assert;
+use Mailgun\HttpClient\RequestBuilder;
+use Mailgun\Hydrator\Hydrator;
+use Mailgun\Model\Webhook\CreateResponse;
+use Mailgun\Model\Webhook\DeleteResponse;
+use Mailgun\Model\Webhook\IndexResponse;
+use Mailgun\Model\Webhook\ShowResponse;
+use Mailgun\Model\Webhook\UpdateResponse;
+use Psr\Http\Client\ClientExceptionInterface;
+use Psr\Http\Client\ClientInterface;
+use Psr\Http\Message\ResponseInterface;
+
+/**
+ * @see https://documentation.mailgun.com/en/latest/api-webhooks.html
+ *
+ * @author Tobias Nyholm <tobias.nyholm@gmail.com>
+ */
+class Webhook extends HttpApi
+{
+    /**
+     * @var string
+     */
+    private $apiKey;
+
+    /**
+     * @param ClientInterface $httpClient
+     * @param RequestBuilder  $requestBuilder
+     * @param Hydrator        $hydrator
+     * @param string          $apiKey
+     */
+    public function __construct($httpClient, RequestBuilder $requestBuilder, Hydrator $hydrator, string $apiKey)
+    {
+        parent::__construct($httpClient, $requestBuilder, $hydrator);
+        $this->apiKey = $apiKey;
+    }
+
+    /**
+     * This function verifies the webhook signature with your API key to to see if it is authentic.
+     * If this function returns FALSE, you must not process the request.
+     * You should reject the request with status code 403 Forbidden.
+     *
+     * @param  int    $timestamp
+     * @param  string $token
+     * @param  string $signature
+     * @return bool
+     */
+    public function verifyWebhookSignature(int $timestamp, string $token, string $signature): bool
+    {
+        if (empty($timestamp) || empty($token) || empty($signature)) {
+            return false;
+        }
+
+        $hmac = hash_hmac('sha256', $timestamp.$token, $this->apiKey);
+
+        if (function_exists('hash_equals')) {
+            // hash_equals is constant time, but will not be introduced until PHP 5.6
+            return hash_equals($hmac, $signature);
+        }
+
+        return $hmac === $signature;
+    }
+
+    /**
+     * @param  string                          $domain
+     * @param  array                           $requestHeaders
+     * @return IndexResponse|ResponseInterface
+     * @throws ClientExceptionInterface
+     */
+    public function index(string $domain, array $requestHeaders = [])
+    {
+        Assert::notEmpty($domain);
+        $response = $this->httpGet(sprintf('/v3/domains/%s/webhooks', $domain), [], $requestHeaders);
+
+        return $this->hydrateResponse($response, IndexResponse::class);
+    }
+
+    /**
+     * @param  string                         $domain
+     * @param  string                         $webhook
+     * @param  array                          $requestHeaders
+     * @return ShowResponse|ResponseInterface
+     * @throws ClientExceptionInterface
+     */
+    public function show(string $domain, string $webhook, array $requestHeaders = [])
+    {
+        Assert::notEmpty($domain);
+        Assert::notEmpty($webhook);
+        $response = $this->httpGet(sprintf('/v3/domains/%s/webhooks/%s', $domain, $webhook), [], $requestHeaders);
+
+        return $this->hydrateResponse($response, ShowResponse::class);
+    }
+
+    /**
+     * @param  string                           $domain
+     * @param  string                           $id
+     * @param  array                            $url
+     * @param  array                            $requestHeaders
+     * @return CreateResponse|ResponseInterface
+     * @throws ClientExceptionInterface
+     */
+    public function create(string $domain, string $id, array $url, array $requestHeaders = [])
+    {
+        Assert::notEmpty($domain);
+        Assert::notEmpty($id);
+        Assert::notEmpty($url);
+
+        $params = [
+            'id' => $id,
+            'url' => $url,
+        ];
+
+        $response = $this->httpPost(sprintf('/v3/domains/%s/webhooks', $domain), $params, $requestHeaders);
+
+        return $this->hydrateResponse($response, CreateResponse::class);
+    }
+
+    /**
+     * @param  string                           $domain
+     * @param  string                           $id
+     * @param  array                            $url
+     * @param  array                            $requestHeaders
+     * @return UpdateResponse|ResponseInterface
+     * @throws ClientExceptionInterface
+     */
+    public function update(string $domain, string $id, array $url, array $requestHeaders = [])
+    {
+        Assert::notEmpty($domain);
+        Assert::notEmpty($id);
+        Assert::notEmpty($url);
+
+        $params = [
+            'url' => $url,
+        ];
+
+        $response = $this->httpPut(sprintf('/v3/domains/%s/webhooks/%s', $domain, $id), $params, $requestHeaders);
+
+        return $this->hydrateResponse($response, UpdateResponse::class);
+    }
+
+    /**
+     * @param  string                           $domain
+     * @param  string                           $id
+     * @param  array                            $requestHeaders
+     * @return DeleteResponse|ResponseInterface
+     * @throws ClientExceptionInterface
+     */
+    public function delete(string $domain, string $id, array $requestHeaders = [])
+    {
+        Assert::notEmpty($domain);
+        Assert::notEmpty($id);
+
+        $response = $this->httpDelete(sprintf('/v3/domains/%s/webhooks/%s', $domain, $id), [], $requestHeaders);
+
+        return $this->hydrateResponse($response, DeleteResponse::class);
+    }
+}

+ 33 - 0
vendor/mailgun/mailgun-php/src/Assert.php

@@ -0,0 +1,33 @@
+<?php
+
+declare(strict_types=1);
+
+/*
+ * Copyright (C) 2013 Mailgun
+ *
+ * This software may be modified and distributed under the terms
+ * of the MIT license. See the LICENSE file for details.
+ */
+
+namespace Mailgun;
+
+use Mailgun\Exception\InvalidArgumentException;
+
+/**
+ * We need to override Webmozart\Assert because we want to throw our own Exception.
+ *
+ * @author Tobias Nyholm <tobias.nyholm@gmail.com>
+ */
+final class Assert extends \Webmozart\Assert\Assert
+{
+    /**
+     * @psalm-pure this method is not supposed to perform side-effects
+     * @psalm-return never
+     * @param mixed $message
+     * @return void
+     */
+    protected static function reportInvalidArgument($message): void
+    {
+        throw new InvalidArgumentException($message);
+    }
+}

+ 21 - 0
vendor/mailgun/mailgun-php/src/Exception.php

@@ -0,0 +1,21 @@
+<?php
+
+declare(strict_types=1);
+
+/*
+ * Copyright (C) 2013 Mailgun
+ *
+ * This software may be modified and distributed under the terms
+ * of the MIT license. See the LICENSE file for details.
+ */
+
+namespace Mailgun;
+
+/**
+ * All Mailgun exception implements this exception.
+ *
+ * @author Tobias Nyholm <tobias.nyholm@gmail.com>
+ */
+interface Exception extends \Throwable
+{
+}

+ 174 - 0
vendor/mailgun/mailgun-php/src/Exception/HttpClientException.php

@@ -0,0 +1,174 @@
+<?php
+
+declare(strict_types=1);
+
+/*
+ * Copyright (C) 2013 Mailgun
+ *
+ * This software may be modified and distributed under the terms
+ * of the MIT license. See the LICENSE file for details.
+ */
+
+namespace Mailgun\Exception;
+
+use Mailgun\Exception;
+use Psr\Http\Message\ResponseInterface;
+use Throwable;
+
+/**
+ * @author Tobias Nyholm <tobias.nyholm@gmail.com>
+ */
+final class HttpClientException extends \RuntimeException implements Exception
+{
+    /**
+     * @var ResponseInterface|null
+     */
+    private $response;
+
+    /**
+     * @var array
+     */
+    private $responseBody = [];
+
+    /**
+     * @var int
+     */
+    private $responseCode;
+
+    public function __construct(string $message, int $code, ResponseInterface $response)
+    {
+        parent::__construct($message, $code);
+
+        $this->response = $response;
+        $this->responseCode = $response->getStatusCode();
+        $body = $response->getBody()->__toString();
+        if (0 !== strpos($response->getHeaderLine('Content-Type'), 'application/json')) {
+            $this->responseBody['message'] = $body;
+        } else {
+            $this->responseBody = json_decode($body, true);
+        }
+    }
+
+    /**
+     * @param  ResponseInterface   $response
+     * @return HttpClientException
+     */
+    public static function badRequest(ResponseInterface $response): HttpClientException
+    {
+        $body = $response->getBody()->__toString();
+        if (0 !== strpos($response->getHeaderLine('Content-Type'), 'application/json')) {
+            $validationMessage = $body;
+        } else {
+            $jsonDecoded = json_decode($body, true);
+            $validationMessage = isset($jsonDecoded['message']) ? $jsonDecoded['message'] : $body;
+        }
+
+        $message = sprintf("The parameters passed to the API were invalid. Check your inputs!\n\n%s", $validationMessage);
+
+        return new self($message, 400, $response);
+    }
+
+    /**
+     * @param  ResponseInterface   $response
+     * @return HttpClientException
+     */
+    public static function unauthorized(ResponseInterface $response): HttpClientException
+    {
+        return new self('Your credentials are incorrect.', 401, $response);
+    }
+
+    /**
+     * @param  ResponseInterface   $response
+     * @return HttpClientException
+     */
+    public static function requestFailed(ResponseInterface $response): HttpClientException
+    {
+        return new self('Parameters were valid but request failed. Try again.', 402, $response);
+    }
+
+    /**
+     * @param  ResponseInterface   $response
+     * @return HttpClientException
+     */
+    public static function notFound(ResponseInterface $response): HttpClientException
+    {
+        $serverMessage = [];
+        $defaultMessage = 'The endpoint you have tried to access does not exist. Check if the domain matches the domain you have configure on Mailgun.';
+        try {
+            $serverMessage = json_decode($response->getBody()->getContents(), true, 512, JSON_THROW_ON_ERROR);
+        } catch (Throwable $throwable) {
+        }
+
+        return new self($serverMessage['message'] ?? $defaultMessage, 404, $response);
+    }
+
+    /**
+     * @param  ResponseInterface   $response
+     * @return HttpClientException
+     */
+    public static function conflict(ResponseInterface $response): HttpClientException
+    {
+        return new self('Request conflicts with current state of the target resource.', 409, $response);
+    }
+
+    /**
+     * @param  ResponseInterface   $response
+     * @return HttpClientException
+     */
+    public static function payloadTooLarge(ResponseInterface $response): HttpClientException
+    {
+        return new self('Payload too large, your total attachment size is too big.', 413, $response);
+    }
+
+    /**
+     * @param  ResponseInterface   $response
+     * @return HttpClientException
+     */
+    public static function tooManyRequests(ResponseInterface $response): HttpClientException
+    {
+        return new self('Too many requests.', 429, $response);
+    }
+
+    /**
+     * @param  ResponseInterface   $response
+     * @return HttpClientException
+     */
+    public static function forbidden(ResponseInterface $response): HttpClientException
+    {
+        $body = $response->getBody()->__toString();
+        if (0 !== strpos($response->getHeaderLine('Content-Type'), 'application/json')) {
+            $validationMessage = $body;
+        } else {
+            $jsonDecoded = json_decode($body, true);
+            $validationMessage = isset($jsonDecoded['Error']) ? $jsonDecoded['Error'] : $body;
+        }
+
+        $message = sprintf("Forbidden!\n\n%s", $validationMessage);
+
+        return new self($message, 403, $response);
+    }
+
+    /**
+     * @return ResponseInterface|null
+     */
+    public function getResponse(): ?ResponseInterface
+    {
+        return $this->response;
+    }
+
+    /**
+     * @return array
+     */
+    public function getResponseBody(): array
+    {
+        return $this->responseBody;
+    }
+
+    /**
+     * @return int
+     */
+    public function getResponseCode(): int
+    {
+        return $this->responseCode;
+    }
+}

+ 47 - 0
vendor/mailgun/mailgun-php/src/Exception/HttpServerException.php

@@ -0,0 +1,47 @@
+<?php
+
+declare(strict_types=1);
+
+/*
+ * Copyright (C) 2013 Mailgun
+ *
+ * This software may be modified and distributed under the terms
+ * of the MIT license. See the LICENSE file for details.
+ */
+
+namespace Mailgun\Exception;
+
+use Mailgun\Exception;
+
+/**
+ * @author Tobias Nyholm <tobias.nyholm@gmail.com>
+ */
+final class HttpServerException extends \RuntimeException implements Exception
+{
+    /**
+     * @param  int                 $httpStatus
+     * @return HttpServerException
+     */
+    public static function serverError(int $httpStatus = 500): HttpServerException
+    {
+        return new self('An unexpected error occurred at Mailgun\'s servers. Try again later and contact support if the error still exists.', $httpStatus);
+    }
+
+    /**
+     * @param  \Throwable          $previous
+     * @return HttpServerException
+     */
+    public static function networkError(\Throwable $previous): HttpServerException
+    {
+        return new self('Mailgun\'s servers are currently unreachable.', 0, $previous);
+    }
+
+    /**
+     * @param  int                 $code
+     * @return HttpServerException
+     */
+    public static function unknownHttpResponseCode(int $code): HttpServerException
+    {
+        return new self(sprintf('Unknown HTTP response code ("%d") received from the API server', $code));
+    }
+}

+ 18 - 0
vendor/mailgun/mailgun-php/src/Exception/HydrationException.php

@@ -0,0 +1,18 @@
+<?php
+
+declare(strict_types=1);
+
+/*
+ * Copyright (C) 2013 Mailgun
+ *
+ * This software may be modified and distributed under the terms
+ * of the MIT license. See the LICENSE file for details.
+ */
+
+namespace Mailgun\Exception;
+
+use Mailgun\Exception;
+
+final class HydrationException extends \RuntimeException implements Exception
+{
+}

+ 21 - 0
vendor/mailgun/mailgun-php/src/Exception/InvalidArgumentException.php

@@ -0,0 +1,21 @@
+<?php
+
+declare(strict_types=1);
+
+/*
+ * Copyright (C) 2013 Mailgun
+ *
+ * This software may be modified and distributed under the terms
+ * of the MIT license. See the LICENSE file for details.
+ */
+
+namespace Mailgun\Exception;
+
+use Mailgun\Exception;
+
+/**
+ * @author Tobias Nyholm <tobias.nyholm@gmail.com>
+ */
+final class InvalidArgumentException extends \InvalidArgumentException implements Exception
+{
+}

+ 21 - 0
vendor/mailgun/mailgun-php/src/Exception/UnknownErrorException.php

@@ -0,0 +1,21 @@
+<?php
+
+declare(strict_types=1);
+
+/*
+ * Copyright (C) 2013 Mailgun
+ *
+ * This software may be modified and distributed under the terms
+ * of the MIT license. See the LICENSE file for details.
+ */
+
+namespace Mailgun\Exception;
+
+use Mailgun\Exception;
+
+/**
+ * @author Tobias Nyholm <tobias.nyholm@gmail.com>
+ */
+final class UnknownErrorException extends \Exception implements Exception
+{
+}

+ 217 - 0
vendor/mailgun/mailgun-php/src/HttpClient/HttpClientConfigurator.php

@@ -0,0 +1,217 @@
+<?php
+
+declare(strict_types=1);
+
+/*
+ * Copyright (C) 2013 Mailgun
+ *
+ * This software may be modified and distributed under the terms
+ * of the MIT license. See the LICENSE file for details.
+ */
+
+namespace Mailgun\HttpClient;
+
+use Composer\InstalledVersions;
+use Http\Client\Common\Plugin;
+use Http\Client\Common\PluginClient;
+use Http\Discovery\Psr17FactoryDiscovery;
+use Http\Discovery\Psr18ClientDiscovery;
+use Mailgun\HttpClient\Plugin\History;
+use Mailgun\HttpClient\Plugin\ReplaceUriPlugin;
+use Psr\Http\Client\ClientInterface;
+use Psr\Http\Message\UriFactoryInterface;
+
+/**
+ * Configure a HTTP client.
+ *
+ * @author Tobias Nyholm <tobias.nyholm@gmail.com>
+ */
+final class HttpClientConfigurator
+{
+    /**
+     * @var string
+     */
+    private $endpoint = 'https://api.mailgun.net';
+
+    /**
+     * If debug is true we will send all the request to the endpoint without appending any path.
+     *
+     * @var bool
+     */
+    private $debug = false;
+
+    /**
+     * @var string
+     */
+    private $apiKey;
+
+    /**
+     * @var UriFactoryInterface
+     */
+    private $uriFactory;
+
+    /**
+     * @var ClientInterface
+     */
+    private $httpClient;
+
+    /**
+     * @var History
+     */
+    private $responseHistory;
+
+    /**
+     * @var ?string
+     */
+    private $subAccountId;
+
+    public function __construct()
+    {
+        $this->responseHistory = new History();
+    }
+
+    /**
+     * @return PluginClient
+     */
+    public function createConfiguredClient(): PluginClient
+    {
+        $userAgent = InstalledVersions::getVersion('mailgun/mailgun-php');
+        if (!isset($userAgent) || !$userAgent) {
+            $userAgent = 'mailgun-sdk-php/v2 (https://github.com/mailgun/mailgun-php)';
+        }
+
+        $defaultPlugin = [
+            'User-Agent' => $userAgent,
+            'Authorization' => 'Basic '.base64_encode(sprintf('api:%s', $this->getApiKey())),
+        ];
+        if (null !== $this->getSubAccountId()) {
+            $defaultPlugin['X-Mailgun-On-Behalf-Of'] = $this->getSubAccountId();
+        }
+
+        $plugins = [
+            new Plugin\AddHostPlugin($this->getUriFactory()->createUri($this->endpoint)),
+            new Plugin\HeaderDefaultsPlugin($defaultPlugin),
+            new Plugin\HistoryPlugin($this->responseHistory),
+        ];
+
+        if ($this->debug) {
+            $plugins[] = new ReplaceUriPlugin($this->getUriFactory()->createUri($this->endpoint));
+        }
+
+        return new PluginClient($this->getHttpClient(), $plugins);
+    }
+
+    /**
+     * @param bool $debug
+     * @return $this
+     */
+    public function setDebug(bool $debug): self
+    {
+        $this->debug = $debug;
+
+        return $this;
+    }
+
+    /**
+     * @param string $endpoint
+     * @return $this
+     */
+    public function setEndpoint(string $endpoint): self
+    {
+        $this->endpoint = $endpoint;
+
+        return $this;
+    }
+
+    /**
+     * @return string
+     */
+    public function getApiKey(): string
+    {
+        return $this->apiKey;
+    }
+
+    /**
+     * @param string $apiKey
+     * @return $this
+     */
+    public function setApiKey(string $apiKey): self
+    {
+        $this->apiKey = $apiKey;
+
+        return $this;
+    }
+
+    /**
+     * @return UriFactoryInterface
+     */
+    private function getUriFactory(): UriFactoryInterface
+    {
+        if (null === $this->uriFactory) {
+            $this->uriFactory = Psr17FactoryDiscovery::findUrlFactory();
+        }
+
+        return $this->uriFactory;
+    }
+
+    /**
+     * @param UriFactoryInterface $uriFactory
+     * @return $this
+     */
+    public function setUriFactory(UriFactoryInterface $uriFactory): self
+    {
+        $this->uriFactory = $uriFactory;
+
+        return $this;
+    }
+
+    /**
+     * @return ClientInterface
+     */
+    private function getHttpClient(): ClientInterface
+    {
+        if (null === $this->httpClient) {
+            $this->httpClient = Psr18ClientDiscovery::find();
+        }
+
+        return $this->httpClient;
+    }
+
+    /**
+     * @param ClientInterface $httpClient
+     * @return $this
+     */
+    public function setHttpClient(ClientInterface $httpClient): self
+    {
+        $this->httpClient = $httpClient;
+
+        return $this;
+    }
+
+    /**
+     * @return History
+     */
+    public function getResponseHistory(): History
+    {
+        return $this->responseHistory;
+    }
+
+    /**
+     * @return string|null
+     */
+    public function getSubAccountId(): ?string
+    {
+        return $this->subAccountId;
+    }
+
+    /**
+     * @param  string|null            $subAccountId
+     * @return HttpClientConfigurator
+     */
+    public function setSubAccountId(?string $subAccountId): self
+    {
+        $this->subAccountId = $subAccountId;
+
+        return $this;
+    }
+}

+ 43 - 0
vendor/mailgun/mailgun-php/src/HttpClient/Plugin/History.php

@@ -0,0 +1,43 @@
+<?php
+
+declare(strict_types=1);
+
+/*
+ * Copyright (C) 2013 Mailgun
+ *
+ * This software may be modified and distributed under the terms
+ * of the MIT license. See the LICENSE file for details.
+ */
+
+namespace Mailgun\HttpClient\Plugin;
+
+use Http\Client\Common\Plugin\Journal;
+use Psr\Http\Message\RequestInterface;
+use Psr\Http\Message\ResponseInterface;
+
+/**
+ * A plugin to remember the last response.
+ *
+ * @author Tobias Nyholm <tobias.nyholm@gmail.com>
+ */
+final class History implements Journal
+{
+    use HistoryTrait;
+    /**
+     * @var ResponseInterface
+     */
+    private $lastResponse;
+
+    /**
+     * @return ResponseInterface|null
+     */
+    public function getLastResponse()
+    {
+        return $this->lastResponse;
+    }
+
+    public function addSuccess(RequestInterface $request, ResponseInterface $response)
+    {
+        $this->lastResponse = $response;
+    }
+}

+ 41 - 0
vendor/mailgun/mailgun-php/src/HttpClient/Plugin/HistoryTrait.php

@@ -0,0 +1,41 @@
+<?php
+
+declare(strict_types=1);
+
+/*
+ * Copyright (C) 2013 Mailgun
+ *
+ * This software may be modified and distributed under the terms
+ * of the MIT license. See the LICENSE file for details.
+ */
+
+namespace Mailgun\HttpClient\Plugin;
+
+use Http\Client\Exception;
+use Psr\Http\Client\ClientExceptionInterface;
+use Psr\Http\Message\RequestInterface;
+
+/*
+ * Below is a some code to make the History plugin compatible with both 1.x and 2.x of php-client/client-common
+ */
+if (\interface_exists(\Http\Client\Common\HttpMethodsClientInterface::class)) {
+    /**
+     * @internal code for php-http/client-common:2.x
+     */
+    trait HistoryTrait
+    {
+        public function addFailure(RequestInterface $request, ClientExceptionInterface $exception)
+        {
+        }
+    }
+} else {
+    /**
+     * @internal code for php-http/client-common:1.x
+     */
+    trait HistoryTrait
+    {
+        public function addFailure(RequestInterface $request, Exception $exception)
+        {
+        }
+    }
+}

+ 52 - 0
vendor/mailgun/mailgun-php/src/HttpClient/Plugin/ReplaceUriPlugin.php

@@ -0,0 +1,52 @@
+<?php
+
+declare(strict_types=1);
+
+/*
+ * Copyright (C) 2013 Mailgun
+ *
+ * This software may be modified and distributed under the terms
+ * of the MIT license. See the LICENSE file for details.
+ */
+
+namespace Mailgun\HttpClient\Plugin;
+
+use Http\Client\Common\Plugin;
+use Psr\Http\Message\RequestInterface;
+use Psr\Http\Message\UriInterface;
+
+/**
+ * Replaces a URI with a new one. Good for debugging.
+ *
+ * @author Tobias Nyholm <tobias.nyholm@gmail.com>
+ */
+final class ReplaceUriPlugin implements Plugin
+{
+    use Plugin\VersionBridgePlugin;
+
+    /**
+     * @var UriInterface
+     */
+    private $uri;
+
+    /**
+     * @param UriInterface $uri
+     */
+    public function __construct(UriInterface $uri)
+    {
+        $this->uri = $uri;
+    }
+
+    /**
+     * @param RequestInterface $request
+     * @param callable $next
+     * @param callable $first
+     * @return mixed
+     */
+    public function doHandleRequest(RequestInterface $request, callable $next, callable $first)
+    {
+        $request = $request->withUri($this->uri);
+
+        return $next($request);
+    }
+}

+ 190 - 0
vendor/mailgun/mailgun-php/src/HttpClient/RequestBuilder.php

@@ -0,0 +1,190 @@
+<?php
+
+declare(strict_types=1);
+
+/*
+ * Copyright (C) 2013 Mailgun
+ *
+ * This software may be modified and distributed under the terms
+ * of the MIT license. See the LICENSE file for details.
+ */
+
+namespace Mailgun\HttpClient;
+
+use Http\Discovery\Psr17FactoryDiscovery;
+use Http\Message\MultipartStream\MultipartStreamBuilder;
+use Psr\Http\Message\RequestFactoryInterface;
+use Psr\Http\Message\RequestInterface;
+use Psr\Http\Message\StreamFactoryInterface;
+use Psr\Http\Message\StreamInterface;
+
+/**
+ * @author Tobias Nyholm <tobias.nyholm@gmail.com>
+ */
+class RequestBuilder
+{
+    /**
+     * @var RequestFactoryInterface|null
+     */
+    private $requestFactory;
+
+    /**
+     * @var StreamFactoryInterface|null
+     */
+    private $streamFactory;
+
+    /**
+     * @var MultipartStreamBuilder
+     */
+    private $multipartStreamBuilder;
+
+    /**
+     * Creates a new PSR-7 request.
+     * @param string $method HTTP method
+     * @param string $uri URI
+     * @param array $headers Request headers
+     * @param array|string|null $body Request body. If body is an array we will send a as multipart stream request.
+     *                                If array, each array *item* MUST look like:
+     *                                array (
+     *                                'content' => string|resource|StreamInterface,
+     *                                'name'    => string,
+     *                                'filename'=> string (optional)
+     *                                'headers' => array (optinal) ['header-name' => 'header-value']
+     *                                )
+     * @throws \JsonException
+     */
+    public function create(string $method, string $uri, array $headers = [], $body = null): RequestInterface
+    {
+        if (!is_array($body)) {
+            $stream = $this->getStreamFactory()->createStream((string)$body);
+
+            return $this->createRequest($method, $uri, $headers, $stream);
+        }
+
+        if (isset($headers['Content-Type']) && $headers['Content-Type'] === 'application/json') {
+            $jsonBody = json_encode($body, JSON_THROW_ON_ERROR);
+            $stream = $this->getStreamFactory()->createStream($jsonBody);
+            $headers['Content-Type'] = 'application/json';
+
+            return $this->createRequest($method, $uri, $headers, $stream);
+        }
+
+        $builder = $this->getMultipartStreamBuilder();
+        foreach ($body as $item) {
+            $name = $this->getItemValue($item, 'name');
+            $content = $this->getItemValue($item, 'content');
+            unset($item['name'], $item['content']);
+
+            $builder->addResource($name, $content, $item);
+        }
+
+        $multipartStream = $builder->build();
+        $boundary = $builder->getBoundary();
+        $builder->reset();
+
+        $headers['Content-Type'] = 'multipart/form-data; boundary="' . $boundary . '"';
+
+        return $this->createRequest($method, $uri, $headers, $multipartStream);
+    }
+
+    /**
+     * @return RequestFactoryInterface
+     */
+    private function getRequestFactory(): RequestFactoryInterface
+    {
+        if (null === $this->requestFactory) {
+            $this->requestFactory = Psr17FactoryDiscovery::findRequestFactory();
+        }
+
+        return $this->requestFactory;
+    }
+
+    /**
+     * @param RequestFactoryInterface $requestFactory
+     * @return $this
+     */
+    public function setRequestFactory(RequestFactoryInterface $requestFactory): self
+    {
+        $this->requestFactory = $requestFactory;
+
+        return $this;
+    }
+
+    /**
+     * @return StreamFactoryInterface
+     */
+    private function getStreamFactory(): StreamFactoryInterface
+    {
+        if (null === $this->streamFactory) {
+            $this->streamFactory = Psr17FactoryDiscovery::findStreamFactory();
+        }
+
+        return $this->streamFactory;
+    }
+
+    /**
+     * @param StreamFactoryInterface $streamFactory
+     * @return $this
+     */
+    public function setStreamFactory(StreamFactoryInterface $streamFactory): self
+    {
+        $this->streamFactory = $streamFactory;
+
+        return $this;
+    }
+
+    /**
+     * @return MultipartStreamBuilder
+     */
+    private function getMultipartStreamBuilder(): MultipartStreamBuilder
+    {
+        if (null === $this->multipartStreamBuilder) {
+            $this->multipartStreamBuilder = new MultipartStreamBuilder();
+        }
+
+        return $this->multipartStreamBuilder;
+    }
+
+    /**
+     * @param MultipartStreamBuilder $multipartStreamBuilder
+     * @return $this
+     */
+    public function setMultipartStreamBuilder(MultipartStreamBuilder $multipartStreamBuilder): self
+    {
+        $this->multipartStreamBuilder = $multipartStreamBuilder;
+
+        return $this;
+    }
+
+    /**
+     * @param string $method
+     * @param string $uri
+     * @param array $headers
+     * @param StreamInterface $stream
+     * @return RequestInterface
+     */
+    private function createRequest(string $method, string $uri, array $headers, StreamInterface $stream): RequestInterface
+    {
+        $request = $this->getRequestFactory()->createRequest($method, $uri);
+        $request = $request->withBody($stream);
+        foreach ($headers as $name => $value) {
+            $request = $request->withAddedHeader($name, $value);
+        }
+
+        return $request;
+    }
+
+    /**
+     * @param array $item
+     * @param string $key
+     * @return mixed|string
+     */
+    private function getItemValue(array $item, string $key)
+    {
+        if (is_bool($item[$key])) {
+            return (string)$item[$key];
+        }
+
+        return $item[$key];
+    }
+}

+ 44 - 0
vendor/mailgun/mailgun-php/src/Hydrator/ArrayHydrator.php

@@ -0,0 +1,44 @@
+<?php
+
+declare(strict_types=1);
+
+/*
+ * Copyright (C) 2013 Mailgun
+ *
+ * This software may be modified and distributed under the terms
+ * of the MIT license. See the LICENSE file for details.
+ */
+
+namespace Mailgun\Hydrator;
+
+use Mailgun\Exception\HydrationException;
+use Psr\Http\Message\ResponseInterface;
+
+/**
+ * Serialize an HTTP response to array.
+ *
+ * @author Tobias Nyholm <tobias.nyholm@gmail.com>
+ */
+final class ArrayHydrator implements Hydrator
+{
+    /**
+     * @param  class-string   $class
+     * @return array
+     * @throws \JsonException
+     */
+    public function hydrate(ResponseInterface $response, string $class)
+    {
+        $body = $response->getBody()->__toString();
+        if (0 !== strpos($response->getHeaderLine('Content-Type'), 'application/json')) {
+            throw new HydrationException('The ArrayHydrator cannot hydrate response with Content-Type:'.$response->getHeaderLine('Content-Type'));
+        }
+
+        try {
+            $content = json_decode($body, true, 512, JSON_THROW_ON_ERROR);
+        } catch (\JsonException $exception) {
+            throw new HydrationException(sprintf('Error (%d) when trying to json_decode response: %s', $exception->getCode(), $exception->getMessage()));
+        }
+
+        return $content;
+    }
+}

+ 28 - 0
vendor/mailgun/mailgun-php/src/Hydrator/Hydrator.php

@@ -0,0 +1,28 @@
+<?php
+
+declare(strict_types=1);
+
+/*
+ * Copyright (C) 2013 Mailgun
+ *
+ * This software may be modified and distributed under the terms
+ * of the MIT license. See the LICENSE file for details.
+ */
+
+namespace Mailgun\Hydrator;
+
+use Mailgun\Exception\HydrationException;
+use Psr\Http\Message\ResponseInterface;
+
+/**
+ * Deserialize a PSR-7 response to something else.
+ */
+interface Hydrator
+{
+    /**
+     * @param class-string $class
+     *
+     * @throws HydrationException
+     */
+    public function hydrate(ResponseInterface $response, string $class);
+}

+ 63 - 0
vendor/mailgun/mailgun-php/src/Hydrator/ModelHydrator.php

@@ -0,0 +1,63 @@
+<?php
+
+declare(strict_types=1);
+
+/*
+ * Copyright (C) 2013 Mailgun
+ *
+ * This software may be modified and distributed under the terms
+ * of the MIT license. See the LICENSE file for details.
+ */
+
+namespace Mailgun\Hydrator;
+
+use Mailgun\Exception\HydrationException;
+use Mailgun\Model\ApiResponse;
+use Psr\Http\Message\ResponseInterface;
+
+/**
+ * Serialize an HTTP response to domain object.
+ *
+ * @author Tobias Nyholm <tobias.nyholm@gmail.com>
+ */
+final class ModelHydrator implements Hydrator
+{
+    /**
+     * @param  class-string      $class
+     * @return ResponseInterface
+     * @throws \JsonException
+     */
+    public function hydrate(ResponseInterface $response, string $class)
+    {
+        $body = $response->getBody()->__toString();
+        $contentType = $response->getHeaderLine('Content-Type');
+
+        if (0 !== strpos($contentType, 'application/json') && 0 !== strpos($contentType, 'application/octet-stream')) {
+            throw new HydrationException('The ModelHydrator cannot hydrate response with Content-Type: '.$contentType);
+        }
+
+        try {
+            $data = json_decode($body, true, 512, JSON_THROW_ON_ERROR);
+        } catch (\JsonException $exception) {
+            throw new HydrationException(sprintf('Error (%d) when trying to json_decode response: %s', $exception->getCode(), $exception->getMessage()));
+        }
+
+        if (is_subclass_of($class, ApiResponse::class)) {
+            $object = call_user_func([$class, 'create'], $data);
+        } else {
+            $object = new $class($data);
+        }
+
+        if (method_exists($object, 'setRawStream')) {
+            $object->setRawStream($response->getBody());
+        }
+        if (method_exists($object, 'setStatusCode')) {
+            $object->setStatusCode($response->getStatusCode());
+        }
+        if (method_exists($object, 'setHeaders')) {
+            $object->setHeaders($response->getHeaders());
+        }
+
+        return $object;
+    }
+}

+ 32 - 0
vendor/mailgun/mailgun-php/src/Hydrator/NoopHydrator.php

@@ -0,0 +1,32 @@
+<?php
+
+declare(strict_types=1);
+
+/*
+ * Copyright (C) 2013 Mailgun
+ *
+ * This software may be modified and distributed under the terms
+ * of the MIT license. See the LICENSE file for details.
+ */
+
+namespace Mailgun\Hydrator;
+
+use Psr\Http\Message\ResponseInterface;
+
+/**
+ * Do not serialize at all. Just return a PSR-7 response.
+ *
+ * @author Tobias Nyholm <tobias.nyholm@gmail.com>
+ */
+final class NoopHydrator implements Hydrator
+{
+    /**
+     * @param class-string $class
+     *
+     * @throws \LogicException
+     */
+    public function hydrate(ResponseInterface $response, string $class)
+    {
+        throw new \LogicException('The NoopHydrator should never be called');
+    }
+}

+ 284 - 0
vendor/mailgun/mailgun-php/src/Mailgun.php

@@ -0,0 +1,284 @@
+<?php
+
+declare(strict_types=1);
+
+/*
+ * Copyright (C) 2013 Mailgun
+ *
+ * This software may be modified and distributed under the terms
+ * of the MIT license. See the LICENSE file for details.
+ */
+
+namespace Mailgun;
+
+use Http\Client\Common\PluginClient;
+use Mailgun\Api\AccountManagement;
+use Mailgun\Api\Attachment;
+use Mailgun\Api\Domain;
+use Mailgun\Api\DomainKeys;
+use Mailgun\Api\EmailValidation;
+use Mailgun\Api\EmailValidationV4;
+use Mailgun\Api\Event;
+use Mailgun\Api\HttpClient;
+use Mailgun\Api\Ip;
+use Mailgun\Api\Mailboxes;
+use Mailgun\Api\MailingList;
+use Mailgun\Api\Message;
+use Mailgun\Api\Metrics;
+use Mailgun\Api\Route;
+use Mailgun\Api\Stats;
+use Mailgun\Api\SubAccounts;
+use Mailgun\Api\Suppression;
+use Mailgun\Api\Tag;
+use Mailgun\Api\Templates;
+use Mailgun\Api\Webhook;
+use Mailgun\HttpClient\HttpClientConfigurator;
+use Mailgun\HttpClient\Plugin\History;
+use Mailgun\HttpClient\RequestBuilder;
+use Mailgun\Hydrator\Hydrator;
+use Mailgun\Hydrator\ModelHydrator;
+use Psr\Http\Client\ClientInterface;
+use Psr\Http\Message\ResponseInterface;
+
+/**
+ * This class is the base class for the Mailgun SDK.
+ */
+class Mailgun
+{
+    /**
+     * @var string|null
+     */
+    private ?string $apiKey;
+
+    /**
+     * @var ClientInterface|PluginClient
+     */
+    private $httpClient;
+
+    /**
+     * @var Hydrator
+     */
+    private Hydrator $hydrator;
+
+    /**
+     * @var RequestBuilder
+     */
+    private RequestBuilder $requestBuilder;
+
+    /**
+     * This is a object that holds the last response from the API.
+     *
+     * @var History
+     */
+    private History $responseHistory;
+
+    /**
+     * @param HttpClientConfigurator $configurator
+     * @param Hydrator|null          $hydrator
+     * @param RequestBuilder|null    $requestBuilder
+     */
+    public function __construct(
+        HttpClientConfigurator $configurator,
+        ?Hydrator $hydrator = null,
+        ?RequestBuilder $requestBuilder = null
+    ) {
+        $this->requestBuilder = $requestBuilder ?: new RequestBuilder();
+        $this->hydrator = $hydrator ?: new ModelHydrator();
+
+        $this->httpClient = $configurator->createConfiguredClient();
+        $this->apiKey = $configurator->getApiKey();
+        $this->responseHistory = $configurator->getResponseHistory();
+    }
+
+    /**
+     * @param  string      $apiKey
+     * @param  string      $endpoint
+     * @param  string|null $subAccountId
+     * @return self
+     */
+    public static function create(string $apiKey, string $endpoint = 'https://api.mailgun.net', ?string $subAccountId = null): self
+    {
+        $httpClientConfigurator = (new HttpClientConfigurator())
+            ->setApiKey($apiKey)
+            ->setEndpoint($endpoint)
+            ->setSubAccountId($subAccountId);
+
+        return new self($httpClientConfigurator);
+    }
+
+    /**
+     * @return ResponseInterface|null
+     */
+    public function getLastResponse(): ?ResponseInterface
+    {
+        return $this->responseHistory->getLastResponse();
+    }
+
+    /**
+     * @return Attachment
+     */
+    public function attachment(): Api\Attachment
+    {
+        return new Api\Attachment($this->httpClient, $this->requestBuilder, $this->hydrator);
+    }
+
+    /**
+     * @return Domain
+     */
+    public function domains(): Api\Domain
+    {
+        return new Api\Domain($this->httpClient, $this->requestBuilder, $this->hydrator);
+    }
+
+    /**
+     * @return Api\DomainV4
+     */
+    public function domainsV4(): Api\DomainV4
+    {
+        return new Api\DomainV4($this->httpClient, $this->requestBuilder, $this->hydrator);
+    }
+
+    /**
+     * @return EmailValidation
+     */
+    public function emailValidation(): Api\EmailValidation
+    {
+        return new Api\EmailValidation($this->httpClient, $this->requestBuilder, $this->hydrator);
+    }
+
+    /**
+     * @return EmailValidationV4
+     */
+    public function emailValidationV4(): Api\EmailValidationV4
+    {
+        return new Api\EmailValidationV4($this->httpClient, $this->requestBuilder, $this->hydrator);
+    }
+
+    /**
+     * @return Event
+     */
+    public function events(): Api\Event
+    {
+        return new Api\Event($this->httpClient, $this->requestBuilder, $this->hydrator);
+    }
+
+    /**
+     * @return Ip
+     */
+    public function ips(): Api\Ip
+    {
+        return new Api\Ip($this->httpClient, $this->requestBuilder, $this->hydrator);
+    }
+
+    /**
+     * @return MailingList
+     */
+    public function mailingList(): Api\MailingList
+    {
+        return new Api\MailingList($this->httpClient, $this->requestBuilder, $this->hydrator);
+    }
+
+    /**
+     * @return Message
+     */
+    public function messages(): Api\Message
+    {
+        return new Api\Message($this->httpClient, $this->requestBuilder, $this->hydrator);
+    }
+
+    /**
+     * @return Route
+     */
+    public function routes(): Api\Route
+    {
+        return new Api\Route($this->httpClient, $this->requestBuilder, $this->hydrator);
+    }
+
+    /**
+     * @return Suppression
+     */
+    public function suppressions(): Api\Suppression
+    {
+        return new Api\Suppression($this->httpClient, $this->requestBuilder, $this->hydrator);
+    }
+
+    /**
+     * @return Stats
+     */
+    public function stats(): Api\Stats
+    {
+        return new Api\Stats($this->httpClient, $this->requestBuilder, $this->hydrator);
+    }
+
+    /**
+     * @return Tag
+     */
+    public function tags(): Api\Tag
+    {
+        return new Api\Tag($this->httpClient, $this->requestBuilder, $this->hydrator);
+    }
+
+    /**
+     * @return Webhook
+     */
+    public function webhooks(): Api\Webhook
+    {
+        return new Api\Webhook($this->httpClient, $this->requestBuilder, $this->hydrator, $this->apiKey ?? '');
+    }
+
+    /**
+     * @return Mailboxes
+     */
+    public function mailboxes(): Api\Mailboxes
+    {
+        return new Api\Mailboxes($this->httpClient, $this->requestBuilder, $this->hydrator);
+    }
+
+    /**
+     * @return HttpClient
+     */
+    public function httpClient(): Api\HttpClient
+    {
+        return new Api\HttpClient($this->httpClient, $this->requestBuilder, $this->hydrator);
+    }
+
+    /**
+     * @return SubAccounts
+     */
+    public function subaccounts(): Api\SubAccounts
+    {
+        return new Api\SubAccounts($this->httpClient, $this->requestBuilder, $this->hydrator);
+    }
+
+    /**
+     * @return Templates
+     */
+    public function templates(): Templates
+    {
+        return new Templates($this->httpClient, $this->requestBuilder, $this->hydrator);
+    }
+
+    /**
+     * @return Metrics
+     */
+    public function metrics(): Metrics
+    {
+        return new Metrics($this->httpClient, $this->requestBuilder, $this->hydrator);
+    }
+
+    /**
+     * @return DomainKeys
+     */
+    public function domainKeys(): Api\DomainKeys
+    {
+        return new Api\DomainKeys($this->httpClient, $this->requestBuilder, $this->hydrator);
+    }
+
+    /**
+     * @return AccountManagement
+     */
+    public function accountManagement(): AccountManagement
+    {
+        return new AccountManagement($this->httpClient, $this->requestBuilder, $this->hydrator);
+    }
+}

+ 147 - 0
vendor/mailgun/mailgun-php/src/Message/BatchMessage.php

@@ -0,0 +1,147 @@
+<?php
+
+declare(strict_types=1);
+
+/*
+ * Copyright (C) 2013 Mailgun
+ *
+ * This software may be modified and distributed under the terms
+ * of the MIT license. See the LICENSE file for details.
+ */
+
+namespace Mailgun\Message;
+
+use Mailgun\Api\Message;
+use Mailgun\Message\Exceptions\MissingRequiredParameter;
+use Mailgun\Message\Exceptions\RuntimeException;
+use Mailgun\Message\Exceptions\TooManyRecipients;
+use Psr\Http\Client\ClientExceptionInterface;
+
+/**
+ * This class is used for batch sending. See the official documentation (link below)
+ * for usage instructions.
+ *
+ * @see https://github.com/mailgun/mailgun-php/blob/master/src/Mailgun/Message/README.md
+ */
+class BatchMessage extends MessageBuilder
+{
+    /**
+     * @var array
+     */
+    private array $batchRecipientAttributes = [];
+
+    /**
+     * @var bool
+     */
+    private bool $autoSend;
+
+    /**
+     * @var array
+     */
+    private array $messageIds = [];
+
+    /**
+     * @var string
+     */
+    private string $domain;
+
+    /**
+     * @var Message
+     */
+    private Message $api;
+
+    /**
+     * @param Message $messageApi
+     * @param string  $domain
+     * @param bool    $autoSend
+     */
+    public function __construct(Message $messageApi, string $domain, bool $autoSend)
+    {
+        $this->api = $messageApi;
+        $this->domain = $domain;
+        $this->autoSend = $autoSend;
+    }
+
+    /**
+     * @param string $headerName
+     * @param string $address
+     * @param array  $variables  {
+     *                           id?:string
+     *                           full_name?: string,
+     *                           first?: string,
+     *                           last?: string,
+     * @return MessageBuilder
+     * @throws MissingRequiredParameter
+     * @throws TooManyRecipients|ClientExceptionInterface
+     */
+    protected function addRecipient(string $headerName, string $address, array $variables): MessageBuilder
+    {
+        if (array_key_exists($headerName, $this->counters['recipients'])) {
+            if (self::RECIPIENT_COUNT_LIMIT === $this->counters['recipients'][$headerName]) {
+                if (false === $this->autoSend) {
+                    throw TooManyRecipients::whenAutoSendDisabled();
+                }
+                $this->finalize();
+            }
+        }
+
+        parent::addRecipient($headerName, $address, $variables);
+
+        if (array_key_exists($headerName, $this->counters['recipients']) && !array_key_exists('id', $variables)) {
+            $variables['id'] = $headerName.'_'.$this->counters['recipients'][$headerName];
+        }
+        if ($variables) {
+            $this->batchRecipientAttributes[$address] = $variables;
+        }
+
+        return $this;
+    }
+
+    /**
+     * @throws RuntimeException
+     * @throws MissingRequiredParameter|ClientExceptionInterface
+     */
+    public function finalize(): void
+    {
+        $message = $this->message;
+
+        if (empty($this->domain)) {
+            throw new RuntimeException('You must call BatchMessage::setDomain before sending messages.');
+        }
+
+        if (empty($message['from'])) {
+            throw MissingRequiredParameter::create('from');
+        }
+
+        if (empty($message['to'])) {
+            throw MissingRequiredParameter::create('to');
+        }
+
+        if (empty($message['subject'])) {
+            throw MissingRequiredParameter::create('subject');
+        }
+
+        if (empty($message['text']) && empty($message['html']) && empty($message['template'])) {
+            throw MissingRequiredParameter::create('text", "html" or "template');
+        }
+
+        $message['recipient-variables'] = json_encode($this->batchRecipientAttributes, JSON_FORCE_OBJECT);
+        $response = $this->api->send($this->domain, $message);
+
+        $this->batchRecipientAttributes = [];
+        $this->counters['recipients']['to'] = 0;
+        $this->counters['recipients']['cc'] = 0;
+        $this->counters['recipients']['bcc'] = 0;
+        unset($this->message['to']);
+
+        $this->messageIds[] = $response->getId();
+    }
+
+    /**
+     * @return string[]
+     */
+    public function getMessageIds(): array
+    {
+        return $this->messageIds;
+    }
+}

+ 27 - 0
vendor/mailgun/mailgun-php/src/Message/Exceptions/LimitExceeded.php

@@ -0,0 +1,27 @@
+<?php
+
+declare(strict_types=1);
+
+/*
+ * Copyright (C) 2013 Mailgun
+ *
+ * This software may be modified and distributed under the terms
+ * of the MIT license. See the LICENSE file for details.
+ */
+
+namespace Mailgun\Message\Exceptions;
+
+use Mailgun\Exception;
+
+class LimitExceeded extends \Exception implements Exception
+{
+    /**
+     * @param string $field
+     * @param int $limit
+     * @return self
+     */
+    public static function create(string $field, int $limit)
+    {
+        return new self(sprintf('You\'ve exceeded the maximum (%d) %s for a single message.', $limit, $field));
+    }
+}

+ 31 - 0
vendor/mailgun/mailgun-php/src/Message/Exceptions/MissingRequiredParameter.php

@@ -0,0 +1,31 @@
+<?php
+
+declare(strict_types=1);
+
+/*
+ * Copyright (C) 2013 Mailgun
+ *
+ * This software may be modified and distributed under the terms
+ * of the MIT license. See the LICENSE file for details.
+ */
+
+namespace Mailgun\Message\Exceptions;
+
+use Mailgun\Exception;
+
+class MissingRequiredParameter extends \Exception implements Exception
+{
+    /**
+     * @param string $parameter
+     * @param string|null $message
+     * @return self
+     */
+    public static function create(string $parameter, ?string $message = null)
+    {
+        if (null === $message) {
+            $message = 'The parameters passed to the API were invalid. Please specify "%s".';
+        }
+
+        return new self(sprintf($message, $parameter));
+    }
+}

+ 18 - 0
vendor/mailgun/mailgun-php/src/Message/Exceptions/RuntimeException.php

@@ -0,0 +1,18 @@
+<?php
+
+declare(strict_types=1);
+
+/*
+ * Copyright (C) 2013 Mailgun
+ *
+ * This software may be modified and distributed under the terms
+ * of the MIT license. See the LICENSE file for details.
+ */
+
+namespace Mailgun\Message\Exceptions;
+
+use Mailgun\Exception;
+
+class RuntimeException extends \RuntimeException implements Exception
+{
+}

+ 37 - 0
vendor/mailgun/mailgun-php/src/Message/Exceptions/TooManyRecipients.php

@@ -0,0 +1,37 @@
+<?php
+
+declare(strict_types=1);
+
+/*
+ * Copyright (C) 2013 Mailgun
+ *
+ * This software may be modified and distributed under the terms
+ * of the MIT license. See the LICENSE file for details.
+ */
+
+namespace Mailgun\Message\Exceptions;
+
+use Mailgun\Exception;
+use Mailgun\Message\MessageBuilder;
+
+class TooManyRecipients extends LimitExceeded implements Exception
+{
+    /**
+     * @param string $field
+     * @param int $limit
+     * @return LimitExceeded|self
+     */
+    public static function create(string $field, int $limit = MessageBuilder::RECIPIENT_COUNT_LIMIT)
+    {
+        return new self(sprintf('You\'ve exceeded the maximum recipient count (%s) for filed "%s".', $limit, $field));
+    }
+
+    /**
+     * @param int $limit
+     * @return self
+     */
+    public static function whenAutoSendDisabled(int $limit = MessageBuilder::RECIPIENT_COUNT_LIMIT)
+    {
+        return new self(sprintf('You\'ve exceeded the maximum recipient count (%s) with autosend disabled.', $limit));
+    }
+}

+ 530 - 0
vendor/mailgun/mailgun-php/src/Message/MessageBuilder.php

@@ -0,0 +1,530 @@
+<?php
+
+declare(strict_types=1);
+
+/*
+ * Copyright (C) 2013 Mailgun
+ *
+ * This software may be modified and distributed under the terms
+ * of the MIT license. See the LICENSE file for details.
+ */
+
+namespace Mailgun\Message;
+
+use DateTime;
+use DateTimeInterface;
+use DateTimeZone;
+use Mailgun\Message\Exceptions\LimitExceeded;
+use Mailgun\Message\Exceptions\TooManyRecipients;
+
+/**
+ * This class is used for composing a properly formed
+ * message object. Dealing with arrays can be cumbersome,
+ * this class makes the process easier. See the official
+ * documentation (link below) for usage instructions.
+ *
+ * @see https://github.com/mailgun/mailgun-php/blob/master/src/Mailgun/Message/README.md
+ */
+class MessageBuilder
+{
+    public const RECIPIENT_COUNT_LIMIT = 1000;
+
+    public const CAMPAIGN_ID_LIMIT = 3;
+
+    public const TAG_LIMIT = 10;
+
+    /**
+     * @var array
+     */
+    protected array $message = [];
+
+    /**
+     * @var array
+     */
+    protected array $variables = [];
+
+    /**
+     * @var array
+     */
+    protected array $counters = [
+        'recipients' => [
+            'to' => 0,
+            'cc' => 0,
+            'bcc' => 0,
+        ],
+        'attributes' => [
+            'attachment' => 0,
+            'campaign_id' => 0,
+            'custom_option' => 0,
+            'tag' => 0,
+        ],
+    ];
+
+    /**
+     * @param array $params
+     * @param string $key
+     * @param mixed $default
+     * @return mixed
+     */
+    private function get(array $params, string $key, $default)
+    {
+        if (array_key_exists($key, $params)) {
+            return $params[$key];
+        }
+
+        return $default;
+    }
+
+    /**
+     * @param array $params {
+     *                        full_name?:
+     *                        string,
+     *                        first?:
+     *                        string,
+     *                        last?:
+     *                        string,
+     *                        }
+     * @return string
+     */
+    private function getFullName(array $params): string
+    {
+        if (isset($params['full_name'])) {
+            return $this->get($params, 'full_name', '');
+        }
+
+        return trim(sprintf('%s %s', $this->get($params, 'first', ''), $this->get($params, 'last', '')));
+    }
+
+    /**
+     * @param  string $address
+     * @param  array  $variables {
+     *                           full_name?: string,
+     *                           first?: string,
+     *                           last?: string,
+     *                           }
+     * @return string
+     */
+    protected function parseAddress(string $address, array $variables): string
+    {
+        $fullName = $this->getFullName($variables);
+        if (!empty($fullName)) {
+            return sprintf('"%s" <%s>', $fullName, $address);
+        }
+
+        return $address;
+    }
+
+    /**
+     * @param  string         $headerName
+     * @param  string         $address
+     * @param  array          $variables  {
+     *                                    full_name?:
+     *                                    string,
+     *                                    first?:
+     *                                    string,
+     *                                    last?:
+     *                                    string,
+     *                                    }
+     * @return MessageBuilder
+     */
+    protected function addRecipient(string $headerName, string $address, array $variables): self
+    {
+        $compiledAddress = $this->parseAddress($address, $variables);
+
+        if ('h:reply-to' === $headerName) {
+            $this->message[$headerName] = $compiledAddress;
+        } elseif (isset($this->message[$headerName])) {
+            $this->message[$headerName][] = $compiledAddress;
+        } else {
+            $this->message[$headerName] = [$compiledAddress];
+        }
+        if (array_key_exists($headerName, $this->counters['recipients'])) {
+            ++$this->counters['recipients'][$headerName];
+        }
+
+        return $this;
+    }
+
+    /**
+     * @param string $address
+     * @param array  $variables {
+     *                          id?: string,
+     *                          full_name?: string,
+     *                          first?: string,
+     *                          last?: string,
+     *                          }
+     * @return MessageBuilder
+     * @throws TooManyRecipients|LimitExceeded
+     */
+    public function addToRecipient(string $address, array $variables = []): self
+    {
+        if ($this->counters['recipients']['to'] > self::RECIPIENT_COUNT_LIMIT) {
+            throw TooManyRecipients::create('to');
+        }
+        $this->addRecipient('to', $address, $variables);
+
+        return $this;
+    }
+
+    /**
+     * @param string $address
+     * @param array  $variables {
+     *                          id?: string,
+     *                          full_name?: string,
+     *                          first?: string,
+     *                          last?: string,
+     *                          }
+     * @return MessageBuilder
+     * @throws TooManyRecipients|LimitExceeded
+     */
+    public function addCcRecipient(string $address, array $variables = []): self
+    {
+        if ($this->counters['recipients']['cc'] > self::RECIPIENT_COUNT_LIMIT) {
+            throw TooManyRecipients::create('cc');
+        }
+
+        $this->addRecipient('cc', $address, $variables);
+
+        return $this;
+    }
+
+    /**
+     * @param string $address
+     * @param array  $variables {
+     *                          id?: string,
+     *                          full_name?: string,
+     *                          first?: string,
+     *                          last?: string,
+     *                          }
+     * @return MessageBuilder
+     * @throws TooManyRecipients|LimitExceeded
+     */
+    public function addBccRecipient(string $address, array $variables = []): self
+    {
+        if ($this->counters['recipients']['bcc'] > self::RECIPIENT_COUNT_LIMIT) {
+            throw TooManyRecipients::create('bcc');
+        }
+
+        $this->addRecipient('bcc', $address, $variables);
+
+        return $this;
+    }
+
+    /**
+     * @param string $address
+     * @param array  $variables {
+     *                          id?: string,
+     *                          full_name?: string,
+     *                          first?: string,
+     *                          last?: string,
+     *                          }
+     *
+     * @return MessageBuilder
+     */
+    public function setFromAddress(string $address, array $variables = []): self
+    {
+        $this->addRecipient('from', $address, $variables);
+
+        return $this;
+    }
+
+    /**
+     * @param string $address
+     * @param array  $variables {
+     *                          id?: string,
+     *                          full_name?: string,
+     *                          first?: string,
+     *                          last?: string,
+     *                          }
+     *
+     * @return MessageBuilder
+     */
+    public function setReplyToAddress(string $address, array $variables = []): self
+    {
+        $this->addRecipient('h:reply-to', $address, $variables);
+
+        return $this;
+    }
+
+    /**
+     * @param  string $subject
+     * @return $this
+     */
+    public function setSubject(string $subject): self
+    {
+        $this->message['subject'] = $subject;
+
+        return $this;
+    }
+
+    /**
+     * @param string $template Name of the Mailgun template
+     */
+    public function setTemplate(string $template): self
+    {
+        $this->message['template'] = $template;
+
+        return $this;
+    }
+
+    /**
+     * @param string $headerName
+     * @param mixed $headerData
+     * @return $this
+     */
+    public function addCustomHeader(string $headerName, $headerData): self
+    {
+        if (!preg_match('/^h:/i', $headerName)) {
+            $headerName = 'h:'.$headerName;
+        }
+
+        if (!array_key_exists($headerName, $this->message)) {
+            $this->message[$headerName] = $headerData;
+        } else {
+            if (is_array($this->message[$headerName])) {
+                $this->message[$headerName][] = $headerData;
+            } else {
+                $this->message[$headerName] = [$this->message[$headerName], $headerData];
+            }
+        }
+
+        return $this;
+    }
+
+    /**
+     * @param  string $textBody
+     * @return $this
+     */
+    public function setTextBody(string $textBody): self
+    {
+        $this->message['text'] = $textBody;
+
+        return $this;
+    }
+
+    /**
+     * @param  string $htmlBody
+     * @return $this
+     */
+    public function setHtmlBody(string $htmlBody): self
+    {
+        $this->message['html'] = $htmlBody;
+
+        return $this;
+    }
+
+    /**
+     * @param  string      $attachmentPath
+     * @param  string|null $attachmentName
+     * @return $this
+     */
+    public function addAttachment(string $attachmentPath, ?string $attachmentName = null): self
+    {
+        if (!isset($this->message['attachment'])) {
+            $this->message['attachment'] = [];
+        }
+
+        $this->message['attachment'][] = [
+            'filePath' => $attachmentPath,
+            'filename' => $attachmentName,
+        ];
+
+        return $this;
+    }
+
+    /**
+     * @param  string      $attachmentContent
+     * @param  string|null $attachmentName
+     * @return $this
+     */
+    public function addStringAttachment(string $attachmentContent, ?string $attachmentName = null): self
+    {
+        if (!isset($this->message['attachment'])) {
+            $this->message['attachment'] = [];
+        }
+
+        $this->message['attachment'][] = [
+            'fileContent' => $attachmentContent,
+            'filename' => $attachmentName,
+        ];
+
+        return $this;
+    }
+
+    /**
+     * @param  string      $inlineImagePath
+     * @param  string|null $inlineImageName
+     * @return $this
+     */
+    public function addInlineImage(string $inlineImagePath, ?string $inlineImageName = null): self
+    {
+        if (!isset($this->message['inline'])) {
+            $this->message['inline'] = [];
+        }
+
+        $this->message['inline'][] = [
+            'filePath' => $inlineImagePath,
+            'filename' => $inlineImageName,
+        ];
+
+        return $this;
+    }
+
+    /**
+     * @param  bool  $enabled
+     * @return $this
+     */
+    public function setTestMode(bool $enabled): self
+    {
+        $this->message['o:testmode'] = $enabled ? 'yes' : 'no';
+
+        return $this;
+    }
+
+    /**
+     * @throws LimitExceeded
+     */
+    public function addCampaignId(string $campaignId): self
+    {
+        if ($this->counters['attributes']['campaign_id'] >= self::CAMPAIGN_ID_LIMIT) {
+            throw LimitExceeded::create('campaigns', self::CAMPAIGN_ID_LIMIT);
+        }
+        if (isset($this->message['o:campaign'])) {
+            $this->message['o:campaign'][] = $campaignId;
+        } else {
+            $this->message['o:campaign'] = [$campaignId];
+        }
+        ++$this->counters['attributes']['campaign_id'];
+
+        return $this;
+    }
+
+    /**
+     * @throws LimitExceeded
+     */
+    public function addTag(string $tag): self
+    {
+        if ($this->counters['attributes']['tag'] >= self::TAG_LIMIT) {
+            throw LimitExceeded::create('tags', self::TAG_LIMIT);
+        }
+
+        if (isset($this->message['o:tag'])) {
+            $this->message['o:tag'][] = $tag;
+        } else {
+            $this->message['o:tag'] = [$tag];
+        }
+        ++$this->counters['attributes']['tag'];
+
+        return $this;
+    }
+
+    /**
+     * @param  bool  $enabled
+     * @return $this
+     */
+    public function setDkim(bool $enabled): self
+    {
+        $this->message['o:dkim'] = $enabled ? 'yes' : 'no';
+
+        return $this;
+    }
+
+    /**
+     * @param  bool  $enabled
+     * @return $this
+     */
+    public function setOpenTracking(bool $enabled): self
+    {
+        $this->message['o:tracking-opens'] = $enabled ? 'yes' : 'no';
+
+        return $this;
+    }
+
+    /**
+     * @param  bool  $enabled
+     * @param  bool  $htmlOnly
+     * @return $this
+     */
+    public function setClickTracking(bool $enabled, bool $htmlOnly = false): self
+    {
+        $value = 'no';
+        if ($enabled) {
+            $value = 'yes';
+            if ($htmlOnly) {
+                $value = 'htmlonly';
+            }
+        }
+
+        $this->message['o:tracking-clicks'] = $value;
+
+        return $this;
+    }
+
+    /**
+     * @param  string      $timeDate
+     * @param  string|null $timeZone
+     * @return $this
+     * @throws \Exception
+     */
+    public function setDeliveryTime(string $timeDate, ?string $timeZone = null): self
+    {
+        if (null !== $timeZone) {
+            $timeZoneObj = new DateTimeZone($timeZone);
+        } else {
+            $timeZoneObj = new DateTimeZone('UTC');
+        }
+
+        $dateTimeObj = new DateTime($timeDate, $timeZoneObj);
+        $formattedTimeDate = $dateTimeObj->format(DateTimeInterface::RFC2822);
+        $this->message['o:deliverytime'] = $formattedTimeDate;
+
+        return $this;
+    }
+
+    /**
+     * @param string $customName
+     * @param mixed $data
+     * @return $this
+     */
+    public function addCustomData(string $customName, $data): self
+    {
+        $this->message['v:'.$customName] = json_encode($data);
+
+        return $this;
+    }
+
+    /**
+     * @param string $parameterName
+     * @param mixed $data
+     * @return $this
+     */
+    public function addCustomParameter(string $parameterName, $data): self
+    {
+        if (isset($this->message[$parameterName])) {
+            $this->message[$parameterName][] = $data;
+        } else {
+            $this->message[$parameterName] = [$data];
+        }
+
+        return $this;
+    }
+
+    /**
+     * @param  array $message
+     * @return $this
+     */
+    public function setMessage(array $message): self
+    {
+        $this->message = $message;
+
+        return $this;
+    }
+
+    /**
+     * @return array
+     */
+    public function getMessage(): array
+    {
+        return $this->message;
+    }
+}

+ 96 - 0
vendor/mailgun/mailgun-php/src/Message/README.md

@@ -0,0 +1,96 @@
+Mailgun - Messages
+==================
+
+This is the Mailgun PHP *Message* utilities.
+
+The below assumes you've already installed the Mailgun PHP SDK in to your
+project. If not, go back to the master README for instructions.
+
+There are two utilities included, `MessageBuilder` and `BatchMessage`.
+
+* `MessageBuilder`: Allows you to build a message object by calling methods for
+each MIME attribute.
+* `BatchMessage`: Extends `MessageBuilder` and allows you to iterate through
+recipients from a list. Messages will fire after the 1,000th recipient has been
+added.
+
+Usage - Message Builder
+-----------------------
+Here's how to use Message Builder to build your Message.
+
+```php
+require 'vendor/autoload.php';
+use Mailgun\Mailgun;
+use Mailgun\Message\MessageBuilder;
+
+// First, instantiate the SDK with your API credentials
+$mg = Mailgun::create('key-example');
+
+# Next, instantiate a Message Builder object from the SDK.
+$builder = new MessageBuilder();
+
+# Define the from address.
+$builder->setFromAddress("me@example.com", array("first"=>"PHP", "last" => "SDK"));
+# Define a to recipient.
+$builder->addToRecipient("john.doe@example.com", array("first" => "John", "last" => "Doe"));
+# Define a cc recipient.
+$builder->addCcRecipient("sally.doe@example.com", array("full_name" => "Sally Doe"));
+# Define the subject.
+$builder->setSubject("A message from the PHP SDK using Message Builder!");
+# Define the body of the message (One is required).
+$builder->setTextBody("This is the text body of the message!");
+$builder->setHtmlBody("<html><p>This is the HTML body of the message</p></html>");
+$builder->setTemplate("template_name");
+
+# Other Optional Parameters.
+$builder->addCampaignId("My-Awesome-Campaign");
+$builder->addCustomHeader("Customer-Id", "12345");
+$builder->addAttachment("@/tron.jpg");
+$builder->setDeliveryTime("tomorrow 8:00AM", "PST");
+$builder->setClickTracking(true);
+
+# Finally, send the message.
+$mg->messages()->send("example.com", $builder->getMessage());
+```
+
+Usage - Batch Message
+---------------------
+Here's how to use Batch Message to easily handle batch sending jobs.
+
+```php
+require 'vendor/autoload.php';
+use Mailgun\Mailgun;
+
+// First, instantiate the SDK with your API credentials
+$mg = Mailgun::create('key-example');
+
+# Next, instantiate a Message Builder object from the SDK, pass in your sending domain.
+$batchMessage = $mg->messages()->getBatchMessage("example.com");
+
+# Define the from address.
+$batchMessage->setFromAddress("me@example.com", array("first"=>"PHP", "last" => "SDK"));
+# Define the subject.
+$batchMessage->setSubject("A Batch Message from the PHP SDK!");
+# Define the body of the message (One is required).
+$batchMessage->setTextBody("This is the text body of the message!");
+$batchMessage->setHtmlBody("<html><p>This is the HTML body of the message</p></html>");
+$batchMessage->setTemplate("template_name");
+
+# Next, let's add a few recipients to the batch job.
+$batchMessage->addToRecipient("john.doe@example.com", array("first" => "John", "last" => "Doe"));
+$batchMessage->addToRecipient("sally.doe@example.com", array("first" => "Sally", "last" => "Doe"));
+$batchMessage->addToRecipient("mike.jones@example.com", array("first" => "Mike", "last" => "Jones"));
+...
+// After 1,000 recipients, Batch Message will automatically post your message to the messages endpoint.
+
+// Call finalize() to send any remaining recipients still in the buffer.
+$batchMessage->finalize();
+
+$messageIds = $batchMessage->getMessageIds();
+
+```
+
+More Documentation
+------------------
+See the official [Mailgun Docs](https://documentation.mailgun.com/en/latest/api-sending.html)
+for more information.

+ 36 - 0
vendor/mailgun/mailgun-php/src/Model/AccountManagement/AccountResponse.php

@@ -0,0 +1,36 @@
+<?php
+
+declare(strict_types=1);
+
+/*
+ * Copyright (C) 2013 Mailgun
+ *
+ * This software may be modified and distributed under the terms
+ * of the MIT license. See the LICENSE file for details.
+ */
+
+namespace Mailgun\Model\AccountManagement;
+
+use Mailgun\Model\ApiResponse;
+
+final class AccountResponse implements ApiResponse
+{
+    private string $message;
+
+    public static function create(array $data): self
+    {
+        $model = new self();
+        $model->message = $data['message'] ?? '';
+
+        return $model;
+    }
+
+    public function getMessage(): string
+    {
+        return $this->message;
+    }
+
+    private function __construct()
+    {
+    }
+}

+ 91 - 0
vendor/mailgun/mailgun-php/src/Model/AccountManagement/HttpSigningKeyResponse.php

@@ -0,0 +1,91 @@
+<?php
+
+declare(strict_types=1);
+
+/*
+ * Copyright (C) 2013 Mailgun
+ *
+ * This software may be modified and distributed under the terms
+ * of the MIT license. See the LICENSE file for details.
+ */
+
+namespace Mailgun\Model\AccountManagement;
+
+use Mailgun\Model\ApiResponse;
+
+final class HttpSigningKeyResponse implements ApiResponse
+{
+    private string $key;
+    private string $createdAt;
+    private string $httpSigningKey;
+    private string $message;
+
+    /**
+     * @param array $data
+     * @return self
+     */
+    public static function create(array $data): self
+    {
+        $model = new self();
+        $model->key = $data['key'] ?? '';
+        $model->createdAt = $data['created_at'] ?? '';
+        $model->httpSigningKey = $data['http_signing_key'] ?? '';
+        $model->message = $data['message'] ?? '';
+
+        return $model;
+    }
+
+    /**
+     * @return string
+     */
+    public function getKey(): string
+    {
+        return $this->key;
+    }
+
+    /**
+     * @return string
+     */
+    public function getCreatedAt(): string
+    {
+        return $this->createdAt;
+    }
+
+    /**
+     * @return string
+     */
+    public function getHttpSigningKey(): string
+    {
+        return $this->httpSigningKey;
+    }
+
+    /**
+     * @param string $httpSigningKey
+     * @return void
+     */
+    public function setHttpSigningKey(string $httpSigningKey): void
+    {
+        $this->httpSigningKey = $httpSigningKey;
+    }
+
+    /**
+     * @return string
+     */
+    public function getMessage(): string
+    {
+        return $this->message;
+    }
+
+    /**
+     * @param string $message
+     * @return void
+     */
+    public function setMessage(string $message): void
+    {
+        $this->message = $message;
+    }
+
+    private function __construct()
+    {
+    }
+}

+ 43 - 0
vendor/mailgun/mailgun-php/src/Model/AccountManagement/SandboxAuthRecipientsResponse.php

@@ -0,0 +1,43 @@
+<?php
+
+declare(strict_types=1);
+
+/*
+ * Copyright (C) 2013 Mailgun
+ *
+ * This software may be modified and distributed under the terms
+ * of the MIT license. See the LICENSE file for details.
+ */
+
+namespace Mailgun\Model\AccountManagement;
+
+use Mailgun\Model\ApiResponse;
+
+final class SandboxAuthRecipientsResponse implements ApiResponse
+{
+    private array $recipients;
+
+    /**
+     * @param array $data
+     * @return self
+     */
+    public static function create(array $data): self
+    {
+        $model = new self();
+        $model->recipients = $data['recipients'] ?? $data['recipient'] ?? [];
+
+        return $model;
+    }
+
+    /**
+     * @return array
+     */
+    public function getRecipients(): array
+    {
+        return $this->recipients;
+    }
+
+    private function __construct()
+    {
+    }
+}

+ 23 - 0
vendor/mailgun/mailgun-php/src/Model/ApiResponse.php

@@ -0,0 +1,23 @@
+<?php
+
+declare(strict_types=1);
+
+/*
+ * Copyright (C) 2013 Mailgun
+ *
+ * This software may be modified and distributed under the terms
+ * of the MIT license. See the LICENSE file for details.
+ */
+
+namespace Mailgun\Model;
+
+/**
+ * @author Tobias Nyholm <tobias.nyholm@gmail.com>
+ */
+interface ApiResponse
+{
+    /**
+     * Create an API response object from the HTTP response from the API server.
+     */
+    public static function create(array $data);
+}

+ 86 - 0
vendor/mailgun/mailgun-php/src/Model/Domain/AbstractDomainResponse.php

@@ -0,0 +1,86 @@
+<?php
+
+declare(strict_types=1);
+
+/*
+ * Copyright (C) 2013 Mailgun
+ *
+ * This software may be modified and distributed under the terms
+ * of the MIT license. See the LICENSE file for details.
+ */
+
+namespace Mailgun\Model\Domain;
+
+use Mailgun\Model\ApiResponse;
+
+/**
+ * @author Tobias Nyholm <tobias.nyholm@gmail.com>
+ */
+abstract class AbstractDomainResponse implements ApiResponse
+{
+    private $message;
+    private $domain;
+    private $inboundDnsRecords;
+    private $outboundDnsRecords;
+
+    public static function create(array $data): self
+    {
+        $rx = [];
+        $tx = [];
+        $domain = null;
+
+        if (isset($data['domain'])) {
+            $domain = Domain::create($data['domain']);
+        }
+
+        if (isset($data['receiving_dns_records'])) {
+            foreach ($data['receiving_dns_records'] as $item) {
+                $rx[] = DnsRecord::create($item);
+            }
+        }
+
+        if (isset($data['sending_dns_records'])) {
+            foreach ($data['sending_dns_records'] as $item) {
+                $tx[] = DnsRecord::create($item);
+            }
+        }
+
+        $model = new static();
+        $model->domain = $domain;
+        $model->inboundDnsRecords = $rx;
+        $model->outboundDnsRecords = $tx;
+        $model->message = $data['message'] ?? null;
+
+        return $model;
+    }
+
+    final private function __construct()
+    {
+    }
+
+    public function getDomain(): ?Domain
+    {
+        return $this->domain;
+    }
+
+    /**
+     * @return DnsRecord[] tx
+     */
+    public function getInboundDNSRecords(): array
+    {
+        return $this->inboundDnsRecords;
+    }
+
+    /**
+     * @return DnsRecord[] tx
+     */
+    public function getOutboundDNSRecords(): array
+    {
+        return $this->outboundDnsRecords;
+    }
+
+    public function getMessage(): ?string
+    {
+        return $this->message;
+    }
+}

+ 128 - 0
vendor/mailgun/mailgun-php/src/Model/Domain/CertStatusResponse.php

@@ -0,0 +1,128 @@
+<?php
+
+declare(strict_types=1);
+
+/*
+ * Copyright (C) 2013 Mailgun
+ *
+ * This software may be modified and distributed under the terms
+ * of the MIT license. See the LICENSE file for details.
+ */
+
+namespace Mailgun\Model\Domain;
+
+use Mailgun\Model\ApiResponse;
+
+final class CertStatusResponse implements ApiResponse
+{
+    private string $status;
+    private string $error;
+    private string $certificate;
+    private string $location;
+    private string $message;
+
+
+    /**
+     * @param array $data
+     * @return self
+     */
+    public static function create(array $data): self
+    {
+        $model = new self();
+        $model->setStatus($data['status']);
+        $model->setError($data['error'] ?? '');
+        $model->setCertificate($data['certificate'] ?? '');
+        $model->setLocation($data['location'] ?? '');
+        $model->setMessage($data['message'] ?? '');
+
+        return $model;
+    }
+
+    /**
+     * @return string
+     */
+    public function getStatus(): string
+    {
+        return $this->status;
+    }
+
+    /**
+     * @param string $status
+     */
+    public function setStatus(string $status): void
+    {
+        $this->status = $status;
+    }
+
+    /**
+     * @return string
+     */
+    public function getError(): string
+    {
+        return $this->error;
+    }
+
+    /**
+     * @param string $error
+     */
+    public function setError(string $error): void
+    {
+        $this->error = $error;
+    }
+
+    /**
+     * @return string
+     */
+    public function getCertificate(): string
+    {
+        return $this->certificate;
+    }
+
+    /**
+     * @param string $certificate
+     */
+    public function setCertificate(string $certificate): void
+    {
+        $this->certificate = $certificate;
+    }
+
+    /**
+     * @return string
+     */
+    public function getLocation(): string
+    {
+        return $this->location;
+    }
+
+    /**
+     * @param string $location
+     */
+    public function setLocation(string $location): void
+    {
+        $this->location = $location;
+    }
+
+    /**
+     * @return string
+     */
+    public function getMessage(): string
+    {
+        return $this->message;
+    }
+
+    /**
+     * @param string $message
+     */
+    public function setMessage(string $message): void
+    {
+        $this->message = $message;
+    }
+
+
+    /**
+     *
+     */
+    private function __construct()
+    {
+    }
+}

+ 59 - 0
vendor/mailgun/mailgun-php/src/Model/Domain/ClickTracking.php

@@ -0,0 +1,59 @@
+<?php
+
+declare(strict_types=1);
+
+/*
+ * Copyright (C) 2013 Mailgun
+ *
+ * This software may be modified and distributed under the terms
+ * of the MIT license. See the LICENSE file for details.
+ */
+
+namespace Mailgun\Model\Domain;
+
+/**
+ * Represents a single Click Tracking setting for a domain tracking.
+ *
+ * @author Artem Bondarenko <artem@uartema.com>
+ */
+final class ClickTracking
+{
+    private ?string $active;
+
+    public static function create(array $data): self
+    {
+        $active = $data['active'] ?? null;
+        $model = new self();
+        $model->active = 'htmlonly' === $active ? $active : ($active ? 'yes' : 'no');
+
+        return $model;
+    }
+
+    private function __construct()
+    {
+    }
+
+    /**
+     * @return string|null
+     */
+    public function getActive(): ?string
+    {
+        return $this->active;
+    }
+
+    /**
+     * @return bool
+     */
+    public function isActive(): bool
+    {
+        return 'yes' === $this->getActive();
+    }
+
+    /**
+     * @return bool
+     */
+    public function isHtmlOnly(): bool
+    {
+        return 'htmlonly' === $this->getActive();
+    }
+}

+ 61 - 0
vendor/mailgun/mailgun-php/src/Model/Domain/ConnectionResponse.php

@@ -0,0 +1,61 @@
+<?php
+
+declare(strict_types=1);
+
+/*
+ * Copyright (C) 2013 Mailgun
+ *
+ * This software may be modified and distributed under the terms
+ * of the MIT license. See the LICENSE file for details.
+ */
+
+namespace Mailgun\Model\Domain;
+
+use Mailgun\Model\ApiResponse;
+
+/**
+ * @author Sean Johnson <sean@mailgun.com>
+ */
+final class ConnectionResponse implements ApiResponse
+{
+    private $noVerify;
+    private $requireTLS;
+
+    public static function create(array $data): ?self
+    {
+        if (!isset($data['connection'])) {
+            return null;
+        }
+        $connSettings = $data['connection'];
+
+        $model = new self();
+        $model->noVerify = $connSettings['skip_verification'] ?? null;
+        $model->requireTLS = $connSettings['require_tls'] ?? null;
+
+        return $model;
+    }
+
+    private function __construct()
+    {
+    }
+
+    /**
+     * Disable remote TLS certificate verification.
+     *
+     * @return bool
+     */
+    public function getSkipVerification(): ?bool
+    {
+        return $this->noVerify;
+    }
+
+    /**
+     * Requires TLS for all outbound communication.
+     *
+     * @return bool
+     */
+    public function getRequireTLS(): ?bool
+    {
+        return $this->requireTLS;
+    }
+}

+ 39 - 0
vendor/mailgun/mailgun-php/src/Model/Domain/CreateCredentialResponse.php

@@ -0,0 +1,39 @@
+<?php
+
+declare(strict_types=1);
+
+/*
+ * Copyright (C) 2013 Mailgun
+ *
+ * This software may be modified and distributed under the terms
+ * of the MIT license. See the LICENSE file for details.
+ */
+
+namespace Mailgun\Model\Domain;
+
+use Mailgun\Model\ApiResponse;
+
+/**
+ * @author Tobias Nyholm <tobias.nyholm@gmail.com>
+ */
+final class CreateCredentialResponse implements ApiResponse
+{
+    private $message;
+
+    private function __construct()
+    {
+    }
+
+    public static function create(array $data): self
+    {
+        $model = new self();
+        $model->message = $data['message'] ?? null;
+
+        return $model;
+    }
+
+    public function getMessage(): ?string
+    {
+        return $this->message;
+    }
+}

+ 19 - 0
vendor/mailgun/mailgun-php/src/Model/Domain/CreateResponse.php

@@ -0,0 +1,19 @@
+<?php
+
+declare(strict_types=1);
+
+/*
+ * Copyright (C) 2013 Mailgun
+ *
+ * This software may be modified and distributed under the terms
+ * of the MIT license. See the LICENSE file for details.
+ */
+
+namespace Mailgun\Model\Domain;
+
+/**
+ * @author Tobias Nyholm <tobias.nyholm@gmail.com>
+ */
+final class CreateResponse extends AbstractDomainResponse
+{
+}

+ 65 - 0
vendor/mailgun/mailgun-php/src/Model/Domain/CredentialResponse.php

@@ -0,0 +1,65 @@
+<?php
+
+declare(strict_types=1);
+
+/*
+ * Copyright (C) 2013 Mailgun
+ *
+ * This software may be modified and distributed under the terms
+ * of the MIT license. See the LICENSE file for details.
+ */
+
+namespace Mailgun\Model\Domain;
+
+use Mailgun\Model\ApiResponse;
+
+/**
+ * @author Sean Johnson <sean@mailgun.com>
+ */
+final class CredentialResponse implements ApiResponse
+{
+    private int $totalCount;
+    private $items;
+
+    public static function create(array $data): self
+    {
+        $items = [];
+        if (isset($data['items'])) {
+            foreach ($data['items'] as $item) {
+                $items[] = CredentialResponseItem::create($item);
+            }
+        }
+
+        if (isset($data['total_count'])) {
+            $count = (int) $data['total_count'];
+        } else {
+            $count = count($items);
+        }
+
+        $model = new self();
+        $model->totalCount = $count;
+        $model->items = $items;
+
+        return $model;
+    }
+
+    private function __construct()
+    {
+    }
+
+    /**
+     * @return int
+     */
+    public function getTotalCount(): int
+    {
+        return $this->totalCount;
+    }
+
+    /**
+     * @return CredentialResponseItem[]
+     */
+    public function getCredentials(): array
+    {
+        return $this->items;
+    }
+}

+ 70 - 0
vendor/mailgun/mailgun-php/src/Model/Domain/CredentialResponseItem.php

@@ -0,0 +1,70 @@
+<?php
+
+declare(strict_types=1);
+
+/*
+ * Copyright (C) 2013 Mailgun
+ *
+ * This software may be modified and distributed under the terms
+ * of the MIT license. See the LICENSE file for details.
+ */
+
+namespace Mailgun\Model\Domain;
+
+/**
+ * @author Sean Johnson <sean@mailgun.com>
+ */
+final class CredentialResponseItem
+{
+    private ?int $sizeBytes;
+    private $createdAt;
+    private ?string $mailbox;
+    private ?string $login;
+
+    public static function create(array $data): self
+    {
+        $model = new self();
+        $model->sizeBytes = $data['size_bytes'] ?? null;
+        $model->createdAt = isset($data['created_at']) ? new \DateTimeImmutable($data['created_at']) : null;
+        $model->mailbox = $data['mailbox'] ?? null;
+        $model->login = $data['login'] ?? null;
+
+        return $model;
+    }
+
+    private function __construct()
+    {
+    }
+
+    /**
+     * @return int|null
+     */
+    public function getSizeBytes(): ?int
+    {
+        return $this->sizeBytes;
+    }
+
+    /**
+     * @return \DateTimeImmutable|null
+     */
+    public function getCreatedAt(): ?\DateTimeImmutable
+    {
+        return $this->createdAt;
+    }
+
+    /**
+     * @return string|null
+     */
+    public function getMailbox(): ?string
+    {
+        return $this->mailbox;
+    }
+
+    /**
+     * @return string|null
+     */
+    public function getLogin(): ?string
+    {
+        return $this->login;
+    }
+}

+ 62 - 0
vendor/mailgun/mailgun-php/src/Model/Domain/DeleteCredentialResponse.php

@@ -0,0 +1,62 @@
+<?php
+
+declare(strict_types=1);
+
+/*
+ * Copyright (C) 2013 Mailgun
+ *
+ * This software may be modified and distributed under the terms
+ * of the MIT license. See the LICENSE file for details.
+ */
+
+namespace Mailgun\Model\Domain;
+
+use Mailgun\Model\ApiResponse;
+
+/**
+ * @author Tobias Nyholm <tobias.nyholm@gmail.com>
+ */
+final class DeleteCredentialResponse implements ApiResponse
+{
+    private $message;
+    private ?string $error;
+    private ?string $spec;
+
+    private function __construct()
+    {
+    }
+
+    public static function create(array $data): self
+    {
+        $model = new self();
+        $model->message = $data['message'] ?? null;
+        $model->error = $data['error'] ?? null;
+        $model->spec = $data['spec'] ?? null;
+
+        return $model;
+    }
+
+    /**
+     * @return string|null
+     */
+    public function getMessage(): ?string
+    {
+        return $this->message;
+    }
+
+    /**
+     * @return string|null
+     */
+    public function getError(): ?string
+    {
+        return $this->error;
+    }
+
+    /**
+     * @return string|null
+     */
+    public function getSpec(): ?string
+    {
+        return $this->spec;
+    }
+}

+ 52 - 0
vendor/mailgun/mailgun-php/src/Model/Domain/DeleteResponse.php

@@ -0,0 +1,52 @@
+<?php
+
+declare(strict_types=1);
+
+/*
+ * Copyright (C) 2013 Mailgun
+ *
+ * This software may be modified and distributed under the terms
+ * of the MIT license. See the LICENSE file for details.
+ */
+
+namespace Mailgun\Model\Domain;
+
+use Mailgun\Model\ApiResponse;
+
+/**
+ * @author Tobias Nyholm <tobias.nyholm@gmail.com>
+ */
+final class DeleteResponse implements ApiResponse
+{
+    private ?string $message;
+    private ?string $error;
+
+    private function __construct()
+    {
+    }
+
+    public static function create(array $data): self
+    {
+        $model = new self();
+        $model->message = $data['message'] ?? null;
+        $model->error = $data['error'] ?? null;
+
+        return $model;
+    }
+
+    /**
+     * @return string|null
+     */
+    public function getMessage(): ?string
+    {
+        return $this->message;
+    }
+
+    /**
+     * @return string|null
+     */
+    public function getError(): ?string
+    {
+        return $this->error;
+    }
+}

+ 117 - 0
vendor/mailgun/mailgun-php/src/Model/Domain/DnsRecord.php

@@ -0,0 +1,117 @@
+<?php
+
+declare(strict_types=1);
+
+/*
+ * Copyright (C) 2013 Mailgun
+ *
+ * This software may be modified and distributed under the terms
+ * of the MIT license. See the LICENSE file for details.
+ */
+
+namespace Mailgun\Model\Domain;
+
+/**
+ * Represents a single DNS record for a domain.
+ *
+ * @author Sean Johnson <sean@mailgun.com>
+ */
+final class DnsRecord
+{
+    private ?string $name;
+    private ?string $type;
+    private ?string $value;
+    private ?string $priority;
+    private ?string $valid;
+    private array $cached;
+    private bool $isActive;
+
+    public static function create(array $data): self
+    {
+        $model = new self();
+        $model->name = $data['name'] ?? null;
+        $model->type = $data['record_type'] ?? null;
+        $model->value = $data['value'] ?? null;
+        $model->priority = $data['priority'] ?? null;
+        $model->valid = $data['valid'] ?? null;
+        $model->cached = $data['cached'] ?? [];
+        $model->isActive = $data['is_active'] ?? false;
+
+        return $model;
+    }
+
+    private function __construct()
+    {
+    }
+
+    /**
+     * name of the record, as used in CNAME, etc.
+     */
+    public function getName(): ?string
+    {
+        return $this->name;
+    }
+
+    /**
+     * DNS record type.
+     * @return string|null
+     */
+    public function getType(): ?string
+    {
+        return $this->type;
+    }
+
+    /**
+     * DNS record value.
+     */
+    public function getValue(): ?string
+    {
+        return $this->value;
+    }
+
+    /**
+     * Record priority, used for MX.
+     */
+    public function getPriority(): ?string
+    {
+        return $this->priority;
+    }
+
+    /**
+     * DNS record has been added to domain DNS?
+     */
+    public function isValid(): bool
+    {
+        return 'valid' === $this->valid;
+    }
+
+    public function getValidity(): ?string
+    {
+        return $this->valid;
+    }
+
+    /**
+     * DNS record current value.
+     */
+    public function getCached(): array
+    {
+        return $this->cached;
+    }
+
+    /**
+     * @return bool
+     */
+    public function isActive(): bool
+    {
+        return $this->isActive;
+    }
+
+    /**
+     * @param bool $isActive
+     * @return void
+     */
+    public function setIsActive(bool $isActive): void
+    {
+        $this->isActive = $isActive;
+    }
+}

+ 253 - 0
vendor/mailgun/mailgun-php/src/Model/Domain/Domain.php

@@ -0,0 +1,253 @@
+<?php
+
+declare(strict_types=1);
+
+/*
+ * Copyright (C) 2013 Mailgun
+ *
+ * This software may be modified and distributed under the terms
+ * of the MIT license. See the LICENSE file for details.
+ */
+
+namespace Mailgun\Model\Domain;
+
+use DateTimeImmutable;
+
+/**
+ * Represents domain information in its simplest form.
+ *
+ * @author Sean Johnson <sean@ramcloud.io>
+ */
+final class Domain
+{
+    private $createdAt;
+    private ?string $smtpLogin;
+    private ?string $name;
+    private ?string $smtpPassword;
+    private $wildcard;
+    private ?string $spamAction;
+    private ?string $state;
+    private ?string $webScheme;
+    private ?string $webPrefix;
+    private string $type;
+    private bool $useAutomaticSenderSecurity;
+    private bool $requireTls;
+    private bool $skipVerification;
+    private string $id;
+    private bool $isDisabled;
+
+    /**
+     * @param array $data
+     * @return self
+     */
+    public static function create(array $data): self
+    {
+        $model = new self();
+        $model->name = $data['name'] ?? null;
+        $model->smtpLogin = $data['smtp_login'] ?? null;
+        $model->smtpPassword = $data['smtp_password'] ?? null;
+        $model->wildcard = $data['wildcard'] ?? null;
+        $model->spamAction = $data['spam_action'] ?? null;
+        $model->state = $data['state'] ?? null;
+        $model->createdAt = isset($data['created_at']) ? new DateTimeImmutable($data['created_at']) : null;
+        $model->webScheme = $data['web_scheme'] ?? null;
+        $model->webPrefix = $data['web_prefix'] ?? null;
+        $model->type = $data['type'] ?? 'sandbox';
+        $model->useAutomaticSenderSecurity = $data['use_automatic_sender_security'] ?? false;
+        $model->requireTls = $data['require_tls'] ?? false;
+        $model->skipVerification = $data['skip_verification'] ?? false;
+        $model->id = $data['id'] ?? '';
+        $model->isDisabled = $data['is_disabled'] ?? false;
+
+        return $model;
+    }
+
+    /**
+     * @return string|null
+     */
+    public function getName(): ?string
+    {
+        return $this->name;
+    }
+
+    /**
+     * @return string|null
+     */
+    public function getSmtpUsername(): ?string
+    {
+        return $this->smtpLogin;
+    }
+
+    /**
+     * @return string|null
+     */
+    public function getSmtpPassword(): ?string
+    {
+        return $this->smtpPassword;
+    }
+
+    /**
+     * @return bool|null
+     */
+    public function isWildcard(): ?bool
+    {
+        return $this->wildcard;
+    }
+
+    /**
+     * @return string|null
+     */
+    public function getSpamAction(): ?string
+    {
+        return $this->spamAction;
+    }
+
+    /**
+     * @return string|null
+     */
+    public function getState(): ?string
+    {
+        return $this->state;
+    }
+
+    /**
+     * @return DateTimeImmutable
+     */
+    public function getCreatedAt(): DateTimeImmutable
+    {
+        return $this->createdAt;
+    }
+
+    /**
+     * @return string|null
+     */
+    public function getWebScheme(): ?string
+    {
+        return $this->webScheme;
+    }
+
+    /**
+     * @param string|null $webScheme
+     * @return void
+     */
+    public function setWebScheme(?string $webScheme): void
+    {
+        $this->webScheme = $webScheme;
+    }
+
+    /**
+     * @return string|null
+     */
+    public function getWebPrefix(): ?string
+    {
+        return $this->webPrefix;
+    }
+
+    /**
+     * @param string|null $webPrefix
+     */
+    public function setWebPrefix(?string $webPrefix): void
+    {
+        $this->webPrefix = $webPrefix;
+    }
+
+    /**
+     * @return string
+     */
+    public function getType(): string
+    {
+        return $this->type;
+    }
+
+    /**
+     * @param string $type
+     */
+    public function setType(string $type): void
+    {
+        $this->type = $type;
+    }
+
+    /**
+     * @return bool
+     */
+    public function isUseAutomaticSenderSecurity(): bool
+    {
+        return $this->useAutomaticSenderSecurity;
+    }
+
+    /**
+     * @param bool $useAutomaticSenderSecurity
+     */
+    public function setUseAutomaticSenderSecurity(bool $useAutomaticSenderSecurity): void
+    {
+        $this->useAutomaticSenderSecurity = $useAutomaticSenderSecurity;
+    }
+
+    /**
+     * @return bool
+     */
+    public function isRequireTls(): bool
+    {
+        return $this->requireTls;
+    }
+
+    /**
+     * @param bool $requireTls
+     */
+    public function setRequireTls(bool $requireTls): void
+    {
+        $this->requireTls = $requireTls;
+    }
+
+    /**
+     * @return bool
+     */
+    public function isSkipVerification(): bool
+    {
+        return $this->skipVerification;
+    }
+
+    /**
+     * @param bool $skipVerification
+     */
+    public function setSkipVerification(bool $skipVerification): void
+    {
+        $this->skipVerification = $skipVerification;
+    }
+
+    /**
+     * @return string
+     */
+    public function getId(): string
+    {
+        return $this->id;
+    }
+
+    /**
+     * @param string $id
+     */
+    public function setId(string $id): void
+    {
+        $this->id = $id;
+    }
+
+    /**
+     * @return bool
+     */
+    public function isDisabled(): bool
+    {
+        return $this->isDisabled;
+    }
+
+    /**
+     * @param bool $isDisabled
+     */
+    public function setIsDisabled(bool $isDisabled): void
+    {
+        $this->isDisabled = $isDisabled;
+    }
+
+    private function __construct()
+    {
+    }
+}

+ 123 - 0
vendor/mailgun/mailgun-php/src/Model/Domain/DomainKeyResponse.php

@@ -0,0 +1,123 @@
+<?php
+
+declare(strict_types=1);
+
+/*
+ * Copyright (C) 2013 Mailgun
+ *
+ * This software may be modified and distributed under the terms
+ * of the MIT license. See the LICENSE file for details.
+ */
+
+namespace Mailgun\Model\Domain;
+
+use Mailgun\Model\ApiResponse;
+
+final class DomainKeyResponse implements ApiResponse
+{
+    private array $items = [];
+    private string $signingDomain;
+    private string $selector;
+    private DnsRecord $dnsRecord;
+
+    /**
+     * @param array $data
+     * @return self
+     */
+    public static function create(array $data): self
+    {
+        if (isset($data['items'])) {
+            $object = new self();
+            $items = [];
+            foreach ($data['items'] as $item) {
+                $model = new self();
+                $model->setSelector($item['selector'] ?? '');
+                $model->setSigningDomain($item['signing_domain'] ?? '');
+                if (!empty($item['dns_record'])) {
+                    $model->setDnsRecord(DnsRecord::create($item['dns_record']));
+                }
+
+                $items[] = $model;
+            }
+            $object->setItems($items);
+
+            return $object;
+        }
+
+        $model = new self();
+        $model->setSelector($data['selector'] ?? '');
+        $model->setDnsRecord(DnsRecord::create($data));
+        $model->setSigningDomain($data['signing_domain'] ?? '');
+
+        return $model;
+    }
+
+    /**
+     * @return string
+     */
+    public function getSigningDomain(): string
+    {
+        return $this->signingDomain;
+    }
+
+    /**
+     * @param string $signingDomain
+     */
+    public function setSigningDomain(string $signingDomain): void
+    {
+        $this->signingDomain = $signingDomain;
+    }
+
+    /**
+     * @return string
+     */
+    public function getSelector(): string
+    {
+        return $this->selector;
+    }
+
+    /**
+     * @param string $selector
+     */
+    public function setSelector(string $selector): void
+    {
+        $this->selector = $selector;
+    }
+
+    /**
+     * @return DnsRecord
+     */
+    public function getDnsRecord(): DnsRecord
+    {
+        return $this->dnsRecord;
+    }
+
+    /**
+     * @param DnsRecord $dnsRecord
+     */
+    public function setDnsRecord(DnsRecord $dnsRecord): void
+    {
+        $this->dnsRecord = $dnsRecord;
+    }
+
+    /**
+     * @return array
+     */
+    public function getItems(): array
+    {
+        return $this->items;
+    }
+
+    /**
+     * @param array $items
+     * @return void
+     */
+    public function setItems(array $items): void
+    {
+        $this->items = $items;
+    }
+
+    private function __construct()
+    {
+    }
+}

+ 88 - 0
vendor/mailgun/mailgun-php/src/Model/Domain/IndexResponse.php

@@ -0,0 +1,88 @@
+<?php
+
+declare(strict_types=1);
+
+/*
+ * Copyright (C) 2013 Mailgun
+ *
+ * This software may be modified and distributed under the terms
+ * of the MIT license. See the LICENSE file for details.
+ */
+
+namespace Mailgun\Model\Domain;
+
+use Mailgun\Model\ApiResponse;
+
+/**
+ * @author Sean Johnson <sean@mailgun.com>
+ */
+final class IndexResponse implements ApiResponse
+{
+    private int $totalCount;
+    private array $items;
+    private array $paging;
+
+    public static function create(array $data): self
+    {
+        $items = [];
+        $paging = [];
+
+        if (isset($data['items'])) {
+            foreach ($data['items'] as $item) {
+                $items[] = Domain::create($item);
+            }
+        }
+
+        if (isset($data['total_count'])) {
+            $count = $data['total_count'];
+        } else {
+            $count = count($items);
+        }
+
+        $paging = $data['paging'] ?? [];
+
+        $model = new self();
+        $model->totalCount = $count;
+        $model->items = $items;
+        $model->paging = $paging;
+
+        return $model;
+    }
+
+    private function __construct()
+    {
+    }
+
+    /**
+     * @return int
+     */
+    public function getTotalCount(): int
+    {
+        return $this->totalCount;
+    }
+
+    /**
+     * @return Domain[]
+     */
+    public function getDomains(): array
+    {
+        return $this->items;
+    }
+
+    /**
+     * @return array
+     */
+    public function getPaging(): array
+    {
+        return $this->paging;
+    }
+
+    /**
+     * @param array $paging
+     * @return void
+     */
+    public function setPaging(array $paging): void
+    {
+        $this->paging = $paging;
+    }
+}

+ 51 - 0
vendor/mailgun/mailgun-php/src/Model/Domain/OpenTracking.php

@@ -0,0 +1,51 @@
+<?php
+
+declare(strict_types=1);
+
+/*
+ * Copyright (C) 2013 Mailgun
+ *
+ * This software may be modified and distributed under the terms
+ * of the MIT license. See the LICENSE file for details.
+ */
+
+namespace Mailgun\Model\Domain;
+
+/**
+ * Represents a single Open Tracking setting for a domain tracking.
+ *
+ * @author Artem Bondarenko <artem@uartema.com>
+ */
+final class OpenTracking
+{
+    private ?string $active;
+
+    public static function create(array $data): self
+    {
+        $active = $data['active'] ?? null;
+        $model = new self();
+        $model->active = $active ? 'yes' : 'no';
+
+        return $model;
+    }
+
+    private function __construct()
+    {
+    }
+
+    /**
+     * @return string|null
+     */
+    public function getActive(): ?string
+    {
+        return $this->active;
+    }
+
+    /**
+     * @return bool
+     */
+    public function isActive(): bool
+    {
+        return 'yes' === $this->getActive();
+    }
+}

+ 82 - 0
vendor/mailgun/mailgun-php/src/Model/Domain/ShowResponse.php

@@ -0,0 +1,82 @@
+<?php
+
+declare(strict_types=1);
+
+/*
+ * Copyright (C) 2013 Mailgun
+ *
+ * This software may be modified and distributed under the terms
+ * of the MIT license. See the LICENSE file for details.
+ */
+
+namespace Mailgun\Model\Domain;
+
+use Mailgun\Model\ApiResponse;
+
+/**
+ * @author Sean Johnson <sean@mailgun.com>
+ */
+final class ShowResponse implements ApiResponse
+{
+    private ?Domain $domain;
+    private array $inboundDnsRecords;
+    private array $outboundDnsRecords;
+
+    public static function create(array $data): self
+    {
+        $rx = [];
+        $tx = [];
+        $domain = null;
+
+        if (isset($data['domain'])) {
+            $domain = Domain::create($data['domain']);
+        }
+
+        if (isset($data['receiving_dns_records'])) {
+            foreach ($data['receiving_dns_records'] as $item) {
+                $rx[] = DnsRecord::create($item);
+            }
+        }
+
+        if (isset($data['sending_dns_records'])) {
+            foreach ($data['sending_dns_records'] as $item) {
+                $tx[] = DnsRecord::create($item);
+            }
+        }
+
+        $model = new self();
+        $model->domain = $domain;
+        $model->inboundDnsRecords = $rx;
+        $model->outboundDnsRecords = $tx;
+
+        return $model;
+    }
+
+    private function __construct()
+    {
+    }
+
+    /**
+     * @return Domain|null
+     */
+    public function getDomain(): ?Domain
+    {
+        return $this->domain;
+    }
+
+    /**
+     * @return DnsRecord[]
+     */
+    public function getInboundDNSRecords(): array
+    {
+        return $this->inboundDnsRecords;
+    }
+
+    /**
+     * @return DnsRecord[]
+     */
+    public function getOutboundDNSRecords(): array
+    {
+        return $this->outboundDnsRecords;
+    }
+}

部分文件因为文件数量过多而无法显示