├── pint.json ├── .gitignore ├── .gitattributes ├── src ├── Contracts │ ├── ResourceContract.php │ └── ClientContract.php ├── Collections │ ├── TagCollection.php │ └── SubscriberCollection.php ├── Exceptions │ └── SendStackApiException.php ├── Enums │ ├── Method.php │ └── Status.php ├── Http │ ├── Resources │ │ ├── SendStackResource.php │ │ ├── TagResource.php │ │ └── SubscribersResource.php │ ├── Requests │ │ ├── TagRequest.php │ │ └── SubscriberRequest.php │ └── Client.php ├── Concerns │ ├── CanAccessProperties.php │ ├── CanSendRequests.php │ └── CanBuildRequests.php ├── DataObjects │ ├── Tag.php │ ├── Name.php │ └── Subscriber.php ├── Facades │ └── SendStack.php └── Providers │ └── SendStackServiceProvider.php ├── tests ├── Feature │ ├── ConfigTest.php │ ├── FacadeTest.php │ ├── ClientTest.php │ ├── TagsTest.php │ └── SubscribersTest.php ├── Datasets │ └── Strings.php ├── TestCase.php └── Pest.php ├── config └── services.php ├── .editorconfig ├── phpstan.neon ├── phpunit.xml.dist ├── phpunit.xml.dist.bak ├── LICENSE ├── composer.json └── README.md /pint.json: -------------------------------------------------------------------------------- 1 | { 2 | "preset": "psr12" 3 | } 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /vendor 2 | /.idea 3 | /.vscode 4 | /.vagrant 5 | .phpunit.result.cache 6 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto 2 | /.github export-ignore 3 | .scrutinizer.yml export-ignore 4 | BACKERS.md export-ignore 5 | CONTRIBUTING.md export-ignore 6 | CHANGELOG.md export-ignore 7 | -------------------------------------------------------------------------------- /src/Contracts/ResourceContract.php: -------------------------------------------------------------------------------- 1 | get('services.sendstack.url'); 7 | 8 | expect($url)->not()->toBeEmpty(); 9 | }); 10 | -------------------------------------------------------------------------------- /config/services.php: -------------------------------------------------------------------------------- 1 | [ 7 | 'url' => env('SENDSTACK_URL', 'https://getsendstack.com/api'), 8 | 'token' => env('SENDSTACK_TOKEN'), 9 | ], 10 | ]; 11 | -------------------------------------------------------------------------------- /src/Collections/TagCollection.php: -------------------------------------------------------------------------------- 1 | toBeInstanceOf(SubscribersResource::class); 12 | }); 13 | -------------------------------------------------------------------------------- /phpstan.neon: -------------------------------------------------------------------------------- 1 | includes: 2 | - ./vendor/nunomaduro/larastan/extension.neon 3 | 4 | parameters: 5 | paths: 6 | - src 7 | 8 | level: 9 9 | 10 | ignoreErrors: 11 | - '#Parameter \$callback of function array_map expects#' 12 | 13 | checkMissingIterableValueType: false 14 | checkGenericClassInNonGenericObjectType: false 15 | -------------------------------------------------------------------------------- /src/Http/Resources/SendStackResource.php: -------------------------------------------------------------------------------- 1 | url; 17 | } 18 | 19 | public function token(): string 20 | { 21 | return $this->token; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /tests/Pest.php: -------------------------------------------------------------------------------- 1 | in('Feature'); 11 | 12 | function fakeClient(null|array $body): void 13 | { 14 | Client::fake(['*' => Http::response( 15 | body: $body, 16 | status: Status::OK(), 17 | )]); 18 | } 19 | -------------------------------------------------------------------------------- /src/DataObjects/Tag.php: -------------------------------------------------------------------------------- 1 | $this->name, 20 | ]; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/Enums/Status.php: -------------------------------------------------------------------------------- 1 | value => Status::SUBSCRIBED, 16 | Status::PENDING->value => Status::PENDING, 17 | default => Status::SUBSCRIBED, 18 | }; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /phpunit.xml.dist: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 10 | ./tests/Feature 11 | 12 | 13 | 14 | 15 | ./app 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /src/DataObjects/Name.php: -------------------------------------------------------------------------------- 1 | $this->first, 21 | 'last' => $this->last, 22 | 'full' => "{$this->first} {$this->last}", 23 | ]; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/Http/Requests/TagRequest.php: -------------------------------------------------------------------------------- 1 | $this->name, 21 | 'allow_form_subscription' => $this->allowFormSubscription, 22 | ]; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /tests/Feature/ClientTest.php: -------------------------------------------------------------------------------- 1 | toBeInstanceOf(ClientContract::class); 15 | })->with('strings'); 16 | 17 | it('can resolve a client from the Laravel container', function () { 18 | $client = app()->make( 19 | abstract: ClientContract::class, 20 | ); 21 | 22 | expect( 23 | $client, 24 | )->toBeInstanceOf(Client::class); 25 | }); 26 | -------------------------------------------------------------------------------- /src/Concerns/CanSendRequests.php: -------------------------------------------------------------------------------- 1 | makeRequest() 23 | ->send( 24 | method: $method->value, 25 | url: $url, 26 | options: $options, 27 | ); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/Concerns/CanBuildRequests.php: -------------------------------------------------------------------------------- 1 | url(), 20 | )->timeout( 21 | seconds: 15, 22 | )->withUserAgent( 23 | userAgent: 'Laravel_SendStack_Package_v1', 24 | )->withToken( 25 | token: $this->token(), 26 | ); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/Facades/SendStack.php: -------------------------------------------------------------------------------- 1 | 2 | 13 | 14 | 15 | ./tests/Feature 16 | 17 | 18 | 19 | 20 | ./app 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /src/Providers/SendStackServiceProvider.php: -------------------------------------------------------------------------------- 1 | mergeConfigFrom( 16 | path: __DIR__.'/../../config/services.php', 17 | key: 'services', 18 | ); 19 | } 20 | 21 | public function register(): void 22 | { 23 | $this->app->singleton( 24 | abstract: ClientContract::class, 25 | concrete: fn (): ClientContract => new Client( 26 | url: strval(config('services.sendstack.url')), 27 | token: strval(config('services.sendstack.token')), 28 | ), 29 | ); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/Http/Requests/SubscriberRequest.php: -------------------------------------------------------------------------------- 1 | $this->firstName, 24 | 'last_name' => $this->lastName, 25 | 'email' => $this->email, 26 | 'tags' => $this->tags, 27 | 'optin' => $this->optIn, 28 | ]; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) Beyond Code GmbH 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 all 13 | 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 THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /tests/Feature/TagsTest.php: -------------------------------------------------------------------------------- 1 | [ 14 | $string, 15 | "$string test", 16 | ] 17 | ], 18 | ); 19 | 20 | $client = new Client( 21 | url: $string, 22 | token: $string, 23 | ); 24 | 25 | expect( 26 | $client->tags()->all(), 27 | )->toBeInstanceOf( 28 | TagCollection::class 29 | )->each( 30 | fn ($tag) => 31 | $tag 32 | ->toBeInstanceOf(Tag::class), 33 | ); 34 | })->with('strings'); 35 | 36 | it('can create a new tag', function (string $string) { 37 | fakeClient( 38 | body: [$string], 39 | ); 40 | 41 | $client = new Client( 42 | url: $string, 43 | token: $string, 44 | ); 45 | 46 | expect( 47 | $client->tags()->create( 48 | request: new TagRequest( 49 | name: $string, 50 | ), 51 | ), 52 | )->toBeInstanceOf(Tag::class); 53 | })->with('strings'); 54 | -------------------------------------------------------------------------------- /src/DataObjects/Subscriber.php: -------------------------------------------------------------------------------- 1 | $this->uuid, 29 | 'email' => $this->email, 30 | 'name' => $this->name->toArray(), 31 | 'meta' => $this->meta, 32 | 'tags' => array_map( 33 | callback: fn (Tag $tag): array => $tag->toArray(), 34 | array: $this->tags, 35 | ), 36 | 'status' => $this->status->value, 37 | 'confirmed' => $this->confirmed, 38 | 'unsubscribed' => $this->unsubscribed, 39 | ]; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/Http/Client.php: -------------------------------------------------------------------------------- 1 | subscribers()->get( 56 | query: $email, 57 | ); 58 | } catch (Throwable) { 59 | return false; 60 | } 61 | 62 | if ($subscriber->status !== Status::SUBSCRIBED) { 63 | return false; 64 | } 65 | 66 | return true; 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/Http/Resources/TagResource.php: -------------------------------------------------------------------------------- 1 | client->send( 19 | method: Method::GET, 20 | url : '/tags', 21 | ); 22 | 23 | if ($response->failed()) { 24 | throw new SendStackApiException( 25 | response: $response, 26 | ); 27 | } 28 | 29 | return TagCollection::make( 30 | items: array_map( 31 | callback: fn (string $tag): mixed => $this->buildTag( 32 | data: $tag 33 | ), 34 | array: (array) $response->json('data'), 35 | ), 36 | ); 37 | } 38 | 39 | public function create(TagRequest $request): DataObjectContract 40 | { 41 | $response = $this->client->send( 42 | method: Method::POST, 43 | url: '/tags', 44 | options: [ 45 | 'json' => $request->toArray(), 46 | ] 47 | ); 48 | 49 | if ($response->failed()) { 50 | throw new SendStackApiException( 51 | response: $response, 52 | ); 53 | } 54 | 55 | return $this->buildTag( 56 | data: strval($response->collect()->first()), 57 | ); 58 | } 59 | 60 | protected function buildTag(string $data): DataObjectContract 61 | { 62 | return new Tag( 63 | name: $data, 64 | ); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "getsendstack/laravel-sendstack", 3 | "description": "A Laravel Package to work with the SendStack API", 4 | "keywords": [ 5 | "beyondcode", 6 | "getsendstack", 7 | "laravel-sendstack", 8 | "sendstack", 9 | "php" 10 | ], 11 | "homepage": "https://github.com/getsendstack/laravel-sendstack", 12 | "license": "MIT", 13 | "authors": [ 14 | { 15 | "role": "Developer", 16 | "name": "Steve McDougall", 17 | "email": "juststevemcd@gmail.com", 18 | "homepage": "https://www.juststeveking.uk/" 19 | } 20 | ], 21 | "autoload": { 22 | "psr-4": { 23 | "SendStack\\Laravel\\": "src/" 24 | } 25 | }, 26 | "autoload-dev": { 27 | "psr-4": { 28 | "SendStack\\Laravel\\Tests\\": "tests/" 29 | } 30 | }, 31 | "require": { 32 | "php": "^8.1", 33 | "illuminate/contracts": "^9.25|^10.0", 34 | "juststeveking/http-status-code": "^3.0", 35 | "juststeveking/laravel-data-object-tools": "^1.0" 36 | }, 37 | "require-dev": { 38 | "guzzlehttp/guzzle": "^7.4", 39 | "laravel/pint": "^1.1", 40 | "nunomaduro/larastan": "^2.1", 41 | "orchestra/testbench": "^7.0|^8.0", 42 | "pestphp/pest": "^1.21|^2.0" 43 | }, 44 | "scripts": { 45 | "test": "./vendor/bin/pest", 46 | "pint": "./vendor/bin/pint", 47 | "stan": "./vendor/bin/phpstan analyse --error-format=github" 48 | }, 49 | "scripts-description": { 50 | "test": "Run the Pest test suite.", 51 | "pint": "Run Laravel Pint to fix any code style issues." 52 | }, 53 | "extra": { 54 | "laravel": { 55 | "providers": [ 56 | "SendStack\\Laravel\\Providers\\SendStackServiceProvider" 57 | ], 58 | "aliases": { 59 | "SendStack": "SendStack\\Laravel\\Facades\\SendStack" 60 | } 61 | } 62 | }, 63 | "config": { 64 | "optimize-autoloader": true, 65 | "preferred-install": "dist", 66 | "sort-packages": true, 67 | "allow-plugins": { 68 | "pestphp/pest-plugin": true 69 | } 70 | }, 71 | "minimum-stability": "stable", 72 | "prefer-stable": true 73 | } 74 | -------------------------------------------------------------------------------- /src/Http/Resources/SubscribersResource.php: -------------------------------------------------------------------------------- 1 | client->send( 23 | method: Method::GET, 24 | url: '/subscribers', 25 | ); 26 | 27 | if ($response->failed()) { 28 | throw new SendStackApiException( 29 | response: $response, 30 | ); 31 | } 32 | 33 | return SubscriberCollection::make( 34 | items: array_map( 35 | callback: fn (array $subscriber): DataObjectContract => $this->buildSubscriber( 36 | data: $subscriber 37 | ), 38 | array: (array) $response->json('data'), 39 | ), 40 | ); 41 | } 42 | 43 | public function get(string $query): Subscriber 44 | { 45 | $response = $this->client->send( 46 | method: Method::GET, 47 | url: "/subscribers/{$query}", 48 | ); 49 | 50 | if ($response->failed()) { 51 | throw new SendStackApiException( 52 | response: $response, 53 | ); 54 | } 55 | 56 | return $this->buildSubscriber( 57 | data: $response->collect()->toArray(), 58 | ); 59 | } 60 | 61 | public function create(SubscriberRequest $request): Subscriber 62 | { 63 | $response = $this->client->send( 64 | method: Method::POST, 65 | url: '/subscribers', 66 | options: [ 67 | 'json' => $request->toArray(), 68 | ] 69 | ); 70 | 71 | if ($response->failed()) { 72 | throw new SendStackApiException( 73 | response: $response, 74 | ); 75 | } 76 | 77 | return $this->buildSubscriber( 78 | data: $response->collect()->toArray(), 79 | ); 80 | } 81 | 82 | public function update(string $uuid, SubscriberRequest $request): Subscriber 83 | { 84 | $response = $this->client->send( 85 | method: Method::PUT, 86 | url: "/subscribers/{$uuid}", 87 | options: [ 88 | 'json' => $request->toArray(), 89 | ] 90 | ); 91 | 92 | if ($response->failed()) { 93 | throw new SendStackApiException( 94 | response: $response, 95 | ); 96 | } 97 | 98 | return $this->buildSubscriber( 99 | data: $response->collect()->toArray(), 100 | ); 101 | } 102 | 103 | public function delete(string $uuid): bool 104 | { 105 | $response = $this->client->send( 106 | method: Method::DELETE, 107 | url: "/subscribers/{$uuid}", 108 | ); 109 | 110 | if ($response->failed()) { 111 | throw new SendStackApiException( 112 | response: $response, 113 | ); 114 | } 115 | 116 | return $response->successful(); 117 | } 118 | 119 | public function attachTag(string $uuid, string $tag): Subscriber 120 | { 121 | $response = $this->client->send( 122 | method: Method::POST, 123 | url: "/subscribers/{$uuid}/tags", 124 | options: [ 125 | 'json' => ['tag' => $tag], 126 | ] 127 | ); 128 | 129 | if ($response->failed()) { 130 | throw new SendStackApiException( 131 | response: $response, 132 | ); 133 | } 134 | 135 | return $this->buildSubscriber( 136 | data: $response->collect()->toArray(), 137 | ); 138 | } 139 | 140 | public function removeTag(string $uuid, string $tag): Subscriber 141 | { 142 | $response = $this->client->send( 143 | method: Method::DELETE, 144 | url: "/subscribers/{$uuid}/tags/{$tag}", 145 | ); 146 | 147 | if ($response->failed()) { 148 | throw new SendStackApiException( 149 | response: $response, 150 | ); 151 | } 152 | 153 | return $this->buildSubscriber( 154 | data: $response->collect()->toArray(), 155 | ); 156 | } 157 | 158 | protected function buildSubscriber(array $data): Subscriber 159 | { 160 | return new Subscriber( 161 | uuid: strval(data_get($data, 'uuid')), 162 | email: strval(data_get($data, 'email')), 163 | name: new Name( 164 | first: strval(data_get($data, 'first_name')), 165 | last: strval(data_get($data, 'last_name')), 166 | ), 167 | meta: strval(data_get($data, 'meta')), 168 | tags: array_map( 169 | callback: fn (string $tag): Tag => new Tag( 170 | name: $tag, 171 | ), 172 | array: (array) data_get($data, 'tags'), 173 | ), 174 | status: Status::match( 175 | value: strval(data_get($data, 'status')), 176 | ), 177 | confirmed: Carbon::parse( 178 | time: strval(data_get($data, 'confirmed_at')), 179 | ), 180 | unsubscribed: Carbon::parse( 181 | time: strval(data_get($data, 'unsubscribed_at')), 182 | ), 183 | ); 184 | } 185 | } 186 | -------------------------------------------------------------------------------- /tests/Feature/SubscribersTest.php: -------------------------------------------------------------------------------- 1 | [ 14 | [ 15 | 'uuid' => $string, 16 | 'email' => $string, 17 | 'first_name' => $string, 18 | 'last_name' => $string, 19 | 'meta' => $string, 20 | 'tags' => [$string], 21 | 'status' => 'subscibed', 22 | 'confirmed_at' => now(), 23 | 'unsubscribed_at' => null, 24 | ], 25 | [ 26 | 'uuid' => $string, 27 | 'email' => $string, 28 | 'first_name' => $string, 29 | 'last_name' => $string, 30 | 'meta' => $string, 31 | 'tags' => [$string], 32 | 'status' => 'subscibed', 33 | 'confirmed_at' => now()->subDays(), 34 | 'unsubscribed_at' => null, 35 | ], 36 | ], 37 | ], 38 | ); 39 | 40 | $client = new Client( 41 | url: $string, 42 | token: $string, 43 | ); 44 | 45 | expect( 46 | $client->subscribers()->all(), 47 | )->toBeInstanceOf( 48 | SubscriberCollection::class 49 | )->each( 50 | fn ($subscriber) => 51 | $subscriber 52 | ->toBeInstanceOf(Subscriber::class), 53 | ); 54 | })->with('strings'); 55 | 56 | it('can get a single subscriber', function (string $string) { 57 | fakeClient( 58 | body: [ 59 | 'uuid' => $string, 60 | 'email' => $string, 61 | 'first_name' => $string, 62 | 'last_name' => $string, 63 | 'meta' => $string, 64 | 'tags' => [$string], 65 | 'status' => 'subscibed', 66 | 'confirmed_at' => now(), 67 | 'unsubscribed_at' => null, 68 | ], 69 | ); 70 | 71 | $client = new Client( 72 | url: $string, 73 | token: $string, 74 | ); 75 | 76 | expect( 77 | $client->subscribers()->get(query: $string), 78 | )->toBeInstanceOf( 79 | Subscriber::class 80 | ); 81 | })->with('strings'); 82 | 83 | it('can create a new subscriber', function (string $string) { 84 | fakeClient( 85 | body: [ 86 | 'uuid' => $string, 87 | 'email' => $string, 88 | 'first_name' => $string, 89 | 'last_name' => $string, 90 | 'meta' => $string, 91 | 'tags' => [$string], 92 | 'status' => 'subscibed', 93 | 'confirmed_at' => now(), 94 | 'unsubscribed_at' => null, 95 | ], 96 | ); 97 | 98 | $client = new Client( 99 | url: $string, 100 | token: $string, 101 | ); 102 | 103 | expect( 104 | $client->subscribers()->create( 105 | request: new SubscriberRequest( 106 | email: $string, 107 | firstName: $string, 108 | lastName: $string, 109 | tags: [$string], 110 | optIn: true, 111 | ), 112 | ), 113 | )->toBeInstanceOf(Subscriber::class); 114 | })->with('strings'); 115 | 116 | it('can update a subscriber', function (string $string) { 117 | fakeClient( 118 | body: [ 119 | 'uuid' => $string, 120 | 'email' => $string, 121 | 'first_name' => $string, 122 | 'last_name' => $string, 123 | 'meta' => $string, 124 | 'tags' => [$string], 125 | 'status' => 'subscibed', 126 | 'confirmed_at' => now(), 127 | 'unsubscribed_at' => null, 128 | ], 129 | ); 130 | 131 | $client = new Client( 132 | url: $string, 133 | token: $string, 134 | ); 135 | 136 | expect( 137 | $client->subscribers()->update( 138 | uuid: $string, 139 | request: new SubscriberRequest( 140 | email: $string, 141 | optIn: false, 142 | ), 143 | ) 144 | )->toBeInstanceOf(Subscriber::class); 145 | })->with('strings'); 146 | 147 | it('can delete a subscriber', function (string $string) { 148 | fakeClient( 149 | body: null, 150 | ); 151 | 152 | $client = new Client( 153 | url: $string, 154 | token: $string, 155 | ); 156 | 157 | expect( 158 | $client->subscribers()->delete( 159 | uuid: $string, 160 | ) 161 | )->toBeBool()->toEqual(true); 162 | })->with('strings'); 163 | 164 | it('can check if an email is an active subscriber', function (string $string) { 165 | fakeClient( 166 | body: [ 167 | 'uuid' => $string, 168 | 'email' => $string, 169 | 'first_name' => $string, 170 | 'last_name' => $string, 171 | 'meta' => $string, 172 | 'tags' => [$string], 173 | 'status' => 'subscibed', 174 | 'confirmed_at' => now(), 175 | 'unsubscribed_at' => null, 176 | ], 177 | ); 178 | 179 | $client = new Client( 180 | url: $string, 181 | token: $string, 182 | ); 183 | 184 | expect( 185 | $client->isActiveSubscriber( 186 | email: $string, 187 | ), 188 | )->toBeBool()->toEqual(true); 189 | })->with('strings'); 190 | 191 | it('can check if an email is an inactive subscriber', function (string $string) { 192 | fakeClient( 193 | body: [ 194 | 'uuid' => $string, 195 | 'email' => $string, 196 | 'first_name' => $string, 197 | 'last_name' => $string, 198 | 'meta' => $string, 199 | 'tags' => [$string], 200 | 'status' => 'pending', 201 | 'confirmed_at' => now(), 202 | 'unsubscribed_at' => null, 203 | ], 204 | ); 205 | 206 | $client = new Client( 207 | url: $string, 208 | token: $string, 209 | ); 210 | 211 | expect( 212 | $client->isActiveSubscriber( 213 | email: $string, 214 | ), 215 | )->toBeBool()->toEqual(false); 216 | })->with('strings'); 217 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Laravel Send Stack 2 | 3 | [![Latest Version on Packagist](https://img.shields.io/packagist/v/getsendstack/laravel-sendstack.svg?style=flat-square)](https://packagist.org/packages/getsendstack/laravel-sendstack) 4 | [![Test Suite](https://github.com/getsendstack/laravel-sendstack/actions/workflows/tests.yml/badge.svg)](https://github.com/getsendstack/laravel-sendstack/actions/workflows/tests.yml) 5 | [![Total Downloads](https://img.shields.io/packagist/dt/getsendstack/laravel-sendstack.svg?style=flat-square)](https://packagist.org/packages/getsendstack/laravel-sendstack) 6 | 7 | 8 | ## Installation 9 | 10 | You can install the package via composer: 11 | 12 | ```bash 13 | composer require getsendstack/laravel-sendstack 14 | ``` 15 | 16 | You can publish the config file with: 17 | 18 | ```bash 19 | php artisan vendor:publish --tag="sendstack-config" 20 | ``` 21 | 22 | ## Set up 23 | 24 | To start using this package, you need to add environment variables for: 25 | 26 | - `SENDSTACK_URL` - Optional, not really needed as this has a default 27 | - `SENDSTACK_TOKEN` - You can generate this from your getSendStack account. 28 | 29 | The package will pick these up in its configuration and use these when it resolves an instance of the `Client`. 30 | 31 | ## Usage 32 | 33 | This package can be used by injecting the `SendStack\Laravel\Http\Client` into a method to instantiate the client: 34 | 35 | ```php 36 | declare(strict_types=1); 37 | 38 | use App\Models\Subscriber; 39 | use Illuminate\Bus\Queueable; 40 | use Illuminate\Contracts\Queue\ShouldQueue; 41 | use Illuminate\Foundation\Bus\Dispatchable; 42 | use Illuminate\Queue\InteractsWithQueue; 43 | use Illuminate\Queue\SerializesModels; 44 | use SendStack\Laravel\Contracts\ClientContract; 45 | 46 | namespace App\Jobs\SendStack; 47 | 48 | class SyncSubscribers implements ShouldQueue 49 | { 50 | use Queueable; 51 | use Dispatchable; 52 | use SerializesModels; 53 | use InteractsWithQueue; 54 | 55 | public function handle(ClientContract $client): void 56 | { 57 | foreach ($client->subscribers()->all() as $subscriber) { 58 | Subscriber::query()->updateOrCreate( 59 | attributes: ['email' => $subscriber->email], 60 | values: $subscriber->toArray(), 61 | ); 62 | } 63 | } 64 | } 65 | ``` 66 | 67 | Alternatively you can use the Facade to help you: 68 | 69 | ```php 70 | declare(strict_types=1); 71 | 72 | use App\Models\Subscriber; 73 | use Illuminate\Bus\Queueable; 74 | use Illuminate\Contracts\Queue\ShouldQueue; 75 | use Illuminate\Foundation\Bus\Dispatchable; 76 | use Illuminate\Queue\InteractsWithQueue; 77 | use Illuminate\Queue\SerializesModels; 78 | use SendStack\Laravel\Facades\SendStack; 79 | 80 | namespace App\Jobs\SendStack; 81 | 82 | class SyncSubscribers implements ShouldQueue 83 | { 84 | use Queueable; 85 | use Dispatchable; 86 | use SerializesModels; 87 | use InteractsWithQueue; 88 | 89 | public function handle(): void 90 | { 91 | foreach (SendStack::subscribers()->all() as $subscriber) { 92 | Subscriber::query()->updateOrCreate( 93 | attributes: ['email' => $subscriber->email], 94 | values: $subscriber->toArray(), 95 | ); 96 | } 97 | } 98 | } 99 | ``` 100 | 101 | ### Getting a list of Subscribers 102 | 103 | ```php 104 | use SendStack\Laravel\Contracts\ClientContract; 105 | use SendStack\Laravel\Facades\SendStack; 106 | 107 | /** 108 | * Without a Facade 109 | */ 110 | $client = app()->make( 111 | abstract: ClientContract::class, 112 | ); 113 | 114 | $client->subscribers()->all(); 115 | 116 | /** 117 | * Using the Facade 118 | */ 119 | SendStack::subscribers()->all(); 120 | ``` 121 | 122 | ### Getting a single Subscriber 123 | 124 | ```php 125 | use SendStack\Laravel\Contracts\ClientContract; 126 | use SendStack\Laravel\Facades\SendStack; 127 | 128 | /** 129 | * Without a Facade 130 | */ 131 | $client = app()->make( 132 | abstract: ClientContract::class, 133 | ); 134 | 135 | $client->subscribers()->get( 136 | query: '1234-1234-1234-1234' // This can be either the subscribers UUID or their Email Address 137 | ); 138 | 139 | /** 140 | * Using the Facade 141 | */ 142 | SendStack::subscribers()->get( 143 | query: '1234-1234-1234-1234', // This can be either the subscribers UUID or their Email Address 144 | ); 145 | ``` 146 | 147 | ### Creating a new Subscriber 148 | 149 | ```php 150 | use SendStack\Laravel\Contracts\ClientContract; 151 | use SendStack\Laravel\Facades\SendStack; 152 | use SendStack\Laravel\Http\Requests\SubscriberRequest; 153 | 154 | /** 155 | * Without a Facade 156 | */ 157 | $client = app()->make( 158 | abstract: ClientContract::class, 159 | ); 160 | 161 | $client->subscribers()->create( 162 | request: new SubscriberRequest( 163 | email: 'contact@getsendstack.com', // Required 164 | firstName: 'Send', // Optional 165 | lastName: 'Stack', // Optional 166 | tags: [ 167 | 'Client', 168 | 'Awesome', 169 | ], // Optional 170 | optIn: true, // Optional 171 | ), 172 | ); 173 | 174 | /** 175 | * Using the Facade 176 | */ 177 | SendStack::subscribers()->create( 178 | request: new SubscriberRequest( 179 | email: 'contact@getsendstack.com', // Required 180 | firstName: 'Send', // Optional 181 | lastName: 'Stack', // Optional 182 | tags: [ 183 | 'Client', 184 | 'Awesome', 185 | ], // Optional 186 | optIn: true, // Optional 187 | ), 188 | ); 189 | ``` 190 | 191 | ### Update a Subscriber 192 | 193 | ```php 194 | use SendStack\Laravel\Contracts\ClientContract; 195 | use SendStack\Laravel\Facades\SendStack; 196 | use SendStack\Laravel\Http\Requests\SubscriberRequest; 197 | 198 | /** 199 | * Without a Facade 200 | */ 201 | $client = app()->make( 202 | abstract: ClientContract::class, 203 | ); 204 | 205 | $client->subscribers()->update( 206 | uuid: '1234-1234-1234-1234', 207 | request: new SubscriberRequest( 208 | email: 'contact@getsendstack.com', // Required 209 | firstName: 'Send', // Optional 210 | lastName: 'Stack', // Optional 211 | tags: [ 212 | 'Client', 213 | 'Awesome', 214 | ], // Optional 215 | optIn: true, // Optional 216 | ), 217 | ); 218 | 219 | /** 220 | * Using the Facade 221 | */ 222 | SendStack::subscribers()->update( 223 | uuid: '1234-1234-1234-1234', 224 | request: new SubscriberRequest( 225 | email: 'contact@getsendstack.com', // Required 226 | firstName: 'Send', // Optional 227 | lastName: 'Stack', // Optional 228 | tags: [ 229 | 'Client', 230 | 'Awesome', 231 | ], // Optional 232 | optIn: true, // Optional 233 | ), 234 | ); 235 | ``` 236 | 237 | ### Deleting a Subscriber 238 | 239 | ```php 240 | use SendStack\Laravel\Contracts\ClientContract; 241 | use SendStack\Laravel\Facades\SendStack; 242 | 243 | /** 244 | * Without a Facade 245 | */ 246 | $client = app()->make( 247 | abstract: ClientContract::class, 248 | ); 249 | 250 | $client->subscribers()->delete( 251 | uuid: '1234-1234-1234-1234' 252 | ); 253 | 254 | /** 255 | * Using the Facade 256 | */ 257 | SendStack::subscribers()->delete( 258 | uuid: '1234-1234-1234-1234', 259 | ); 260 | ``` 261 | 262 | ### Attaching a Tag to a Subscriber 263 | 264 | ```php 265 | use SendStack\Laravel\Contracts\ClientContract; 266 | use SendStack\Laravel\Facades\SendStack; 267 | 268 | /** 269 | * Without a Facade 270 | */ 271 | $client = app()->make( 272 | abstract: ClientContract::class, 273 | ); 274 | 275 | $client->subscribers()->attachTag( 276 | uuid: '1234-1234-1234-1234', 277 | tag: 'Early Access', 278 | ); 279 | 280 | /** 281 | * Using the Facade 282 | */ 283 | SendStack::subscribers()->attachTag( 284 | uuid: '1234-1234-1234-1234', 285 | tag: 'Early Access', 286 | ); 287 | ``` 288 | 289 | ### Removing a Tag from a Subscriber 290 | 291 | ```php 292 | use SendStack\Laravel\Contracts\ClientContract; 293 | use SendStack\Laravel\Facades\SendStack; 294 | 295 | /** 296 | * Without a Facade 297 | */ 298 | $client = app()->make( 299 | abstract: ClientContract::class, 300 | ); 301 | 302 | $client->subscribers()->removeTag( 303 | uuid: '1234-1234-1234-1234', 304 | tag: 'Early Access', 305 | ); 306 | 307 | /** 308 | * Using the Facade 309 | */ 310 | SendStack::subscribers()->removeTag( 311 | uuid: '1234-1234-1234-1234', 312 | tag: 'Early Access', 313 | ); 314 | ``` 315 | 316 | ### Checking if an email address is an Active Subscriber 317 | 318 | ```php 319 | use SendStack\Laravel\Contracts\ClientContract; 320 | use SendStack\Laravel\Facades\SendStack; 321 | 322 | /** 323 | * Without a Facade 324 | */ 325 | $client = app()->make( 326 | abstract: ClientContract::class, 327 | ); 328 | 329 | $client->isActiveSubscriber( 330 | email: 'taylor@laravel.com', 331 | ); 332 | 333 | /** 334 | * Using the Facade 335 | */ 336 | SendStack::isActiveSubscriber( 337 | email: 'taylor@laravel.com', 338 | ); 339 | ``` 340 | 341 | ### Getting all Tags 342 | 343 | ```php 344 | use SendStack\Laravel\Contracts\ClientContract; 345 | use SendStack\Laravel\Facades\SendStack; 346 | 347 | /** 348 | * Without a Facade 349 | */ 350 | $client = app()->make( 351 | abstract: ClientContract::class, 352 | ); 353 | 354 | $client->tags()->all(); 355 | 356 | /** 357 | * Using the Facade 358 | */ 359 | SendStack::tags()->all(); 360 | ``` 361 | 362 | ### Creating a new Tag 363 | 364 | ```php 365 | use SendStack\Laravel\Contracts\ClientContract; 366 | use SendStack\Laravel\Facades\SendStack; 367 | use SendStack\Laravel\Http\Requests\TagRequest; 368 | 369 | /** 370 | * Without a Facade 371 | */ 372 | $client = app()->make( 373 | abstract: ClientContract::class, 374 | ); 375 | 376 | $client->tags()->create( 377 | request: new TagRequest( 378 | name: 'Test', // Required 379 | allowFormSubscription: true, // Optional 380 | ), 381 | ); 382 | 383 | /** 384 | * Using the Facade 385 | */ 386 | SendStack::tags()->create( 387 | request: new TagRequest( 388 | name: 'Test', // Required 389 | allowFormSubscription: true, // Optional 390 | ), 391 | ); 392 | ``` 393 | 394 | ## Testing 395 | 396 | ```bash 397 | composer test 398 | ``` 399 | 400 | ## Changelog 401 | 402 | Please see [CHANGELOG](CHANGELOG.md) for more information on what has changed recently. 403 | 404 | ## Security Vulnerabilities 405 | 406 | Please review [our security policy](../../security/policy) on how to report security vulnerabilities. 407 | 408 | ## Credits 409 | 410 | - [Steve McDougall](https://github.com/juststeveking) 411 | - [All Contributors](../../contributors) 412 | 413 | ## License 414 | 415 | The MIT License (MIT). Please see [License File](LICENSE.md) for more information. 416 | --------------------------------------------------------------------------------