hocza/sendy
A Laravel package for integrating the Sendy email newsletter platform. It provides a clean wrapper around Sendy’s API for subscribing and unsubscribing users, managing mailing lists, and handling configuration in Laravel applications.
Install and publish the package config:
composer require hocza/sendy
php artisan vendor:publish --provider="Hocza\Sendy\SendyServiceProvider" --tag=config
Set the Sendy connection values in .env:
SENDY_API_KEY=your-sendy-api-key
SENDY_INSTALLATION_URL=https://sendy.example.com
SENDY_LIST_ID=your-default-list-id
Look first at:
config/sendy.php after publishing, to confirm config keys and URL handling.vendor/hocza/sendy/src to see how Sendy’s plain-text API responses are returned or normalized.A typical first use case is subscribing a user during registration or newsletter opt-in:
use Hocza\Sendy\Facades\Sendy;
Sendy::subscribe(
config('sendy.list_id'),
$user->email,
$user->name,
true // confirm meaning of boolean/double opt-in flag in README
);
Do not call Sendy inline in a web request for day-to-day use. Treat the first integration as: validate input, persist local preference, dispatch a queued job, and let the job call Sendy.
Wrap the package behind an app service. Keep Sendy::subscribe() calls inside one NewsletterService or SendyListService. This lets you swap the package, add logging, normalize responses, and fake the integration in tests without spreading vendor calls through controllers.
final class NewsletterService
{
public function subscribe(User $user, string $listId): void
{
// Call the package client here, not in controllers.
}
}
Use queued jobs for all Sendy writes. Sendy is an external HTTP dependency. Dispatch a job after the local user/preference transaction commits:
DB::afterCommit(fn () => SubscribeToSendy::dispatch($user->id, $listId));
Add tries, backoff(), and a queue rate limit. Subscribe is usually idempotent enough to retry, but unsubscribe/delete semantics should be checked per endpoint.
Model lists explicitly. Map application segments or audiences to Sendy list IDs in config or a database table:
'lists' => [
'newsletter' => env('SENDY_LIST_NEWSLETTER_ID'),
'product_updates' => env('SENDY_LIST_PRODUCT_ID'),
],
Pass the list ID per call rather than relying on a single global list. This is the main scaling point once you have more than one audience.
Keep local preference as the source of truth. Store newsletter_opted_in, newsletter_opted_in_at, and list subscriptions locally. Sendy is the delivery system, not your application’s subscription state. On preference changes, update locally first, then queue the Sendy sync.
Use status checks for reconciliation, not every request. A scheduled command can check a small sample or recently changed users with the package’s status method, if exposed. Do not status-check on every page load or login.
Test by faking your wrapper, not Sendy. Bind a fake NewsletterService in tests and assert jobs are dispatched. If you test the package client itself, use Laravel’s HTTP fake only if the package uses Laravel’s HTTP client; otherwise mock the package client class.
1 or true, while failures return an error string such as Invalid email address. Do not treat every HTTP 200 as success. Inspect what the package returns and normalize it in your wrapper.boolean argument is easy to misread. In Sendy’s API it typically controls double opt-in behavior. Set it explicitly and test the actual email flow; do not rely on the package default./sendy path, or a full API endpoint. Avoid trailing-slash duplication and test with the exact production URL.php artisan config:clear or php artisan config:cache after changing Sendy credentials or list IDs.How can I help you explore Laravel packages today?