ecomailcz/ecomail
Laravel integration for the Ecomail.cz email marketing API. Manage mailing lists, subscribers, tags, custom fields, campaigns, and events with a fluent client, facades, and config-based authentication for newsletter and transactional workflows.
ecomailcz/ecomail is the official PHP wrapper for the Ecomail.cz transactional/marketing API — not a Laravel package. There is no auto-discovery, no facade, no config file. You get a PSR-4 autoloaded set of classes under Ecomail\ and a Guzzle client underneath. Your first job as a Laravel dev is to wrap it.
composer require ecomailcz/ecomail
Where to look first: the repo README for the resource list (lists, subscribers, and the campaign/webhook resources as they exist in your pinned version), then Ecomail's API docs at https://api2.ecomailapp.cz/. Method names drift between releases — trust the README shipped with the version you install, not blog posts.
Bind it as a singleton so the Guzzle client is reused:
// config/services.php
'ecomail' => ['key' => env('ECOMAIL_API_KEY')],
// App\Providers\AppServiceProvider::register()
$this->app->singleton(\Ecomail\Ecomail::class, fn () =>
new \Ecomail\Ecomail(config('services.ecomail.key'))
);
First use case — the one that covers 80% of real work — is pushing a subscriber onto a list:
$ecomail = app(\Ecomail\Ecomail::class);
$ecomail->subscribers->create($listId, [
'email' => $user->email,
'name' => $user->name,
'subscriber_status' => 1, // 1 = subscribed
]);
Wrap it in a domain service. Controllers and jobs should never touch Ecomail\Ecomail directly. A NewsletterService (or EcomailClient interface) with methods like subscribe(User $u, int $listId) keeps the SDK at the edge, makes it swappable, and lets you mock your own boundary instead of a concrete third-party class.
Queue everything. The SDK is synchronous Guzzle — every call blocks. Never call it inside a web request. Dispatch a job on a dedicated marketing or ecomail queue with tries = 3 and backoff = [10, 60, 300]. Rate limits are the usual reason to isolate this queue.
Event-driven subscription. UserRegistered → SubscribeToNewsletter listener that dispatches the queued job. This is the clean Laravel shape and keeps signup latency decoupled from Ecomail availability.
Bulk imports. For backfills, chunk by a few hundred, dispatch one job per chunk, and let the queue serialise them. Don't loop tens of thousands of subscribers in a single job — you'll hit the timeout and the rate limit at the same time.
Webhooks are your responsibility. Ecomail can push opens, clicks, bounces and unsubscribes back to you, but the SDK does not help — you write a plain route/controller, verify the payload, and reconcile against your own subscriber state. This is the pattern most teams need on day two.
Testing. Fake your wrapper service ($this->mock(NewsletterService::class)). If you must test the SDK layer itself, inject a Guzzle MockHandler — but don't hit the live API in CI, and don't try to Mockery::mock(\Ecomail\Ecomail::class) if it has final methods.
ecomailcz/ecomail-php but the Composer package is ecomailcz/ecomail. Double-check what you actually installed before debugging "class not found"..env, never log them, and rotate via Ecomail's dashboard if leaked.GuzzleHttp\Exception\ClientException (4xx, including 401 bad key and 422 validation) separately from ServerException (5xx). 429 means rate limited — retry with backoff, don't hammer.subscriber_status is a footgun. Getting the integer wrong silently unsubscribes people or marks them unconfirmed. Treat it as a constant, not a magic number.https://api2.ecomailapp.cz/... with the same key. Keep a thin raw() helper on your service for exactly this.lists->all() and custom-field definitions change rarely — cache for a few minutes to stay well under rate limits and to avoid a cold API call on every page render.composer.json and budget for occasionally dropping to raw HTTP when a method you need doesn't exist. Don't build your architecture assuming the SDK will grow with your needs — your NewsletterService wrapper is the abstraction that protects you.How can I help you explore Laravel packages today?