├── CHANGELOG.md ├── LICENSE.md ├── README.md ├── composer.json ├── phpstan-baseline.neon ├── phpstan.neon.dist └── src ├── Actions ├── ManagesCampaigns.php ├── ManagesEmailLists.php ├── ManagesSubscribers.php └── ManagesTransactionalMails.php ├── Exceptions ├── ActionFailed.php ├── InvalidData.php ├── ResourceNotFound.php └── Unauthorized.php ├── Mailcoach.php ├── MakesHttpRequests.php ├── Resources ├── ApiResource.php ├── Campaign.php ├── CampaignBounce.php ├── CampaignClick.php ├── CampaignOpen.php ├── CampaignUnsubscribe.php ├── EmailList.php ├── Subscriber.php ├── Tag.php ├── TransactionalMail.php └── TransactionalMailTemplate.php └── Support └── PaginatedResults.php /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to `mailcoach-sdk-php` will be documented in this file. 4 | 5 | ## 1.9.0 - 2025-05-05 6 | 7 | ### What's Changed 8 | 9 | * Add transactional-mails to endpoints 10 | * Bump dependabot/fetch-metadata from 2.2.0 to 2.3.0 by @dependabot in https://github.com/spatie/mailcoach-sdk-php/pull/44 11 | * Bump aglipanci/laravel-pint-action from 2.4 to 2.5 by @dependabot in https://github.com/spatie/mailcoach-sdk-php/pull/45 12 | 13 | **Full Changelog**: https://github.com/spatie/mailcoach-sdk-php/compare/1.8.1...1.9.0 14 | 15 | ## 1.8.1 - 2024-11-08 16 | 17 | ### What's Changed 18 | 19 | * Handle multiple test emails by @edalzell in https://github.com/spatie/mailcoach-sdk-php/pull/43 20 | 21 | ### New Contributors 22 | 23 | * @edalzell made their first contribution in https://github.com/spatie/mailcoach-sdk-php/pull/43 24 | 25 | **Full Changelog**: https://github.com/spatie/mailcoach-sdk-php/compare/1.8.0...1.8.1 26 | 27 | ## 1.8.0 - 2024-11-07 28 | 29 | * Fix `isSubscribed` returning `true` when subscriber wasn't confirmed 30 | * Add `isUnconfirmed()` method to subscriber resource 31 | 32 | **Full Changelog**: https://github.com/spatie/mailcoach-sdk-php/compare/1.7.1...1.7.2 33 | 34 | ## 1.7.1 - 2024-11-07 35 | 36 | * Don't double encode filter values as `http_build_query` already encodes 37 | 38 | **Full Changelog**: https://github.com/spatie/mailcoach-sdk-php/compare/1.7.0...1.7.1 39 | 40 | ## 1.7.0 - 2024-08-28 41 | 42 | ### What's Changed 43 | 44 | * removeTags & addTags functions added. by @meminuygur in https://github.com/spatie/mailcoach-sdk-php/pull/42 45 | 46 | **Full Changelog**: https://github.com/spatie/mailcoach-sdk-php/compare/1.6.0...1.7.0 47 | 48 | ## 1.6.0 - 2024-07-15 49 | 50 | ### What's Changed 51 | 52 | * Bump ramsey/composer-install from 2 to 3 by @dependabot in https://github.com/spatie/mailcoach-sdk-php/pull/30 53 | * Bump dependabot/fetch-metadata from 1.6.0 to 2.1.0 by @dependabot in https://github.com/spatie/mailcoach-sdk-php/pull/36 54 | * Bump dependabot/fetch-metadata from 2.1.0 to 2.2.0 by @dependabot in https://github.com/spatie/mailcoach-sdk-php/pull/39 55 | * added Unsubscribe by Email method by @meminuygur in https://github.com/spatie/mailcoach-sdk-php/pull/40 56 | 57 | ### New Contributors 58 | 59 | * @meminuygur made their first contribution in https://github.com/spatie/mailcoach-sdk-php/pull/40 60 | 61 | **Full Changelog**: https://github.com/spatie/mailcoach-sdk-php/compare/1.5.0...1.6.0 62 | 63 | ## 1.5.0 - 2024-05-02 64 | 65 | ### What's Changed 66 | 67 | * Bump aglipanci/laravel-pint-action from 2.3.1 to 2.4 by @dependabot in https://github.com/spatie/mailcoach-sdk-php/pull/33 68 | * Fix #34 -- Added a isSubscribed method on the Subscriber resource by @philipsorensen in https://github.com/spatie/mailcoach-sdk-php/pull/37 69 | 70 | ### New Contributors 71 | 72 | * @philipsorensen made their first contribution in https://github.com/spatie/mailcoach-sdk-php/pull/37 73 | 74 | **Full Changelog**: https://github.com/spatie/mailcoach-sdk-php/compare/1.4.1...1.5.0 75 | 76 | ## 1.4.1 - 2024-04-12 77 | 78 | ### What's Changed 79 | 80 | * Fix data retrieval when creating a campaign by @mariomka in https://github.com/spatie/mailcoach-sdk-php/pull/32 81 | 82 | ### New Contributors 83 | 84 | * @mariomka made their first contribution in https://github.com/spatie/mailcoach-sdk-php/pull/32 85 | 86 | **Full Changelog**: https://github.com/spatie/mailcoach-sdk-php/compare/1.4.0...1.4.1 87 | 88 | ## 1.4.0 - 2024-02-09 89 | 90 | ### What's Changed 91 | 92 | * Tags: GET, UPDATE & DELETE endpoints by @Nielsvanpach in https://github.com/spatie/mailcoach-sdk-php/pull/24 93 | 94 | **Full Changelog**: https://github.com/spatie/mailcoach-sdk-php/compare/1.3.1...1.4.0 95 | 96 | ## 1.3.1 - 2024-02-08 97 | 98 | ### What's Changed 99 | 100 | * Bump aglipanci/laravel-pint-action from 2.3.0 to 2.3.1 by @dependabot in https://github.com/spatie/mailcoach-sdk-php/pull/25 101 | * use correct key for previous url by @Nielsvanpach in https://github.com/spatie/mailcoach-sdk-php/pull/28 102 | 103 | **Full Changelog**: https://github.com/spatie/mailcoach-sdk-php/compare/1.3.0...1.3.1 104 | 105 | ## 1.3.0 - 2023-11-03 106 | 107 | ### What's Changed 108 | 109 | - Bump actions/checkout from 3 to 4 by @dependabot in https://github.com/spatie/mailcoach-sdk-php/pull/20 110 | - Bump stefanzweifel/git-auto-commit-action from 4 to 5 by @dependabot in https://github.com/spatie/mailcoach-sdk-php/pull/22 111 | - Send transaction Mail by @Nielsvanpach in https://github.com/spatie/mailcoach-sdk-php/pull/23 112 | 113 | ### New Contributors 114 | 115 | - @Nielsvanpach made their first contribution in https://github.com/spatie/mailcoach-sdk-php/pull/23 116 | 117 | **Full Changelog**: https://github.com/spatie/mailcoach-sdk-php/compare/1.2.0...1.3.0 118 | 119 | ## 1.2.0 - 2023-08-24 120 | 121 | ### What's Changed 122 | 123 | - retrieve opens, clicks, unsubscribes and bounces for a given campaign by @smknstd in https://github.com/spatie/mailcoach-sdk-php/pull/19 124 | 125 | ### New Contributors 126 | 127 | - @smknstd made their first contribution in https://github.com/spatie/mailcoach-sdk-php/pull/19 128 | 129 | **Full Changelog**: https://github.com/spatie/mailcoach-sdk-php/compare/1.1.2...1.2.0 130 | 131 | ## 1.1.2 - 2023-07-17 132 | 133 | ### What's Changed 134 | 135 | - Bump dependabot/fetch-metadata from 1.5.1 to 1.6.0 by @dependabot in https://github.com/spatie/mailcoach-sdk-php/pull/17 136 | - fix: Problems with finding subscribers with "+" character in their emails by @ImJustToNy in https://github.com/spatie/mailcoach-sdk-php/pull/18 137 | 138 | ### New Contributors 139 | 140 | - @ImJustToNy made their first contribution in https://github.com/spatie/mailcoach-sdk-php/pull/18 141 | 142 | **Full Changelog**: https://github.com/spatie/mailcoach-sdk-php/compare/1.1.1...1.1.2 143 | 144 | ## 1.1.1 - 2023-06-28 145 | 146 | ### What's Changed 147 | 148 | - URLencode filter values by @tinusg in https://github.com/spatie/mailcoach-sdk-php/pull/16 149 | 150 | ### New Contributors 151 | 152 | - @tinusg made their first contribution in https://github.com/spatie/mailcoach-sdk-php/pull/16 153 | 154 | **Full Changelog**: https://github.com/spatie/mailcoach-sdk-php/compare/1.1.0...1.1.1 155 | 156 | ## 1.1.0 - 2023-02-09 157 | 158 | - add resubscribe method as requested in https://github.com/spatie/laravel-mailcoach-sdk/issues/10 159 | 160 | **Full Changelog**: https://github.com/spatie/mailcoach-sdk-php/compare/1.0.5...1.1.0 161 | 162 | ## 1.0.5 - 2023-02-02 163 | 164 | ### What's Changed 165 | 166 | - fix missing attributes in subscriber class 167 | - Bump dependabot/fetch-metadata from 1.3.5 to 1.3.6 by @dependabot in https://github.com/spatie/mailcoach-sdk-php/pull/8 168 | 169 | **Full Changelog**: https://github.com/spatie/mailcoach-sdk-php/compare/1.0.4...1.0.5 170 | 171 | ## 1.0.4 - 2023-01-19 172 | 173 | - make mailers optional 174 | 175 | **Full Changelog**: https://github.com/spatie/mailcoach-sdk-php/compare/1.0.3...1.0.4 176 | 177 | ## 1.0.3 - 2023-01-04 178 | 179 | ### What's Changed 180 | 181 | - Prevent infinite loop and use correct `subscribers` method by @ash-jc-allen in https://github.com/spatie/mailcoach-sdk-php/pull/6 182 | 183 | ### New Contributors 184 | 185 | - @ash-jc-allen made their first contribution in https://github.com/spatie/mailcoach-sdk-php/pull/6 186 | 187 | **Full Changelog**: https://github.com/spatie/mailcoach-sdk-php/compare/1.0.2...1.0.3 188 | 189 | ## 1.0.2 - 2022-11-25 190 | 191 | - fix subscriber endpoint 192 | 193 | ## 1.0.1 - 2022-11-08 194 | 195 | - correct exception names 196 | 197 | ## 1.0.0 - 2022-11-08 198 | 199 | - initial release 200 | 201 | ## 0.0.3 - 2022-11-08 202 | 203 | - experimental release 204 | 205 | ## 0.0.2 - 2022-11-08 206 | 207 | - experimental release 208 | 209 | ## 0.0.1 - 2022-11-07 210 | 211 | - experimental release 212 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) spatie 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # An SDK for using the Mailcoach API in PHP 2 | 3 | [![Latest Version on Packagist](https://img.shields.io/packagist/v/spatie/mailcoach-sdk-php.svg?style=flat-square)](https://packagist.org/packages/spatie/mailcoach-sdk-php) 4 | [![Tests](https://github.com/spatie/mailcoach-sdk-php/actions/workflows/run-tests.yml/badge.svg?branch=main)](https://github.com/spatie/mailcoach-sdk-php/actions/workflows/run-tests.yml) 5 | [![PHPStan](https://github.com/spatie/mailcoach-sdk-php/actions/workflows/phpstan.yml/badge.svg)](https://github.com/spatie/mailcoach-sdk-php/actions/workflows/phpstan.yml) 6 | [![Total Downloads](https://img.shields.io/packagist/dt/spatie/mailcoach-sdk-php.svg?style=flat-square)](https://packagist.org/packages/spatie/mailcoach-sdk-php) 7 | 8 | This package contains the PHP SDK to work with [Mailcoach](https://mailcoach.app). Both self-hosted (v6 and up) and hosted Mailcoach (aka Mailcoach Cloud) are supported. Using this package you can manage email lists, subscribers and campaigns. 9 | 10 | Here are a few examples: 11 | 12 | ```php 13 | $mailcoach = new \Spatie\MailcoachSdk\Mailcoach('', '') 14 | 15 | // creating a campaign 16 | $campaign = $mailcoach->createCampaign([ 17 | 'email_list_uuid' => 'use-a-real-email-list-uuid-here', 18 | 'name' => 'My new campaign', 19 | 'fields' => [ 20 | 'title' => 'The title on top of the newsletter', 21 | 'content' => '# Welcome to my newsletter', 22 | ], 23 | ]); 24 | 25 | // sending a test of the campaign to the given email address 26 | $campaign->sendTest('john@example.com'); 27 | 28 | // sending a campaign 29 | $campaign->send(); 30 | ``` 31 | 32 | By default, Mailcoach' endpoints will are paginated with a limit of 1000. The package makes it easy to work with paginated resources. Just call `->next()` to get the next page. 33 | 34 | ```php 35 | // listing all subscribers of a list 36 | $subscribers = $mailcoach->emailList('use-a-real-email-list-uuid-here')->subscribers(); 37 | 38 | do { 39 | foreach($subscribers as $subscriber) { 40 | echo $subscriber->email; 41 | } 42 | } while($subscribers = $subscribers->next()) 43 | ``` 44 | 45 | ## Support us 46 | 47 | [](https://spatie.be/github-ad-click/mailcoach-sdk-php) 48 | 49 | We invest a lot of resources into creating [best in class open source packages](https://spatie.be/open-source). You can support us by [buying one of our paid products](https://spatie.be/open-source/support-us). 50 | 51 | We highly appreciate you sending us a postcard from your hometown, mentioning which of our package(s) you are using. You'll find our address on [our contact page](https://spatie.be/about-us). We publish all received postcards on [our virtual postcard wall](https://spatie.be/open-source/postcards). 52 | 53 | ## Installation 54 | 55 | You can install the package via composer: 56 | 57 | ```bash 58 | composer require spatie/mailcoach-sdk-php 59 | ``` 60 | 61 | You must also install Guzzle 62 | 63 | ```bash 64 | composer require guzzlehttp/guzzle 65 | ``` 66 | 67 | ## Usage 68 | 69 | To get started, you must first new up an instance of `Spatie\MailcoachSdk\Mailcoach` 70 | 71 | ```php 72 | use Spatie\MailcoachSdk\Mailcoach; 73 | 74 | $mailcoach = new Mailcoach('', '') 75 | ``` 76 | 77 | You can find both the API key and the Mailcoach API endpoint in the "API Tokens" screen of the Mailcoach settings. 78 | 79 | ### Handling pagination 80 | 81 | There are several methods, such as `emailLists()`, 'subscribers()' and `campaigns()` to will return paginated results. To get the next page of results just call `next()` on a result. If there are no more results, that method returns `null`. 82 | 83 | Here's how you display the email addresses of every subscriber on a list 84 | 85 | ```php 86 | $subscribers = $mailcoach->subscribers('email; 91 | } 92 | } while($subscribers = $subscribers->next()) 93 | ``` 94 | 95 | On paginated results, `$subscribers` in the example above there are also some more convenience methods: 96 | 97 | - `results()`: get the results. A results object is also iterable, so you can also get to the results by simply using the object in a loop 98 | - `next()`: fetch the next page of results 99 | - `previous()`: fetch the previous page of results 100 | - `currentPage()`: get the current page number 101 | - `total()`: get the total number of results across all pages 102 | - `nextUrl()`: get the URL that will be called to get the next page of results 103 | - `previousUrl()`: get the URL that will be called to get the previous page of results 104 | 105 | ### Working with email lists 106 | 107 | Here's how to get all email lists: 108 | 109 | ```php 110 | $emailLists = $mailcoach->emailLists(); 111 | ``` 112 | 113 | You can get a single email list: 114 | 115 | ```php 116 | $emailList = $mailcoach->emailList(''); 117 | ``` 118 | 119 | This is how you can create an email list: 120 | 121 | ```php 122 | $mailcoach->createEmailList(['name' => 'My new email list']); 123 | ``` 124 | 125 | You can get properties of email list: 126 | 127 | ```php 128 | $emailList->name; 129 | $emailList->uuid; 130 | // ... 131 | ``` 132 | 133 | Take a look at the source code of `Spatie\MailcoachSdk\Resources\EmailList` to see the list of available properties. 134 | 135 | You can update an email list by change one of the properties and calling `save()`. 136 | 137 | ```php 138 | $emailList->name = 'Updated name'; 139 | $emailList->save(); 140 | ``` 141 | 142 | You can delete an email list by calling `delete()`. 143 | 144 | ```php 145 | $emailList->delete(); 146 | ``` 147 | 148 | ### Working with subscribers 149 | 150 | To get all subscribers of a list, you can call `subscribers()` on an email list. 151 | 152 | ```php 153 | $subscribers = $mailcoach 154 | ->emailList('') 155 | ->subscribers(); 156 | ``` 157 | 158 | Optionally, you can pass filters to `subscribers()`. Here how to get all subscribers with a Gmail-address. 159 | 160 | ```php 161 | $subscribers = $mailcoach 162 | ->emailList('') 163 | ->subscribers(['email' => 'gmail.com']); 164 | ``` 165 | 166 | Alternatively, you can call `subscribers()` on `$mailcoach` 167 | 168 | ```php 169 | $subscribers = $mailcoach->subscribers('', $optionalFilters); 170 | ``` 171 | 172 | There's also a convenience method to quickly get a subscriber from a list. 173 | 174 | ```php 175 | // returns instance of Spatie\MailcoachSdk\Resources\Subscriber 176 | // or null if the subscriber does not exist. 177 | 178 | $subscriber = $emaillist->subscriber('john@example.com'); 179 | ``` 180 | Alternatively, you can get a subscriber by its UUID: 181 | 182 | ```php 183 | $subscriber = $mailcoach->subscriber(''); 184 | ``` 185 | 186 | This how you can create a subscriber: 187 | 188 | ```php 189 | $subscriber = $mailcoach->createSubscriber('', [ 190 | 'email' => 'john@example.com', 191 | ]); 192 | ``` 193 | 194 | You can get properties of a subscriber: 195 | 196 | ```php 197 | $subscriber->firstName; 198 | $subscriber->email; 199 | // ... 200 | ``` 201 | 202 | Take a look at the source code of `Spatie\MailcoachSdk\Resources\Subscriber` to see the list of available properties. 203 | 204 | You can update a subscriber by change one of the properties and calling `save()`. 205 | 206 | ```php 207 | $subscriber->firstName = 'Updated name'; 208 | $subscriber->save(); 209 | ``` 210 | 211 | You can confirm, unsubscribe, resubscribe, delete a subscriber, and add or remove tags by calling these methods: 212 | ```php 213 | $subscriber->confirm(); 214 | $subscriber->unsubscribe(); 215 | $subscriber->resubscribe(); 216 | $subscriber->delete(); 217 | $subscriber->addTags(['abandoned-cart', 'product-updates']); 218 | $subscriber->removeTags(['abandoned-cart', 'product-updates']); 219 | ``` 220 | 221 | ### Working with campaigns 222 | 223 | Here's how to get all campaigns. 224 | 225 | ```php 226 | $campaigns = $mailcoach->campaigns(); 227 | ``` 228 | 229 | You can also get a single campaign(); 230 | 231 | ```php 232 | $campaign = $mailcoach->campaign(''); 233 | ``` 234 | 235 | This is how you can create a campaign: 236 | 237 | ```php 238 | $campaign = $this->createCampaign([ 239 | 'name' => 'My new campaign', 240 | 'subject' => 'Here is some fantastic content for you', 241 | 'email_list_uuid' => '', 242 | 243 | // optionally, you can specify the uuid of a template 244 | 'template_uuid' => '', 245 | 246 | // if that template has field, you can pass the values 247 | // in the `fields` array. If you use the markdown editor, 248 | // we'll automatically handle any passed markdown 249 | 'fields' => [ 250 | 'title' => 'Content for the title place holder', 251 | 'content' => '# My title', 252 | ], 253 | ]); 254 | ``` 255 | 256 | You can get properties of a campaign: 257 | 258 | ```php 259 | $campaign->name; 260 | $campaign->subject; 261 | // ... 262 | ``` 263 | 264 | Take a look at the source code of `Spatie\MailcoachSdk\Resources\Campaign` to see the list of available properties. 265 | 266 | You can update a campaign by change one of the properties and calling `save()`. 267 | 268 | ```php 269 | $campaign->name = 'Campaign'; 270 | $campaign->save(); 271 | ``` 272 | 273 | A test mail will be sent when calling `sendTest()`: 274 | 275 | ```php 276 | // sending a test to a single person 277 | $campaign->sendTest('john@example.com'); 278 | 279 | // sending a test to multiple persons 280 | $campaign->sendTest(['john@example.com', 'jane@example.com']); 281 | ``` 282 | 283 | The campaign will be sent to all subscribers of your list, by calling `send()`: 284 | 285 | ```php 286 | $campaign->send(); 287 | ``` 288 | 289 | A campaign can be deleted: 290 | 291 | ```php 292 | $campaign->delete(); 293 | ``` 294 | 295 | ## Testing 296 | 297 | ```php 298 | composer test 299 | ``` 300 | 301 | ## Changelog 302 | 303 | Please see [CHANGELOG](CHANGELOG.md) for more information on what has changed recently. 304 | 305 | ## Contributing 306 | 307 | Please see [CONTRIBUTING](https://github.com/spatie/.github/blob/main/CONTRIBUTING.md) for details. 308 | 309 | ## Security Vulnerabilities 310 | 311 | Please review [our security policy](../../security/policy) on how to report security vulnerabilities. 312 | 313 | ## Credits 314 | 315 | - [Freek Van der Herten](https://github.com/freekmurze) 316 | - [All Contributors](../../contributors) 317 | 318 | ## License 319 | 320 | The MIT License (MIT). Please see [License File](LICENSE.md) for more information. 321 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "spatie/mailcoach-sdk-php", 3 | "description": "An SDK for using the Mailcoach API in PHP", 4 | "keywords": [ 5 | "spatie", 6 | "mailcoach-sdk-php" 7 | ], 8 | "homepage": "https://github.com/spatie/mailcoach-sdk-php", 9 | "license": "MIT", 10 | "authors": [ 11 | { 12 | "name": "Freek Van der Herten", 13 | "email": "freek@spatie.be", 14 | "role": "Developer" 15 | } 16 | ], 17 | "require": { 18 | "php": "^8.1" 19 | }, 20 | "require-dev": { 21 | "guzzlehttp/guzzle": "^7.0", 22 | "laravel/pint": "^1.2", 23 | "pestphp/pest": "^1.20", 24 | "pestphp/pest-plugin-mock": "^1.0", 25 | "phpstan/phpstan": "^1.9", 26 | "spatie/invade": "^2.1", 27 | "spatie/ray": "^1.28", 28 | "tomb1n0/guzzle-mock-handler": "^1.7", 29 | "vlucas/phpdotenv": "^5.5" 30 | }, 31 | "suggest": { 32 | "guzzlehttp/guzzle": "Make HTTP requests" 33 | }, 34 | "autoload": { 35 | "psr-4": { 36 | "Spatie\\MailcoachSdk\\": "src" 37 | } 38 | }, 39 | "autoload-dev": { 40 | "psr-4": { 41 | "Spatie\\MailcoachSdk\\Tests\\": "tests" 42 | } 43 | }, 44 | "scripts": { 45 | "test": "vendor/bin/pest", 46 | "analyse": "vendor/bin/phpstan", 47 | "test-coverage": "vendor/bin/pest --coverage", 48 | "format": "vendor/bin/pint" 49 | }, 50 | "config": { 51 | "sort-packages": true, 52 | "allow-plugins": { 53 | "phpstan/extension-installer": true, 54 | "pestphp/pest-plugin": true 55 | } 56 | }, 57 | "minimum-stability": "dev", 58 | "prefer-stable": true 59 | } 60 | -------------------------------------------------------------------------------- /phpstan-baseline.neon: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spatie/mailcoach-sdk-php/126e27f42d087b3d5436c3508f5cf96979312edd/phpstan-baseline.neon -------------------------------------------------------------------------------- /phpstan.neon.dist: -------------------------------------------------------------------------------- 1 | includes: 2 | - phpstan-baseline.neon 3 | 4 | parameters: 5 | level: 5 6 | paths: 7 | - src 8 | tmpDir: build/phpstan 9 | -------------------------------------------------------------------------------- /src/Actions/ManagesCampaigns.php: -------------------------------------------------------------------------------- 1 | get("campaigns/{$uuid}")['data']; 26 | 27 | return new Campaign($attributes, $this); 28 | } 29 | 30 | public function createCampaign(array $data): Campaign 31 | { 32 | $attributes = $this->post('campaigns', $data)['data']; 33 | 34 | return new Campaign($attributes, $this); 35 | } 36 | 37 | public function updateCampaign(string $uuid, array $data): Campaign 38 | { 39 | $attributes = $this->put("campaigns/{$uuid}", $data)['data']; 40 | 41 | return new Campaign($attributes, $this); 42 | } 43 | 44 | public function deleteCampaign(string $campaignUuid): void 45 | { 46 | $this->delete("campaigns/{$campaignUuid}"); 47 | } 48 | 49 | public function sendTest(string $campaignUuid, string|array $email): void 50 | { 51 | if (is_array($email)) { 52 | $email = implode(',', $email); 53 | } 54 | 55 | $this->post("campaigns/{$campaignUuid}/send-test", ['email' => $email]); 56 | } 57 | 58 | public function send(string $campaignUuid): void 59 | { 60 | $this->post("campaigns/{$campaignUuid}/send"); 61 | } 62 | 63 | public function campaignOpens(string $campaignUuid): PaginatedResults 64 | { 65 | return PaginatedResults::make( 66 | "campaigns/{$campaignUuid}/opens", 67 | CampaignOpen::class, 68 | $this, 69 | ); 70 | } 71 | 72 | public function campaignClicks(string $campaignUuid): PaginatedResults 73 | { 74 | return PaginatedResults::make( 75 | "campaigns/{$campaignUuid}/clicks", 76 | CampaignClick::class, 77 | $this, 78 | ); 79 | } 80 | 81 | public function campaignUnsubscribes(string $campaignUuid): PaginatedResults 82 | { 83 | return PaginatedResults::make( 84 | "campaigns/{$campaignUuid}/unsubscribes", 85 | CampaignUnsubscribe::class, 86 | $this, 87 | ); 88 | } 89 | 90 | public function campaignBounces(string $campaignUuid): PaginatedResults 91 | { 92 | return PaginatedResults::make( 93 | "campaigns/{$campaignUuid}/bounces", 94 | CampaignBounce::class, 95 | $this, 96 | ); 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /src/Actions/ManagesEmailLists.php: -------------------------------------------------------------------------------- 1 | $filters 13 | * @return PaginatedResults 14 | */ 15 | public function emailLists(array $filters = []): PaginatedResults 16 | { 17 | return PaginatedResults::make( 18 | "email-lists{$this->buildFilterString($filters)}", 19 | EmailList::class, 20 | $this, 21 | ); 22 | } 23 | 24 | public function createEmailList(array $data): EmailList 25 | { 26 | $attributes = $this->post('email-lists', $data); 27 | 28 | return new EmailList($attributes, $this); 29 | } 30 | 31 | public function emailList(string $uuid): EmailList 32 | { 33 | $attributes = $this->get("email-lists/{$uuid}")['data']; 34 | 35 | return new EmailList($attributes, $this); 36 | } 37 | 38 | public function updateEmailList(string $uuid, array $data): EmailList 39 | { 40 | $attributes = $this->put("email-lists/{$uuid}", $data)['data']; 41 | 42 | return new EmailList($attributes, $this); 43 | } 44 | 45 | public function deleteEmailList(string $uuid): void 46 | { 47 | $this->delete("email-lists/{$uuid}"); 48 | } 49 | 50 | public function addTagToEmailList(string $uuid, array $data): Tag 51 | { 52 | $attributes = $this->post("email-lists/{$uuid}/tags", $data); 53 | 54 | return new Tag($attributes, $this); 55 | } 56 | 57 | public function updateTagOnEmailList(string $emailListUuid, string $tagUuid, array $data): Tag 58 | { 59 | $attributes = $this->put("email-lists/{$emailListUuid}/tags/{$tagUuid}", $data)['data']; 60 | 61 | return new Tag($attributes, $this); 62 | } 63 | 64 | public function deleteTagFromEmailList(string $emailListUuid, string $tagUuid): void 65 | { 66 | $this->delete("email-lists/{$emailListUuid}/tags/{$tagUuid}"); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/Actions/ManagesSubscribers.php: -------------------------------------------------------------------------------- 1 | $filters 12 | * @return PaginatedResults 13 | */ 14 | public function subscribers(string $emailListUuid, array $filters = []): PaginatedResults 15 | { 16 | return PaginatedResults::make( 17 | "email-lists/{$emailListUuid}/subscribers{$this->buildFilterString($filters)}", 18 | Subscriber::class, 19 | $this, 20 | ); 21 | } 22 | 23 | public function subscriber(string $uuid): Subscriber 24 | { 25 | $attributes = $this->get("subscribers/{$uuid}")['data']; 26 | 27 | return new Subscriber($attributes, $this); 28 | } 29 | 30 | public function createSubscriber(string $emailListUuid, array $attributes): Subscriber 31 | { 32 | $attributes = $this->post("email-lists/{$emailListUuid}/subscribers", $attributes)['data']; 33 | 34 | return new Subscriber($attributes, $this); 35 | } 36 | 37 | public function findByEmail(string $emailListUuid, string $email): ?Subscriber 38 | { 39 | $email = urlencode($email); 40 | 41 | $subscribers = $this->get("email-lists/{$emailListUuid}/subscribers?filter[email]={$email}")['data']; 42 | 43 | if (count($subscribers) === 0) { 44 | return null; 45 | } 46 | 47 | return new Subscriber($subscribers[0], $this); 48 | } 49 | 50 | public function updateSubscriber(string $subscriberUuid, array $attributes): Subscriber 51 | { 52 | $attributes = $this->patch("subscribers/{$subscriberUuid}", $attributes)['data']; 53 | 54 | return new Subscriber($attributes, $this); 55 | } 56 | 57 | public function deleteSubscriber(string $subscriberUuid): void 58 | { 59 | $this->delete("subscribers/{$subscriberUuid}"); 60 | } 61 | 62 | public function confirmSubscriber(string $subscriberUuid): void 63 | { 64 | $this->post("subscribers/{$subscriberUuid}/confirm"); 65 | } 66 | 67 | public function unsubscribeSubscriber(string $subscriberUuid): void 68 | { 69 | $this->post("subscribers/{$subscriberUuid}/unsubscribe"); 70 | } 71 | 72 | public function resubscribeSubscriber(string $subscriberUuid): void 73 | { 74 | $this->post("subscribers/{$subscriberUuid}/resubscribe"); 75 | } 76 | 77 | public function resendConfirmation(string $subscriberUuid): void 78 | { 79 | $this->post("subscribers/{$subscriberUuid}/resend-confirmation"); 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/Actions/ManagesTransactionalMails.php: -------------------------------------------------------------------------------- 1 | buildFilterString($filters)})", 18 | TransactionalMail::class, 19 | $this, 20 | ); 21 | } 22 | 23 | public function transactionalMail(string $uuid): TransactionalMail 24 | { 25 | $attributes = $this->get("transactional-mails/{$uuid}")['data']; 26 | 27 | return new TransactionalMail($attributes, $this); 28 | } 29 | 30 | public function transactionalMailTemplates(array $filters = []): PaginatedResults 31 | { 32 | return PaginatedResults::make( 33 | "transactional-mails/templates{$this->buildFilterString($filters)})", 34 | TransactionalMailTemplate::class, 35 | $this, 36 | ); 37 | } 38 | 39 | public function transactionalMailTemplate(string $uuid): TransactionalMailTemplate 40 | { 41 | $attributes = $this->get("transactional-mails/templates/{$uuid}")['data']; 42 | 43 | return new TransactionalMailTemplate($attributes, $this); 44 | } 45 | 46 | public function sendTransactionMail( 47 | string $name, 48 | string $from, 49 | string $to, 50 | /** @param array $replacements */ 51 | ?array $replacements = null, 52 | /** @param array{'name': string, 'content': string, 'content_type': string, 'content_id': ?string} $attachments */ 53 | ?array $attachments = null, 54 | ?string $subject = null, 55 | ?string $cc = null, 56 | ?string $bcc = null, 57 | ?string $replyTo = null, 58 | ?bool $fake = null, 59 | ?bool $store = null 60 | ): void { 61 | $this->post('transactional-mails/send', [ 62 | 'mail_name' => $name, 63 | 'from' => $from, 64 | 'to' => $to, 65 | 'cc' => $cc, 66 | 'bcc' => $bcc, 67 | 'reply_to' => $replyTo, 68 | 'replacements' => $replacements, 69 | 'attachments' => $attachments, 70 | 'subject' => $subject, 71 | 'fake' => $fake, 72 | 'store' => $store, 73 | ]); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/Exceptions/ActionFailed.php: -------------------------------------------------------------------------------- 1 | errors = $errors; 14 | 15 | parent::__construct('The given data failed to pass validation. '.print_r($this->errors, true)); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/Exceptions/ResourceNotFound.php: -------------------------------------------------------------------------------- 1 | client ??= new Client([ 26 | 'http_errors' => false, 27 | 'base_uri' => $this->mailcoachEndpoint.'/', 28 | 'headers' => [ 29 | 'Authorization' => "Bearer {$this->apiToken}", 30 | 'Accept' => 'application/json', 31 | 'Content-Type' => 'application/json', 32 | ], 33 | ]); 34 | } 35 | 36 | public function apiToken(): string 37 | { 38 | return $this->apiToken; 39 | } 40 | 41 | public function endpoint(): string 42 | { 43 | return $this->mailcoachEndpoint; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/MakesHttpRequests.php: -------------------------------------------------------------------------------- 1 | request('GET', $uri); 17 | } 18 | 19 | public function post(string $uri, array $payload = []) 20 | { 21 | return $this->request('POST', $uri, $payload); 22 | } 23 | 24 | public function put(string $uri, array $payload = []) 25 | { 26 | return $this->request('PUT', $uri, $payload); 27 | } 28 | 29 | public function patch(string $uri, array $payload = []) 30 | { 31 | return $this->request('PATCH', $uri, $payload); 32 | } 33 | 34 | public function delete(string $uri, array $payload = []) 35 | { 36 | return $this->request('DELETE', $uri, $payload); 37 | } 38 | 39 | public function request(string $verb, string $uri, array $payload = []): mixed 40 | { 41 | $response = $this->client->request( 42 | $verb, 43 | $uri, 44 | empty($payload) ? [] : ['form_params' => $payload] 45 | ); 46 | 47 | if (! $this->isSuccessful($response)) { 48 | $this->handleRequestError($response); 49 | 50 | return null; 51 | } 52 | 53 | $responseBody = (string) $response->getBody(); 54 | 55 | return json_decode($responseBody, true) ?: $responseBody; 56 | } 57 | 58 | public function isSuccessful($response): bool 59 | { 60 | if (! $response) { 61 | return false; 62 | } 63 | 64 | return (int) substr($response->getStatusCode(), 0, 1) === 2; 65 | } 66 | 67 | protected function buildFilterString(array $filters): string 68 | { 69 | if (count($filters) === 0) { 70 | return ''; 71 | } 72 | 73 | $preparedFilters = []; 74 | foreach ($filters as $name => $value) { 75 | $preparedFilters["filter[{$name}]"] = $value; 76 | } 77 | 78 | return '?'.http_build_query($preparedFilters); 79 | } 80 | 81 | protected function handleRequestError(ResponseInterface $response): void 82 | { 83 | if ($response->getStatusCode() === 422) { 84 | throw new InvalidData(json_decode((string) $response->getBody(), true)); 85 | } 86 | 87 | if ($response->getStatusCode() === 404) { 88 | throw new ResourceNotFound; 89 | } 90 | 91 | if ($response->getStatusCode() === 400) { 92 | throw new ActionFailed((string) $response->getBody()); 93 | } 94 | 95 | if ($response->getStatusCode() === 401) { 96 | throw new Unauthorized((string) $response->getBody()); 97 | } 98 | 99 | throw new Exception((string) $response->getBody()); 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /src/Resources/ApiResource.php: -------------------------------------------------------------------------------- 1 | attributes = $attributes; 17 | 18 | $this->mailcoach = $mailcoach; 19 | 20 | $this->fill(); 21 | } 22 | 23 | protected function fill(): void 24 | { 25 | foreach ($this->attributes as $key => $value) { 26 | $key = $this->camelCase($key); 27 | 28 | $this->{$key} = $value; 29 | } 30 | } 31 | 32 | public function toArray(): array 33 | { 34 | $publicProperties = get_object_vars($this); 35 | unset($publicProperties['attributes']); 36 | unset($publicProperties['mailcoach']); 37 | 38 | $properties = []; 39 | 40 | foreach ($publicProperties as $key => $value) { 41 | $properties[$this->snakeCase($key)] = $value; 42 | } 43 | 44 | return $properties; 45 | } 46 | 47 | protected function camelCase(string $string): string 48 | { 49 | $parts = explode('_', $string); 50 | 51 | foreach ($parts as $i => $part) { 52 | if ($i !== 0) { 53 | $parts[$i] = ucfirst($part); 54 | } 55 | } 56 | 57 | return str_replace(' ', '', implode(' ', $parts)); 58 | } 59 | 60 | protected function snakeCase(string $string): string 61 | { 62 | return strtolower(preg_replace('/(?mailcoach->updateCampaign($this->uuid, $this->toArray()); 76 | 77 | $this->attributes = $campaign->toArray(); 78 | 79 | $this->fill(); 80 | 81 | return $this; 82 | } 83 | 84 | public function delete(): self 85 | { 86 | $this->mailcoach->deleteCampaign($this->uuid); 87 | 88 | return $this; 89 | } 90 | 91 | public function sendTest(string|array $email): self 92 | { 93 | $this->mailcoach->sendTest($this->uuid, $email); 94 | 95 | return $this; 96 | } 97 | 98 | public function send(): self 99 | { 100 | $this->mailcoach->send($this->uuid); 101 | 102 | return $this; 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /src/Resources/CampaignBounce.php: -------------------------------------------------------------------------------- 1 | $filters 65 | */ 66 | public function subscribers(array $filters = []): PaginatedResults 67 | { 68 | return $this->mailcoach->subscribers($this->uuid, $filters); 69 | } 70 | 71 | public function subscriber(string $email): ?Subscriber 72 | { 73 | return $this->mailcoach->findByEmail($this->uuid, $email); 74 | } 75 | 76 | public function save(): self 77 | { 78 | $emailList = $this->mailcoach->updateEmailList($this->uuid, $this->toArray()); 79 | 80 | $this->attributes = $emailList->toArray(); 81 | 82 | $this->fill(); 83 | 84 | return $this; 85 | } 86 | 87 | public function delete(): self 88 | { 89 | $this->mailcoach->deleteEmailList($this->uuid); 90 | 91 | return $this; 92 | } 93 | 94 | public function unsubscribe(string $email) 95 | { 96 | return $this->mailcoach->post("email-lists/{$this->uuid}/unsubscribe", ['email' => $email]); 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /src/Resources/Subscriber.php: -------------------------------------------------------------------------------- 1 | mailcoach->updateSubscriber($this->uuid, $this->toArray()); 32 | 33 | $this->attributes = $subscriber->toArray(); 34 | 35 | $this->fill(); 36 | 37 | return $this; 38 | } 39 | 40 | public function delete(): void 41 | { 42 | $this->mailcoach->deleteSubscriber($this->uuid); 43 | } 44 | 45 | public function confirm(): self 46 | { 47 | $this->mailcoach->confirmSubscriber($this->uuid); 48 | 49 | return $this; 50 | } 51 | 52 | public function isSubscribed(): bool 53 | { 54 | return $this->subscribedAt !== null && $this->unsubscribedAt === null; 55 | } 56 | 57 | public function isUnconfirmed(): bool 58 | { 59 | return $this->subscribedAt === null && $this->unsubscribedAt === null; 60 | } 61 | 62 | public function unsubscribe(): self 63 | { 64 | $this->mailcoach->unsubscribeSubscriber($this->uuid); 65 | 66 | return $this; 67 | } 68 | 69 | public function resubscribe(): self 70 | { 71 | $this->mailcoach->resubscribeSubscriber($this->uuid); 72 | 73 | return $this; 74 | } 75 | 76 | public function removeTags(array $tags): self 77 | { 78 | $this->mailcoach->delete("subscribers/{$this->uuid}/tags", ['tags' => $tags]); 79 | 80 | return $this; 81 | } 82 | 83 | public function addTags(array $tags): self 84 | { 85 | $this->mailcoach->post("subscribers/{$this->uuid}/tags", ['tags' => $tags]); 86 | 87 | return $this; 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /src/Resources/Tag.php: -------------------------------------------------------------------------------- 1 | get($endpoint); 19 | 20 | $results = array_map( 21 | fn ($attributes) => new $mappingClass($attributes, $mailcoach), 22 | $response['data'], 23 | ); 24 | 25 | return new self( 26 | $results, 27 | $response['links'], 28 | $response['meta'], 29 | $mailcoach, 30 | $mappingClass, 31 | ); 32 | } 33 | 34 | /** 35 | * @param array{first: ?string, last: ?string, prev: ?string, next: ?string} $links 36 | * @param array{current_page: int, total: int} $meta 37 | */ 38 | public function __construct( 39 | protected array $results, 40 | protected array $links, 41 | protected array $meta, 42 | protected Mailcoach $mailcoach, 43 | protected string $mappingClass 44 | ) {} 45 | 46 | public function results(): array 47 | { 48 | return $this->results; 49 | } 50 | 51 | public function previousUrl(): ?string 52 | { 53 | return $this->links['prev']; 54 | } 55 | 56 | public function nextUrl(): ?string 57 | { 58 | return $this->links['next']; 59 | } 60 | 61 | public function previous(): ?self 62 | { 63 | if (! $previousUrl = $this->previousUrl()) { 64 | return null; 65 | } 66 | 67 | return PaginatedResults::make($previousUrl, $this->mappingClass, $this->mailcoach); 68 | } 69 | 70 | public function next(): ?self 71 | { 72 | if (! $nextUrl = $this->nextUrl()) { 73 | return null; 74 | } 75 | 76 | return PaginatedResults::make($nextUrl, $this->mappingClass, $this->mailcoach); 77 | } 78 | 79 | public function currentPage(): int 80 | { 81 | return $this->meta['current_page']; 82 | } 83 | 84 | public function total(): int 85 | { 86 | return $this->meta['total']; 87 | } 88 | 89 | public function offsetExists(mixed $offset): bool 90 | { 91 | return isset($this->results[$offset]); 92 | } 93 | 94 | public function offsetGet(mixed $offset): mixed 95 | { 96 | return $this->results[$offset]; 97 | } 98 | 99 | public function offsetSet(mixed $offset, mixed $value): void 100 | { 101 | $this->results[$offset] = $value; 102 | } 103 | 104 | public function offsetUnset(mixed $offset): void 105 | { 106 | unset($this->results[$offset]); 107 | } 108 | 109 | public function getIterator(): Traversable 110 | { 111 | return new ArrayIterator($this->results); 112 | } 113 | } 114 | --------------------------------------------------------------------------------