mautic/core-lib
Core library for Mautic, the open-source marketing automation platform. Provides shared PHP classes, services, and utilities used across Mautic bundles, helping developers build and extend campaigns, contacts, segments, and integrations.
mautic/core-lib is not a Laravel package you install with composer require. It is the shared PHP foundation (entities, repositories, services, config) of Mautic's marketing-automation platform — a standalone Symfony 4.4 / PHP 7.4–8.0 application with its own database, container, and cron suite. Read the repo to understand the domain model and API contract; you will never boot it inside a Laravel process.
Day to day you will be integrating with a running Mautic instance over its REST API and webhooks. Start there, not with the source tree.
Minimal steps
/api/ responds. Open the Swagger UI that ships at https://your-mautic/api/ — that is the real reference for what you will call.'api_enable_basic_auth' => true in config/local.php and provision a dedicated API user. OAuth2 is only worth the refresh-token machinery if you need per-user scoping or delegated third-party access.composer require mautic/api-library
// App\Providers\MauticServiceProvider
$this->app->singleton('mautic.contacts', function ($app) {
$auth = (new \Mautic\Auth\ApiAuth())->newAuth('BasicAuth', [
'userName' => config('services.mautic.user'),
'password' => config('services.mautic.password'),
]);
return (new \Mautic\MauticApi())->newApi(
'contacts', $auth, rtrim(config('services.mautic.base_url'), '/')
);
});
MauticClient interface so tests and future Mautic 5 upgrades do not touch call sites.First use case to validate end-to-end: take a Laravel registration, create the contact in Mautic, add them to a segment, and confirm the segment triggers a campaign. That single path exercises auth, field aliases, the contacts and segments endpoints, and Mautic's cron workers — the four things that break first.
Never call Mautic in the request cycle. Every write belongs in a queued job. The API is slow, non-transactional with your DB, and a Mautic outage must not take down signup or checkout.
SyncContactToMautic::dispatch($user)->onQueue('marketing');
Upsert, don't create. Mautic dedupes on email but returns a 400 on genuine conflicts. Look up first, then edit or create:
$existing = $contactsApi->getList("email:{$user->email}", 0, 1, 'id', 'ASC');
$payload = [
'email' => $user->email,
'firstname' => $user->first_name,
'lastname' => $user->last_name,
'ipAddress' => $user->registration_ip,
'tags' => ['laravel', 'plan:'.$user->plan],
];
$contact = empty($existing['contacts'])
? $contactsApi->create($payload)
: $contactsApi->edit($existing['contacts'][0]['id'], $payload, true); // true = PATCH
The third argument (true) is what developers miss most — without it edit() sends a PUT and blanks every field you don't include.
Batch aggressively for backfills and importers. Use createBatch() / editBatch() in chunks of 100 (Mautic's hard cap):
$users->chunk(100)->each(fn ($chunk) =>
$contactsApi->createBatch($chunk->map(fn ($u) => [...])->all())
);
Segments are the integration point, not tags. Tags are fine for ad-hoc filtering, but campaigns fire off segment membership. Use addContact() / removeContact() and let mautic:segments:update do the evaluation — never assume membership is queryable immediately after the call returns.
Webhook receiver that actually works. Mautic posts application/x-www-form-urlencoded, not JSON:
Route::post('/webhooks/mautic/{secret}', MauticWebhookController::class)
->withoutMiddleware([\App\Http\Middleware\VerifyCsrfToken::class]);
Check the {secret} path segment against config, then hand the raw input to a job keyed on eventId for idempotency. Do not try to validate() the payload shape — keys vary per trigger type.
Define field aliases before you push them. Mautic will not auto-create custom fields from an API payload. Create them once via /api/fields/contact/new, cache the alias map in config, then push. Sending 'plan' => 'pro' with no plan alias means the value silently disappears.
Identity mapping. Add mautic_contact_id to your local model, backfill it via a job, and keep a reconciliation job looking for form-created contacts that never mapped back.
Client-side tracking. Drop mtc.js into the base Blade layout and use mt('send', 'pageview', {...}) for anonymous identification; reserve the API for anything that must be authoritative. Don't double-count the same event through both paths.
/api/leads is /api/contacts. The legacy naming lives throughout the API and SDK — getContacts() returns lead-shaped data. Read docs with that translation in your head.$start + $limit, limit capped at 100. There is no total you can trust under concurrent writes — loop until an empty page, never on a count.getList('email:[email protected]') handles the shorthand; complex queries need the where[n][col|expr|val] array form. Escape !, %, and : in user-supplied values, or you get silent zero-result queries instead of errors.ApiException::getResponse()->getBody() for the real validation message; getCode() gives you 400 vs 401 vs 404. A 404 frequently means "endpoint not enabled for this user's role," not "resource missing."mautic:segments:update, mautic:campaigns:update, and the email queue commands are not running, segments and campaigns go stale silently and emails never send. Alert on a cron heartbeat, not on error logs.composer update + doctrine:migrations:migrate + cache clear on the Mautic host).MauticClient yourself, with a circuit breaker so Mautic slowness cannot cascade into user-facing latency.How can I help you explore Laravel packages today?