Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Mailerlite Api V2 Php Sdk Laravel Package

mailerlite/mailerlite-api-v2-php-sdk

Official PHP SDK for MailerLite API v2, offering straightforward methods to manage subscribers, groups, campaigns, fields, and automations. Works with Laravel or any PHP app via Composer, with simple client setup and response handling.

Deep Wiki
Context7

Getting Started

This is the MailerLite Classic SDK — it talks to https://api.mailerlite.com/api/v2/, not the newer MailerLite API. Before you write a line of code, confirm you actually have a Classic account and a Classic API key (Classic dashboard → Integrations → Developer API). A key from the new MailerLite UI will 401 against this package.

composer require mailerlite/mailerlite-api-v2-php-sdk
use MailerLiteApi\MailerLite;

$ml = new MailerLite(['api_key' => config('services.mailerlite.key')]);

// Verify credentials and see the response shape
$groups = $ml->groups()->get();

The client is a thin factory: each call like ->subscribers(), ->groups(), ->fields(), ->segments(), ->campaigns(), ->webhooks() returns an API resource object that exposes roughly get(), find($id), create($data), update($id, $data), delete($id).

Where to look first:

  1. README.md — the examples are the de-facto documentation.
  2. src/Api/ — one class per endpoint; if a method isn't in the README, it's probably defined here.
  3. src/Common/RestClient.php — this is where JSON decoding, error throwing, and the base URI live. Read it once; it explains 90% of the surprises below.

First real use case — a newsletter signup form:

$subscriber = $ml->groups()->subscribers($groupId)->create([
    'email'  => $request->email,
    'name'   => $request->name,
    'fields' => ['company' => $request->company],
]);

That call both creates the subscriber (if new) and adds them to the group, which is exactly what you want for a landing-page/opt-in flow. Get $groupId once from $ml->groups()->get() and put it in config.

Implementation Patterns

Bind a single client in the container

Don't instantiate MailerLite per call site. Register a singleton so the config array and any injected Guzzle client are set in one place:

// app/Providers/MailerLiteServiceProvider.php
$this->app->singleton(MailerLite::class, fn () => new MailerLite([
    'api_key' => config('services.mailerlite.key'),
]));

Add mailerlite.key to config/services.php reading env('MAILERLITE_API_KEY'). In Octane/queue workers, resolve from the container per job rather than storing the client on a long-lived property.

Wrap it in your own gateway

The SDK has no interfaces, no DTOs, and no fakes. Put a small class between your app and the SDK — App\Services\Newsletter\MailerLiteGateway with subscribe(), unsubscribe(), addToGroup(), removeFromGroup() — and bind it to an interface. This is the single highest-value thing you can do with this package:

  • it isolates you from the eventual Classic sunset/migration,
  • it gives you a mockable seam for tests,
  • it centralises the group IDs, field-key mapping, and status handling.

Model the three writes you actually need

Most apps only ever do a handful of operations. Keep them in the gateway:

// 1. Create/upsert a subscriber in a group (signup)
$ml->groups()->subscribers($groupId)->create($payload);

// 2. Update profile fields
$ml->subscribers()->update($id, ['fields' => ['company' => 'Acme']]);

// 3. Unsubscribe (status change, not delete — preserves stats)
$ml->subscribers()->update($id, ['status' => 'unsubscribed']);

Prefer status changes over ->delete(). Deleting in Classic is destructive and loses history; unsubscribed is what your users and the compliance team expect.

Bulk imports: page in, throttle out

There is no cursor helper. Paginate manually with limit/offset and treat the batch/REST endpoints as the fast path:

$offset = 0;
do {
    $page = $ml->subscribers()->get(['limit' => 1000, 'offset' => $offset]);
    foreach ($page as $sub) { /* ... */ }
    $offset += 1000;
} while (count($page) === 1000);

For writes, the Classic API exposes a batch endpoint (POST /batch, up to ~50 operations per request). If your installed version has a batch() resource, use it; otherwise reach it through the underlying RestClient/Guzzle. Either way, run imports as a queued job with a rate limiter — see below.

Queue everything that isn't on the request path

Signup forms can tolerate a synchronous call; imports, campaign creation, and webhook fan-out cannot. Use a job with $tries = 3, a backoff() that grows, and Laravel's RateLimited middleware or Redis::throttle to stay under the Classic limit (120 req/min per account).

public function middleware(): array
{
    return [(new RateLimited('mailerlite'))->releaseAfter(5)];
}

Catch MailerLiteApi\Exceptions\ApiException, inspect for 429/5xx, and release() — 4xx (bad email, missing field) should fail fast, not retry forever.

Cache the slow-moving metadata

Group IDs, custom field keys, and segment IDs change rarely and are read on every signup. Cache::remember('mailerlite.groups', 3600, fn () => $ml->groups()->get()) removes a network round-trip from your hot path. Invalidate on deploy or via a scheduled command.

Webhooks instead of polling

Classic supports webhooks (subscriber.created, subscriber.unsubscribed, subscriber.updated, campaign.sent). Register with $ml->webhooks()->create(['url' => ..., 'events' => [...]]), verify and normalise the payload in a controller, then dispatch a job. Classic webhooks are not signed, so include a shared secret in the URL path or query string and reject anything without it — then re-fetch the subscriber by ID before trusting the body.

Testing

Because there are no built-in fakes, test your gateway, not the SDK. Bind a fake implementation of your interface in the container for feature tests. For integration-style tests, construct MailerLite/its RestClient with a Guzzle MockHandler if the version you installed allows injecting a client — check src/Common/RestClient.php for the constructor signature before relying on it.

Gotchas and Tips

This package is on a sunset API and is itself unmaintained (last release Aug 2021). Pin the version in composer.json, and treat migration to the current MailerLite API/SDK as scheduled technical debt rather than a someday task. The differences are not cosmetic: different host, Bearer-token auth, different payload shapes and endpoint paths. Keeping your app behind the gateway interface above is what makes that migration a one-file change.

Responses are raw decoded JSON, not models. Collection calls generally return a plain PHP array of stdClass objects (the data envelope is unwrapped by the resource class); single-resource calls return one object. Access with $subscriber->id, not $subscriber['id']. Don't assume types — fields arrives as an object/array whose keys are your custom field keys.

Pagination metadata is lost. The meta/links block from the API response is not surfaced by the resource, so you cannot get a total count or a next-page URL. Drive loops off count($page) < $limit and always pass an explicit limit.

Rate limits are per-account and shared with everything else hitting the API — including the MailerLite UI automations. A 429 in staging is usually someone else's import. Log the raw body on failure; Classic returns a nested error object with a message and code, and the exception message alone is often terse.

Empty bodies on DELETE. A 204 decodes to null. Guard against calling methods on a "response" that is null — this bites people who wrap every call in ->something().

Partial updates can wipe fields. When updating fields, send the full field map you want the subscriber to end up with rather than a single key, unless you've verified the merge behaviour on your API version. PUT semantics on Classic have historically been closer to replace than patch.

Understand status vs type. status is active/unsubscribed/unconfirmed/bounced/junk. Creating a subscriber with type => 'unconfirmed' (or relying on the group's double opt-in setting) triggers a confirmation email; leaving it default activates immediately. If EU consent matters, set this deliberately per group — the group setting can silently override your intent.

Custom field keys must already exist. fields are keyed by the field's key, not its display name, and date fields expect Y-m-d. Fetch $ml->fields()->get() once, cache it, and validate incoming keys against it — unknown keys are dropped or error depending on version, both of which are confusing to debug.

Email can stand in for the ID on several subscriber endpoints in the Classic API, which is great for idempotent imports (update($email, ...)) but means a "not found" may actually be an encoding problem — always URL-encode addresses with + in them, or the SDK will happily send a broken path.

No default Guzzle timeout. A hung connection blocks a PHP-FPM worker indefinitely. If your version lets you pass a Guzzle client into MailerLite/RestClient, set timeout and connect_timeout; if not, at least run non-critical calls in queued jobs with a job-level timeout.

Composer may fight you. Old Guzzle constraints are the most common install failure on modern Laravel. Check the require block before installing; if resolution fails, you'll need a fork with a widened constraint or to isolate the call behind a separate service rather than downgrading Guzzle across the app.

Debugging tip: when a call behaves oddly, dump json_decode($responseBody) from a one-off raw Guzzle request to the same path. The SDK adds almost nothing on top of the HTTP call, so the API docs and the raw response are the ground truth — the wrapper is not hiding behaviour, it's just not documenting it.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
uni-method/symfony-lib
tacticmedia/scim-server-bundle
neika-ai/symfony-bundle
mattleads/agent-publishing-bundle
glavweb/framework-bundle
glavweb/extra-bundle
glavweb/api-bundle
glavweb/admin-bundle
adachsoft/api-version-bundle
elsayed85/laravel-api-exceptions
ouhssini/laravel-demo-mode
daworks/ncloud-outbound-mailer-driver
gp247/cms
ignibyte/aic-gates
nexa-merchant/blog
novasa/laravel-language-center
opencage/geocode
adolfoholzer/parameters
livenetworks/ln-starter
shitoudev/permissionmanager