jeffersongoncalves/laravel-mailchimp
A Laravel package for integrating Mailchimp, making it easy to connect your app to Mailchimp's API for managing audiences, subscribers, and newsletter signups with a simple, fluent interface.
The package is a thin Laravel wrapper around Mailchimp's Marketing API (v3), published as a fresh 1.0.0. Because this is the initial tagged release, treat the source as the source of truth — the release notes only link to the changelog, so there are no documented breaking changes or feature callouts to anchor on.
Minimal setup:
composer require jeffersongoncalves/laravel-mailchimp
php artisan vendor:publish --tag=mailchimp-config
Add your credentials to .env:
MAILCHIMP_API_KEY=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx-us21
MAILCHIMP_SERVER_PREFIX=us21
Then confirm the service provider and facade are auto-registered (Laravel package discovery). If they are not, register them in config/app.php.
Where to look first:
config/mailchimp.php — the published config reveals the supported options (API key, server prefix, timeout, default list, cache settings).src/ — the service provider and the main client/service class define the public surface area.First use case — subscribe a user to a list:
use JeffersonGoncalves\Mailchimp\Facades\Mailchimp;
Mailchimp::lists()->addMember('audience-id', [
'email_address' => $user->email,
'status' => 'subscribed',
'merge_fields' => ['FNAME' => $user->first_name],
]);
If the exact fluent API differs, the underlying mailchimp/marketing SDK response shape is what you will be working with, so you can fall back to that documentation for method names and payload structure.
Facade vs. container injection. Prefer injecting the package's client/service into your own classes over reaching for the facade statically. It keeps your code testable and lets you swap the binding in tests. Reserve the facade for tinker sessions and one-off scripts.
Wrap Mailchimp calls in your own service. Do not scatter Mailchimp:: calls across controllers. Create an App\Services\NewsletterService (or similar) that composes the package client. This gives you one place to add retries, logging, and error translation. Mailchimp's API returns structured error responses (with title, status, detail); normalize those to your domain exceptions at that boundary.
Sync on model events, but queue them. The classic pattern is a UserObserver that subscribes on created and updates on updated. Always throw the sync into a job — Mailchimp is an external HTTP call and will block your request cycle. A SyncSubscriberToMailchimp job with $tries = 3 and a backoff handles transient failures.
class SyncSubscriberToMailchimp implements ShouldQueue
{
use InteractsWithQueue, Queueable;
public function handle(NewsletterService $newsletter): void
{
$newsletter->upsertMember($this->user);
}
}
Upsert, don't create. Mailchimp's "add member" endpoint returns a 400 if the email already exists. Use the member PUT endpoint (upsert by subscriber hash) for idempotent syncing. This makes your jobs safely retryable.
Webhooks for the reverse direction. Mailchimp can POST back on subscribe/unsubscribe/profile updates. Register a route, then verify the request using the webhook secret (Mailchimp sends it as a query parameter or header depending on configuration). Handle unsubscribe and cleaned events to keep your local state consistent. Process webhook payloads in a job — Mailchimp expects a fast 2xx response.
Batch operations for bulk imports. For large syncs, use the batch API rather than looping. It is dramatically faster and respects rate limits more gracefully. This is a good candidate for a chunked console command run via the scheduler.
Testing. The Mailchimp client ultimately makes Guzzle/HTTP calls. Use Laravel's Http::fake() at the SDK boundary if the package is built on Laravel's HTTP client, or bind a mock of the package's client interface into the container. Assert that your job dispatches and that the correct payload shape is sent — that is the part that breaks when Mailchimp changes merge field validation.
The API key includes the data center suffix. xxxx-us21 means server prefix us21. If the package requires the prefix separately (likely, given the config file), a mismatch here produces confusing 404 or Invalid Resource errors. Parse the suffix from the key if you want one source of truth.
Rate limits are real and specific. Mailchimp allows roughly 10 concurrent connections and about 10 requests per second per key. Loops over thousands of users will hit 429s. The SDK does not always retry automatically — check whether the package wires in retry middleware. If not, add ->retry() config on the Guzzle client or your job middleware.
Merge fields fail fast and hard. Setting FNAME when the audience has no FNAME merge field returns a 400. Validate against the audience's merge fields (fetch once, cache it) or your imports will fail per-record rather than per-batch. *|FNAME|*-style merge tags in emails are separate from API merge fields — easy to conflate.
status_if_new vs status. On upsert, status_if_new sets the status only for new subscribers; status overwrites. Accidentally sending status: subscribed on updates can re-subscribe people who unsubscribed, which is a compliance problem. Use the if_new variants for sync jobs.
Do not double-subscribe already-subscribed users via the "add" endpoint. As above — this is the single most common integration bug and returns a noisy 400 even though "already subscribed" is arguably success. Treat it as idempotent in your service layer.
Cache list/audience metadata. Audiences, merge fields, and segments change rarely. Cache these with Cache::remember and a TTL to cut API calls and sidestep rate limits. Invalidate on webhook events that modify schema.
Extension points. The published config is the primary extension point — timeouts, default audience, cache store. If the package exposes an interface for the client, bind your own implementation in a service provider to wrap logging or retries. Check for package-level events (e.g., MemberSubscribed) before building your own — listening to those is cleaner than observing responses.
Logging and observability. Mailchimp errors often hide real causes behind generic 400s. Log the full request/response detail field in non-production environments while a new integration stabilizes. Once it is stable, keep an alert on 4xx rates — a spike usually means merge-field drift or a misconfigured audience.
Pin the version. 1.0.0 is the first tag and the release notes document nothing. Review the source before deploying, pin exact versions in composer.json, and expect the API surface to mature. Read the changelog between patch releases — pre-1.1 wrappers often adjust method signatures as real-world usage surfaces edge cases.
How can I help you explore Laravel packages today?