jeffersongoncalves/laravel-klaviyo
A Laravel integration for Klaviyo, the email and SMS marketing platform. Manage profiles, lists, events, and campaigns through a clean, expressive API, with configuration and service provider support for quick setup in any Laravel application.
composer require <vendor>/laravel-klaviyo
php artisan vendor:publish --tag=klaviyo-config
.env:
KLAVIYO_API_KEY=pk_xxx
php artisan config:clear
config/klaviyo.php — API key, base URL, timeout, retry/rate-limit settings.A good first use case is the most common Laravel/Klaviyo workflow: upsert a profile, add it to a list, and track an event.
use App\Models\User;
$klaviyo = app(\Klaviyo\Client::class); // confirm exact binding in the service provider
$profile = $klaviyo->profiles()->upsert([
'email' => $user->email,
'first_name' => $user->first_name,
'last_name' => $user->last_name,
]);
$klaviyo->lists()->addProfiles($listId, [$profile['id']]);
$klaviyo->events()->create([
'profile' => ['email' => $user->email],
'metric' => ['name' => 'Started Trial'],
'properties' => ['plan' => 'pro'],
]);
If the package exposes a facade, prefer dependency injection in application code and reserve the facade for tinker/scripts. Run a single read call first, such as listing segments or metrics, to validate credentials before testing write operations.
Inject the client, not the facade. Bind the package client through the container and inject it into services, jobs, and listeners. This keeps tests swappable and avoids facade coupling.
class KlaviyoSubscriber
{
public function __construct(private \Klaviyo\Client $klaviyo) {}
}
Wrap writes in queued jobs. Profile upserts, list membership changes, and event tracking should generally be queued. This isolates Klaviyo latency and failures from the request lifecycle and gives you retry/backoff semantics.
TrackKlaviyoEvent::dispatch($user, 'Order Placed', ['order_id' => $order->id])
->onQueue('klaviyo');
Use a dedicated integration layer. Keep Klaviyo calls out of controllers and models. A small service such as App\Services\Klaviyo\SubscriberSync makes it easier to enforce consent rules, map local fields, and centralize error handling.
Model Klaviyo resources as read/write boundaries. Profiles, lists, events, and templates are typically write-capable. Campaigns, flows, metrics, and segments are often read-oriented in an API client. Treat reporting resources as read-only unless the package explicitly documents otherwise.
Handle pagination explicitly. List endpoints commonly return cursor-based pagination. Loop until the cursor is exhausted rather than assuming a single response is complete.
$cursor = null;
do {
$page = $klaviyo->profiles()->all(['page[cursor]' => $cursor]);
foreach ($page['data'] as $profile) { /* ... */ }
$cursor = $page['links']['next'] ?? null;
} while ($cursor);
Map local users to Klaviyo profiles deliberately. Decide whether email or an external ID is the canonical identifier, and use the same identifier consistently across profiles, events, and list operations. Store the returned Klaviyo profile ID if you need stable references.
Isolate multi-tenant credentials. If you operate multiple Klaviyo accounts, avoid a single global config. Resolve a client instance per tenant/account with its own API key and configuration rather than mutating global config at runtime.
Test with Laravel’s HTTP fake. Since the package is an API client, assert outgoing requests instead of mocking every resource method:
Http::fake(['api.klaviyo.com/*' => Http::response(['data' => []], 200)]);
// trigger your integration service/job, then assert the request payload
Http::assertSent(fn ($request) => $request['data']['attributes']['email'] === '[email protected]');
Also consider binding a fake client implementation in the container for unit tests of higher-level services.
Use events/listeners for domain workflows. For example, listen to Registered or OrderPlaced and dispatch a Klaviyo job. This keeps Klaviyo concerns out of the registration/checkout path and makes it easy to disable or requeue syncs.
Add observability around integration calls. Log failures with context (endpoint, status, Klaviyo request ID) but never log API keys or full PII payloads. Emit metrics for 4xx/5xx rates so credential or payload issues are visible.
429. Do not retry aggressively inside synchronous requests.KLAVIYO_API_KEY or related env values in a cached deployment, run php artisan config:cache as part of deploy. Local config:clear fixes most “why is it using the old key” issues.composer.json and review changelogs before minor upgrades. There is no prior assessment to compare against, so treat this release as the baseline.How can I help you explore Laravel packages today?