hurracane/mailchimp-bundle
hurracane/mailchimp-bundle is a PHP bundle for integrating Mailchimp into your application, with configuration and service wiring for mailing list, subscriber, and API interactions. It helps reduce boilerplate when adding Mailchimp-powered email workflows to PHP projects.
Verify before you composer require. This is a Symfony bundle, not a Laravel package — so the DI wiring, config tree, and auto-registration that make up half its surface are dead code in a Laravel app. The realistic first step is to treat the repo as reference source, not a dependency:
git clone https://gitlab.com/akpranga/hurricane/mailchimp-bundle /tmp/mcb
cat /tmp/mcb/composer.json # note symfony/* constraints
find /tmp/mcb/src -type f # look for the client vs. the bundle glue
First place to look: locate the actual API client class. If it accepts an API key in its constructor and uses Guzzle or a PSR-18 client with no ContainerAwareInterface, ServiceSubscriberInterface, or Symfony\Component\Config\Definition\* imports, it's portable — copy those files into app/Services/Mailchimp/ (keep the license header). If the client is entangled with Symfony services, stop and use mailchimp/marketing or drewm/mailchimp-api instead.
First use case — capture a signup into an audience:
// app/Services/Mailchimp/MailchimpClient.php (harvested or wrapped)
$client->subscribe(
listId: config('services.mailchimp.list_id'),
email: $request->email,
status: 'pending', // double opt-in; do NOT default to 'subscribed'
);
Wiring it into Laravel:
config/services.php → 'mailchimp' => ['key' => env('MAILCHIMP_API_KEY'), 'list_id' => env('MAILCHIMP_LIST_ID')].AppServiceProvider: $this->app->bind(MailchimpClient::class, fn () => new ...).-us21) — don't add a third env var someone will forget.SyncNewsletterSubscriber) rather than calling the API inline in the controller.If the integration is worth more than a sprint, skip the harvest and go straight to the official SDK behind your own MailchimpClient interface — same amount of wiring, none of the abandonment risk.
Facade over the extracted client so call sites stay readable and swappable:
class Mailchimp extends Facade
{
protected static function getFacadeAccessor(): string { return 'mailchimp'; }
}
// Usage: Mailchimp::subscribe(...)
Queue every write. The upstream code is synchronous and rate-limited to 10 concurrent connections; a batch backfill on a request thread will hit 429 and poison the response.
SyncSubscriber::dispatch($user)->onQueue('marketing');
// Worker: --queue=marketing with a low --tries and exponential backoff
Upsert members idempotently. Mailchimp's PUT /lists/{id}/members/{subscriber_hash} is an upsert — use it instead of POST + existence check. The subscriber hash is md5(strtolower($email)), and getting this wrong produces duplicate contacts rather than errors.
Backfills go through /batches, not loops. Chunk 500 operations per batch, then poll GET /batches/{id} until status is finished, and parse each operation's response for per-record failures — the batch itself returns 200 even when every member in it failed.
Tag on product behavior as a separate job from the subscribe, so a Mailchimp outage during signup doesn't block a user flow that never depended on it:
Mailchimp::tagMember($email, ['trial-started', 'plan-pro']);
Test doubles via the interface — a FakeMailchimpClient bound in TestCase::setUp() lets the whole suite run without network. This is the single biggest reason to write your own interface even if you harvest the upstream client unchanged.
Consent mirroring: map your internal consent state to pending / subscribed / unsubscribed explicitly — don't let Mailchimp's default subscribed status imply consent you don't have records for.
DependencyInjection\Extension, no Configuration tree, no auto-registration. Anything you copy is just plain classes — treat it that way.symfony/http-kernel: ^3.4 and friends; Laravel 10/11 pulls symfony/console: ^6|^7. composer require will fail resolution. Extract the files rather than fighting the resolver, and never ship with --ignore-platform-reqs as the workaround.vcs repository entry pinned to a commit ref — a routine composer update on an unrelated package can pull a newer Symfony component and break this dependency's satisfiability, blocking deploys. Vendor the extracted files instead.-usX suffix must match the base URL. Derive it, log it once at boot, and return a clear app-side error rather than surfacing Mailchimp's response verbatim.429 includes X-Mailchimp-API-Error-Code. Honor Retry-After, add jitter, and don't retry 4xx other than 429.200 doesn't mean success. Batch responses carry per-record errors; PUT upserts can return member-level errors in the body. Always inspect the parsed body, not just the status code.cleaned events are spoofable.config:cache output. Use env-only injection and redact the key in exception handlers.How can I help you explore Laravel packages today?