├── 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 | [](https://packagist.org/packages/getsendstack/laravel-sendstack)
4 | [](https://github.com/getsendstack/laravel-sendstack/actions/workflows/tests.yml)
5 | [](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 |
--------------------------------------------------------------------------------