nextmigrant / laravel-sendy
A generic Sendy email marketing API client for Laravel
Requires
- php: ^8.2
- illuminate/contracts: ^10.0||^11.0||^12.0
- spatie/laravel-package-tools: ^1.16
Requires (Dev)
- orchestra/testbench: ^8.0||^9.0||^10.0
- phpunit/phpunit: ^10.5||^11.0
README
A reusable Laravel package that wraps the Sendy self-hosted email marketing API. It provides a clean SendyService class for subscribing/unsubscribing users, managing list membership, querying subscription statuses and active counts, and retrieving brand lists — all with a built-in production environment guard so no real API calls are ever made during development or testing.
Installation
composer require nextmigrant/laravel-sendy
Optionally publish the config file:
php artisan vendor:publish --tag=sendy-config
This creates config/sendy.php. You can also set everything via environment variables:
| Variable | Description |
|---|---|
SENDY_ENABLED |
Enable or disable all API calls (default: true). Set to false in local/staging. |
SENDY_API_KEY |
Your Sendy installation API key |
SENDY_URL |
Base URL of your Sendy installation (e.g. https://sendy.yourdomain.com) |
SENDY_NEW_USERS_LIST_ID |
List ID for the default "new signups" list |
Usage
You can use the Sendy facade or inject/instantiate SendyService directly:
use NextMigrant\Sendy\Facades\Sendy; // Via facade $response = Sendy::subscribe('john@example.com', 'list-abc', 'John', 'Doe');
use NextMigrant\Sendy\SendyService; // Via direct instantiation $sendy = new SendyService; $response = $sendy->subscribe('john@example.com', 'list-abc', 'John', 'Doe'); // Or via dependency injection (registered as a singleton) public function __construct(private SendyService $sendy) {}
Note: All methods are guarded by the
sendy.enabledconfig value. When set tofalse(viaSENDY_ENABLED=falsein your.env), methods returnnull(or[]forgetLists) without making any API calls.
Subscribe
$response = $sendy->subscribe( email: 'john@example.com', listId: 'your-list-id', firstName: 'John', lastName: 'Doe', ); // With optional full name and custom fields $response = $sendy->subscribe( email: 'john@example.com', listId: 'your-list-id', firstName: 'John', lastName: 'Doe', fullName: 'Dr. John Doe', options: [ 'country' => 'CA', 'gdpr' => 'true', 'Birthday' => '1990-01-15', ], );
Unsubscribe
$response = $sendy->unsubscribe('john@example.com', 'your-list-id');
Delete Subscriber
$response = $sendy->deleteSubscriber('john@example.com', 'your-list-id');
Get Subscription Status
$response = $sendy->getSubscriptionStatus('john@example.com', 'your-list-id'); // $response->message will be one of: // Subscribed, Unsubscribed, Unconfirmed, Bounced, Soft bounced, Complained
Get Active Subscriber Count
$response = $sendy->getActiveSubscriberCount('your-list-id'); // $response->message contains the count as a string (e.g. "1523")
Get Lists
$lists = $sendy->getLists(brandId: 1); // Returns: [['id' => 1, 'name' => 'Newsletter'], ...] // Include hidden lists $lists = $sendy->getLists(brandId: 1, includeHidden: true);
Response Object
All methods (except getLists) return a SendyResponse DTO:
use NextMigrant\Sendy\SendyResponse; $response->success; // bool $response->message; // string — "1" on success, or an error message
The static factory SendyResponse::fromApiResponse(string $body) treats "1" and "true" (case-insensitive) as success.
Testing
composer test
License
MIT