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

Mailchimp Laravel Package

nztim/mailchimp

A Laravel wrapper for the Mailchimp API v3 that simplifies managing lists, subscribers, campaigns, and related resources. Includes configuration, service provider integration, and an expressive PHP interface for quick Mailchimp operations.

View on GitHub
Deep Wiki
Context7

Getting Started

nztim/mailchimp is a thin, DrewM-style wrapper around the Mailchimp API v3 (GET/POST/PATCH/DELETE verb methods) with a Laravel service provider and facade. The wrapper does very little magic — most of your day-to-day work is knowing Mailchimp's REST endpoints and payload shapes.

Install and wire up:

composer require nztim/mailchimp
php artisan vendor:publish --provider="Nztim\Mailchimp\MailchimpServiceProvider"

On Laravel 5.5+ the provider/facade are auto-discovered. On older versions register Nztim\Mailchimp\MailchimpServiceProvider and alias Mailchimp in config/app.php.

Set the two things that matter in .env:

MAILCHIMP_API_KEY=0123456789abcdef-us14
MAILCHIMP_LIST_ID=abc123def4

The API key must include the data-center suffix (-us14); the wrapper derives the base URL from it.

First use case — subscribe a user to your default list:

use Nztim\Mailchimp\Mailchimp;

Mailchimp::post("lists/{$listId}/members", [
    'email_address' => '[email protected]',
    'status'        => 'subscribed',   // or 'pending' for double opt-in
    'merge_fields'  => ['FNAME' => 'Jane', 'LNAME' => 'Doe'],
]);

Where to look first:

  1. config/mailchimp.php — confirm the key names and whether a default list is exposed.
  2. The published Mailchimp class — this is the source of truth for available helper methods (if any) versus the raw get/post/patch/delete verbs.
  3. The Mailchimp API v3 reference — endpoint names, fields, and error codes are what you'll actually be reading; the package only transports them.

Implementation Patterns

Always route writes through the queue. A subscription call is a remote HTTP request; do it in a ShouldQueue job triggered by Registered/ProfileUpdated events rather than inline in the controller.

class SyncToMailchimp implements ShouldQueue
{
    public function handle(Mailchimp $mailchimp): void
    {
        $hash = md5(strtolower(trim($this->user->email)));
        $mailchimp->put("lists/{$listId}/members/{$hash}", [
            'email_address' => $this->user->email,
            'status_if_new' => 'subscribed',
            'merge_fields'  => ['FNAME' => $this->user->first_name],
        ]);
    }
}

Use PUT for upserts, POST for strict creates. POST /lists/{id}/members returns 400 Member Exists when the address is already there. PUT /lists/{id}/members/{subscriber_hash} is idempotent and is the correct verb for any "create or update" flow (registration, profile sync, nightly backfill).

Type-hint the class, don't reach for the facade inside services. Resolving Nztim\Mailchimp\Mailchimp from the container keeps it swappable and testable; use the facade only at controller/console boundaries.

Bulk work belongs in /batches. Syncing thousands of contacts one-by-one will hit Mailchimp's 10-concurrent-connection limit and the 429 handler. Build the operations array and POST a batch:

Mailchimp::post('batches', [
    'operations' => $ops, // [['method' => 'PUT', 'path' => 'lists/.../members/'.$hash, 'body' => json_encode($body)], ...]
]);

Webhooks for inbound state. Register a Mailchimp webhook to a Laravel route, then reconcile the member on subscribe/unsubscribe/cleaned events. The package doesn't help with verification — Mailchimp doesn't sign payloads, so either fetch the member back before trusting the event or gate the route behind a secret path/token.

Testing. The wrapper issues real Guzzle requests, so Http::fake() won't intercept it. Bind a mock of the Mailchimp class in the container (or the facade) and assert on get/post/patch calls, or extract a small Newsletter interface around it and fake that. Doing the latter also gives you an escape hatch if the package is eventually abandoned.

Multi-list setups. Treat the configured list_id as a default, not a constraint — pass the list id explicitly into each call so audience selection is a parameter, not global state.

Gotchas and Tips

  • API key data center. xxx-us14 is required in full. A key missing the suffix (or copied from a different data center) yields a terse 401/404 from Guzzle with no hint — confirm the suffix first before debugging anything else.
  • getLastError() / getLastResponse() are instance state. Read them immediately after each call; the next request overwrites them. Under Octane, treat the client as request-scoped, not a singleton.
  • PATCH vs PUT matters. PATCH merges partial fields; PUT is create-or-replace. A PUT that omits a populated merge_fields key can blank it — send the full contact body on upserts.
  • Emails are case-insensitive in the hash. Always strtolower(trim($email)) before md5(), or you'll create duplicate members.
  • Merge tags are strict uppercase. fname fails with 400 Invalid Resource; FNAME works. Error bodies include the offending field, so log the full response JSON.
  • DELETE archives, it doesn't erase. For GDPR/compliance removal use POST /lists/{id}/members/{hash}/actions/delete-permanent.
  • Unsubscribing is a PATCH of status, not a delete. Same for cleaned and transactional.
  • Pagination defaults bite. GET /lists/{id}/members caps at low page sizes; pass count (max 1000) and offset, or use fields/exclude_fields to shrink payloads.
  • 429 handling is your job. Wrap calls in a retry() with exponential backoff, or push bulk work into the queue/batch endpoint.
  • Config caching. After changing .env, run php artisan config:cache — a cached config silently ignores the edited key.
  • Version pinning. With ~99 stars and a narrow scope, pin the version in composer.json and consider wrapping the class in your own Newsletter service so swapping the transport later is a one-file change.
  • HTTP timeouts default to Guzzle's — set an explicit timeout if you call Mailchimp in any request-handling path, otherwise a slow Mailchimp response stalls your app.
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.
jinas/dhivatar
store-doc/laravel-storage-driver
plus-it/laravel-translation-manager
diephp/perhaps
diephp/telegraph
osmaviation/spreadsheet
microsoft/fluentui-system-icons
mmccook/php-json-canonicalization-scheme
middlewares/firewall
qubiqx/montapacking
nyoncode/laravel-package-console
pnscripts/filament-broadquill-lite
pnscripts/laravel-sentfold-lite
vleap/mx-sdk-laravel-internal
twidpay/utils
htetmyathlaing/scout-elasticsearch-driver
artisan-toolbox/front-awesome
ls/client-assistant
users-au/laravel-client
cgv/localized-resource