diff --git a/README.md b/README.md index adba3c3..028ec06 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,300 @@ -### WIP -This package is __WIP__, please do not use it in a production project yet. +# Laravel Sendy is a simple and clean wrapper for the Sendy API + +[![Latest Version on Packagist](https://img.shields.io/packagist/v/coderflexx/laravel-sendy.svg?style=flat-square)](https://packagist.org/packages/coderflexx/laravel-sendy) +[![GitHub Tests Action Status](https://img.shields.io/github/actions/workflow/status/coderflexx/laravel-sendy/run-tests.yml?branch=main&label=tests&style=flat-square)](https://github.com/coderflexx/laravel-sendy/actions?query=workflow%3Arun-tests+branch%3Amain) +[![GitHub Code Style Action Status](https://img.shields.io/github/actions/workflow/status/coderflexx/laravel-sendy/fix-php-code-style-issues.yml?branch=main&label=code%20style&style=flat-square)](https://github.com/coderflexx/laravel-sendy/actions?query=workflow%3A"Fix+PHP+code+style+issues"+branch%3Amain) +[![Total Downloads](https://img.shields.io/packagist/dt/coderflexx/laravel-sendy.svg?style=flat-square)](https://packagist.org/packages/coderflexx/laravel-sendy) + +--- + +Laravel Sendy is a simple and clean wrapper for the Sendy API, making it easy to manage subscribers, lists, and campaigns directly from your Laravel application. + + +## Installation + +You can install the package via composer: + +```bash +composer require coderflexx/laravel-sendy +``` + +You can publish the config file with: + +```bash +php artisan vendor:publish --tag="laravel-sendy" +``` + +This is the contents of the published config file: + +```php +return [ + /* + |-------------------------------------------------------------------------- + | Sendy Installation URL + |-------------------------------------------------------------------------- + | + | This URL is used to connect to your Sendy installation. It should + | point to the root of your Sendy installation. For example: + | https://your-sendy-installation.com + */ + 'sendy_installation_url' => env('SENDY_INSTALLATION_URL', 'https://your-sendy-installation.com'), + + /* + |-------------------------------------------------------------------------- + | Sendy API Key + |-------------------------------------------------------------------------- + | + | This key is used to authenticate your application with the Sendy + | installation. You can find your API key in the Sendy settings. + | Make sure to keep this key secure and do not share it with anyone. + | It is recommended to use environment variables to store sensitive + | information like API keys. You can set the SENDY_API_KEY + */ + 'sendy_api_key' => env('SENDY_API_KEY', 'your-sendy-api-key'), +]; +``` + +## API Keys +After Installation, you can grab your `API KEYS` from the sendy app installation, then add them in `.env` file + +```env +SENDY_INSTALLATION_URL=https://your-app-installation.com/ +SENDY_API_KEY=your-api-key +``` + +## Sendy Version +This package is compatible with __Sendy v6.1.2__ (Latest) + +## Usage + +In order to use the package, you can use the facade directly, followed by the main method api (e.g. `subscribers()` then the verb (action)) + +```php +use Coderflex\LaravelSendy\Facades\Sendy; + +$sendy = Sendy::{$service()}->{$action()} +``` + +### Async Argument for HTTP Requests + +All HTTP requests support an `async` option, allowing you to __defer execution__. This is useful when a request doesn't need to run immediately or isn't a high priority. You can handle it later using await when you're ready to process the result. + +Example: + +```php +$promise = Sendy::subscribers()->subscribe( + data: $data, + async: true // The request is deferred and returns a promise +); + +// perform other tasks/operation here + +// later, wait for the response when you're ready. +$response = $promise->await(); +``` + +### Subscribers +In order to create a create/delete a subscriber, you have to access the subscribers service first, then to the action + +#### Subscribe a User + +```php +use Coderflex\LaravelSendy\Facades\Sendy; + +$data = [ + 'name' => 'John Doe', + 'email' => 'john@example.com', + 'list' => '123', + 'country' => 'US', +]; + +$response = Sendy::subscribers()->subscribe( + data: $data, + async: false + ); +``` + +Full Documentation [Here](https://sendy.co/api#subscribe) + +#### Unsubscribe a User + +```php +use Coderflex\LaravelSendy\Facades\Sendy; + +$data = [ + 'email' => 'john@example.com', + 'list' => '123', + 'boolean' => true, // to get plan text response, instead of json. +]; + +$response = Sendy::subscribers()->unsubscribe( + data: $data, + async: false + ); + +``` +Full Documentation [Here](https://sendy.co/api#unsubscribe) + +#### Delete Subscriber + +```php +use Coderflex\LaravelSendy\Facades\Sendy; + +$data = [ + 'email' => 'john@example.com', + 'list_id' => '123', +]; + +$response = Sendy::subscribers()->delete( + data: $data, + async: false + ); + +``` + +Full Documentation [Here](https://sendy.co/api#delete-subscriber) + +#### Subscriber Status + +```php +use Coderflex\LaravelSendy\Facades\Sendy; + +$data = [ + 'email' => 'john@example.com', + 'list_id' => '123', +]; + +$response = Sendy::subscribers()->status( + data: $data, + async: false + ); + +``` + +Full Documentation [Here](https://sendy.co/api#subscription-status) + +#### Subscribers Count + +```php +use Coderflex\LaravelSendy\Facades\Sendy; + +$data = [ + 'email' => 'john@example.com', + 'list_id' => '123', +]; + +$response = Sendy::subscribers()->count( + listId: '123', + async: false + ); + +``` + +Full Documentation [Here](https://sendy.co/api#active-subscriber-count) + +### Lists + +Same thing as the __subscribers__ service, you can access the `lists()` service, then the http action you want. + +#### Get Lists + +```php +use Coderflex\LaravelSendy\Facades\Sendy; + +$data = [ + 'brand_id' => '123', + 'include_hidden' => 'yes', // either yes or no. +]; + +$response = Sendy::lists()->get( + data: $data, + async: false + ); + +``` +Full Documentation [Here](https://sendy.co/api#get-lists) + +### Brands + +__Laravel Sendy__ allows you to retrieve all the brand list you have by + +```php +use Coderflex\LaravelSendy\Facades\Sendy; + +$response = Sendy::brands()->get(); + +``` + +Full Documentation [Here](https://sendy.co/api#get-brands) + +### Create & Send Compaigns + +```php +use Coderflex\LaravelSendy\Facades\Sendy; + +$data = [ + 'subject' => 'Test Subject', + 'from_name' => 'John Doe', + 'from_email' => 'john@example.com', + 'reply_to' => 'alex@example.com', + 'title' => 'Test Title', + 'plain_text' => 'This is a plain text version of the email.', + 'html_text' => '

This is a HTML version of the email.

', + 'list_ids' => 'abc123', + 'segment_ids' => 'xyz456', + 'exclude_list_ids' => null, + 'exclude_segment_ids' => null, + 'brand_id' => '123', + 'query_string' => null, + 'track_opens' => 1, + 'track_clicks' => 1, + 'send_campaign' => 1, // if set to 1 the compaign will be created & sent. + 'schedule_date_time' => null, + 'schedule_timezone' => null, +]; + +$response = Sendy::compaigns()->create( + data: $data, + async: false + ); + +``` + +If you want to create and send the compaign at the same time, use `createAndSend` method + +```php + +$response = Sendy::compaigns()->createAndSend( + data: $data, + async: false + ); +``` + +Full Documentation [Here](https://sendy.co/api#create-send-campaigns) + +## Testing + +```bash +composer test +``` + +## Changelog + +Please see [CHANGELOG](CHANGELOG.md) for more information on what has changed recently. + +## Contributing + +Please see [CONTRIBUTING](CONTRIBUTING.md) for details. + +## Security Vulnerabilities + +Please review [our security policy](../../security/policy) on how to report security vulnerabilities. + +## Credits + +- [Oussama Sid](https://github.com/ousid) +- [All Contributors](../../contributors) + +## License + +The MIT License (MIT). Please see [License File](LICENSE.md) for more information. diff --git a/src/Facades/LaravelSendy.php b/src/Facades/Sendy.php similarity index 76% rename from src/Facades/LaravelSendy.php rename to src/Facades/Sendy.php index f98ecbc..2adc9fa 100644 --- a/src/Facades/LaravelSendy.php +++ b/src/Facades/Sendy.php @@ -5,17 +5,17 @@ use Illuminate\Support\Facades\Facade; /** - * @see \Coderflex\LaravelSendy\LaravelSendy + * @see \Coderflex\LaravelSendy\Sendy * * @method static \Coderflex\LaravelSendy\Resources\Subscribers subscribers() * @method static \Coderflex\LaravelSendy\Resources\Lists lists() * @method static \Coderflex\LaravelSendy\Resources\Brands brands() * @method static \Coderflex\LaravelSendy\Resources\Campaigns campaigns() */ -class LaravelSendy extends Facade +class Sendy extends Facade { protected static function getFacadeAccessor(): string { - return \Coderflex\LaravelSendy\LaravelSendy::class; + return \Coderflex\LaravelSendy\Sendy::class; } } diff --git a/src/Resources/Brands.php b/src/Resources/Brands.php index ba94cf1..4f2a9b2 100644 --- a/src/Resources/Brands.php +++ b/src/Resources/Brands.php @@ -2,12 +2,12 @@ namespace Coderflex\LaravelSendy\Resources; -use Coderflex\LaravelSendy\Facades\LaravelSendy; +use Coderflex\LaravelSendy\Facades\Sendy; class Brands { public function get() { - return LaravelSendy::post('/api/brands/get-brands.php'); + return Sendy::post('/api/brands/get-brands.php'); } } diff --git a/src/Resources/Campaigns.php b/src/Resources/Campaigns.php index b3fb387..ecc9977 100644 --- a/src/Resources/Campaigns.php +++ b/src/Resources/Campaigns.php @@ -3,14 +3,23 @@ namespace Coderflex\LaravelSendy\Resources; use Coderflex\LaravelSendy\DTOs\Campaigns\CampaignDTO; -use Coderflex\LaravelSendy\Facades\LaravelSendy; +use Coderflex\LaravelSendy\Facades\Sendy; class Campaigns { - public function create(array $data) + public function create(array $data, bool $async = false) { $data = CampaignDTO::validate($data); - return LaravelSendy::post('/api/campaigns/create.php', $data); + return Sendy::post('/api/campaigns/create.php', $data, $async); + } + + public function createAndSend(array $data, bool $async = false) + { + $data = array_merge($data, [ + 'send_compaign' => 1, + ]); + + return Sendy::post('/api/campaigns/create.php', $data, $async); } } diff --git a/src/Resources/Lists.php b/src/Resources/Lists.php index dde78e3..abbc42f 100644 --- a/src/Resources/Lists.php +++ b/src/Resources/Lists.php @@ -3,7 +3,7 @@ namespace Coderflex\LaravelSendy\Resources; use Coderflex\LaravelSendy\DTOs\Lists\ListsDTO; -use Coderflex\LaravelSendy\Facades\LaravelSendy; +use Coderflex\LaravelSendy\Facades\Sendy; class Lists { @@ -16,6 +16,6 @@ public function get(array $data, bool $async = false) { $data = ListsDTO::validate($data); - return LaravelSendy::post('/api/lists/get-lists.php', $data, $async); + return Sendy::post('/api/lists/get-lists.php', $data, $async); } } diff --git a/src/Resources/Subscribers.php b/src/Resources/Subscribers.php index 61500b1..afd833d 100644 --- a/src/Resources/Subscribers.php +++ b/src/Resources/Subscribers.php @@ -6,7 +6,7 @@ use Coderflex\LaravelSendy\DTOs\Subscribers\SubscribeDTO; use Coderflex\LaravelSendy\DTOs\Subscribers\SubscriberStatusDTO; use Coderflex\LaravelSendy\DTOs\Subscribers\UnsubscribeDTO; -use Coderflex\LaravelSendy\Facades\LaravelSendy; +use Coderflex\LaravelSendy\Facades\Sendy; class Subscribers { @@ -14,28 +14,28 @@ public function subscribe(array $data, bool $async = false) { $data = SubscribeDTO::validate($data); - return LaravelSendy::post('subscribe', $data, $async); + return Sendy::post('subscribe', $data, $async); } public function unsubscribe(array $data, bool $async = false) { $data = UnsubscribeDTO::validate($data); - return LaravelSendy::post('api/subscribers/unsubscribe.php', $data, $async); + return Sendy::post('api/subscribers/unsubscribe.php', $data, $async); } public function delete(array $data, bool $async = false) { $data = DeleteSubscriberDTO::validate($data); - return LaravelSendy::post('api/subscribers/delete.php', $data, $async); + return Sendy::post('api/subscribers/delete.php', $data, $async); } public function status(array $data, bool $async = false) { $data = SubscriberStatusDTO::validate($data); - return LaravelSendy::post('api/subscribers/subscription-status.php', $data, $async); + return Sendy::post('api/subscribers/subscription-status.php', $data, $async); } public function count(int $listId, bool $async = false) @@ -44,6 +44,6 @@ public function count(int $listId, bool $async = false) 'list_id' => $listId, ]; - return LaravelSendy::post('api/subscribers/subscriber-count.php', $data, $async); + return Sendy::post('api/subscribers/subscriber-count.php', $data, $async); } } diff --git a/src/LaravelSendy.php b/src/Sendy.php similarity index 96% rename from src/LaravelSendy.php rename to src/Sendy.php index 59e7257..2abdd1a 100644 --- a/src/LaravelSendy.php +++ b/src/Sendy.php @@ -4,7 +4,7 @@ use Coderflex\LaravelSendy\Concerns\InteractsWithHttpRequests; -class LaravelSendy +class Sendy { use InteractsWithHttpRequests; diff --git a/tests/Resources/BrandsTest.php b/tests/Resources/BrandsTest.php index 3e98357..13e4922 100644 --- a/tests/Resources/BrandsTest.php +++ b/tests/Resources/BrandsTest.php @@ -1,6 +1,6 @@ Http::response([123 => 'Brand Name'], 200), ]); - $response = LaravelSendy::brands()->get(); + $response = Sendy::brands()->get(); expect($response->json())->toBe([123 => 'Brand Name']); diff --git a/tests/Resources/CompaignsTest.php b/tests/Resources/CompaignsTest.php index cd83292..7b9321f 100644 --- a/tests/Resources/CompaignsTest.php +++ b/tests/Resources/CompaignsTest.php @@ -1,6 +1,6 @@ Http::response(['status' => 'Campaign created'], 200), + ]); + + $response = Sendy::campaigns()->create([ + 'subject' => 'Test Subject', + 'from_name' => 'John Doe', + 'from_email' => 'john@example.com', + 'reply_to' => 'alex@example.com', + 'title' => 'Test Title', + 'plain_text' => 'This is a plain text version of the email.', + 'html_text' => '

This is a HTML version of the email.

', + 'list_ids' => 'abc123', + 'segment_ids' => 'xyz456', + 'exclude_list_ids' => null, + 'exclude_segment_ids' => null, + 'brand_id' => 'brand123', + 'query_string' => null, + 'track_opens' => 1, + 'track_clicks' => 1, + 'send_campaign' => 0, + 'schedule_date_time' => null, + 'schedule_timezone' => null, + ]); + + expect($response->json())->toBe(['status' => 'Campaign created']); + + Http::assertSent(function ($request) { + return $request->url() === 'https://sendy.test/api/campaigns/create.php' && + $request['from_email'] === 'john@example.com' && + $request['from_name'] === 'John Doe' && + $request['api_key'] === 'test_api_key'; + }); +}); + it('can create and send a campaigns', function () { Http::fake([ 'https://sendy.test/api/campaigns/create.php' => Http::response(['status' => 'Campaign created and now sending'], 200), ]); - $response = LaravelSendy::campaigns()->create([ + $response = Sendy::campaigns()->createAndSend([ 'subject' => 'Test Subject', 'from_name' => 'John Doe', 'from_email' => 'john@example.com', @@ -31,7 +67,6 @@ 'query_string' => null, 'track_opens' => 1, 'track_clicks' => 1, - 'send_campaign' => 1, 'schedule_date_time' => null, 'schedule_timezone' => null, ]); diff --git a/tests/Resources/EndpointsTest.php b/tests/Resources/EndpointsTest.php index e0a72b2..37b51a2 100644 --- a/tests/Resources/EndpointsTest.php +++ b/tests/Resources/EndpointsTest.php @@ -1,6 +1,6 @@ Http::response(true, 200), ]); - $response = LaravelSendy::brands()->get(); + $response = Sendy::brands()->get(); })->throws(\Coderflex\LaravelSendy\Exceptions\InvalidApiKeyException::class); @@ -27,6 +27,6 @@ 'laravel-sendy.api_url' => null, ]); - $response = LaravelSendy::brands()->get(); + $response = Sendy::brands()->get(); })->throws(\Coderflex\LaravelSendy\Exceptions\InvalidApiUrlException::class); diff --git a/tests/Resources/ListsTest.php b/tests/Resources/ListsTest.php index 87a7b6d..d6efbe7 100644 --- a/tests/Resources/ListsTest.php +++ b/tests/Resources/ListsTest.php @@ -1,6 +1,6 @@ Http::response([123 => 'Custom List'], 200), ]); - $response = LaravelSendy::lists()->get([ + $response = Sendy::lists()->get([ 'brand_id' => 123, 'include_hidden' => 'yes', ]); diff --git a/tests/Resources/SubscribersTest.php b/tests/Resources/SubscribersTest.php index 7d7ba94..62b3a2b 100644 --- a/tests/Resources/SubscribersTest.php +++ b/tests/Resources/SubscribersTest.php @@ -1,6 +1,6 @@ Http::response(true, 200), ]); - $response = LaravelSendy::subscribers()->subscribe([ + $response = Sendy::subscribers()->subscribe([ 'name' => 'John Doe', 'email' => 'john@example.com', 'list' => 'abc123', @@ -37,7 +37,7 @@ 'https://sendy.test/api/subscribers/unsubscribe.php' => Http::response(true, 200), ]); - $response = LaravelSendy::subscribers()->unsubscribe([ + $response = Sendy::subscribers()->unsubscribe([ 'list' => 123, 'email' => 'jane@example.com', 'boolean' => true, @@ -57,7 +57,7 @@ 'https://sendy.test/api/subscribers/delete.php' => Http::response(true, 200), ]); - $response = LaravelSendy::subscribers()->delete([ + $response = Sendy::subscribers()->delete([ 'list_id' => 123, 'email' => 'john@example.com', ]); @@ -74,7 +74,7 @@ 'https://sendy.test/api/subscribers/subscription-status.php' => Http::response(['status' => 'Subscribed'], 200), ]); - $response = LaravelSendy::subscribers()->status([ + $response = Sendy::subscribers()->status([ 'list_id' => 123, 'email' => 'john@example.com', ]); @@ -87,7 +87,7 @@ 'https://sendy.test/api/subscribers/subscriber-count.php' => Http::response(25, 200), ]); - $response = LaravelSendy::subscribers()->count(123); + $response = Sendy::subscribers()->count(123); expect($response->json())->toBe(25); });