rezzza/mailchimp-bundle
Symfony bundle that wraps the MailChimp API, providing ready-to-use services and configuration for managing lists, subscribers, and campaigns from your application. Suitable for legacy Symfony projects; note the small user base and limited recent maintenance.
rezzza/mailchimp-bundle is a Symfony 2 bundle, not a Laravel package. There is no service provider, no facade, and no config/mailchimp.php convention you can publish. A Laravel developer should treat the repository as a reference implementation for wiring Mailchimp into a container, not as something to composer require into a Laravel app (its constraints on symfony/symfony ~2.3 and PHP 5.x will conflict with modern Laravel).
Minimal steps to evaluate and reuse it:
composer.json and identify the underlying Mailchimp client it depends on (older releases wrap the Mailchimp API v1.3/2.0 client; the bundle itself mostly provides DI configuration and a thin service).DependencyInjection/Configuration.php — this is where the supported config keys live (api_key, list IDs, default list, double opt-in flags, etc.). That tree is the actual "API surface" of the bundle.config/services.php, bind a singleton in a service provider, and inject it where needed.First practical use case — subscribe a user to a list:
// config/services.php
'mailchimp' => [
'key' => env('MAILCHIMP_API_KEY'), // e.g. abc123...-us10
'list' => env('MAILCHIMP_LIST_ID'),
],
// app/Providers/MailchimpServiceProvider.php
public function register(): void
{
$this->app->singleton(MailchimpClient::class, fn () => new MailchimpClient(
config('services.mailchimp.key'),
config('services.mailchimp.list'),
));
}
app(MailchimpClient::class)->subscribe(
email: $user->email,
mergeFields: ['FNAME' => $user->first_name, 'LNAME' => $user->last_name],
doubleOptIn: false,
);
If you are starting fresh, skip the bundle entirely and use mailchimp/mailchimp-marketing (API v3) or drewm/mailchimp-api. The bundle's value today is mostly in showing the shape of a clean abstraction: config in one place, a single injected service, list defaults.
Service-provider wrapping. Mirror the bundle's DI extension by registering a singleton that owns the HTTP client and the default list. Keep the Mailchimp client out of controllers.
Dedicated repository/service for list operations. Rather than sprinkling ->post() calls, expose intent methods:
class MailchimpSubscriber
{
public function __construct(private MailchimpClient $client) {}
public function subscribe(User $user): void { /* ... */ }
public function unsubscribe(string $email): void { /* ... */ }
public function updateMergeFields(string $email, array $fields): void { /* ... */ }
public function addTag(string $email, string $tag): void { /* ... */ }
}
Model events for synchronization. Hook created / updated on the model that represents the subscriber, but always push to a queue. Mailchimp calls are network I/O and will slow requests:
User::created(fn (User $user) => SyncToMailchimp::dispatch($user));
Queued jobs for bulk operations. Use batch endpoints (or chunked jobs) rather than a loop of individual requests. The bundle-era API used /batch-subscribe; in v3 you use the batch endpoint with operations. Either way, keep each job under a few hundred members.
Webhooks as the write-back path. The bundle-era setup assumed the app was the source of truth. Modern Mailchimp is bidirectional: add a POST /webhooks/mailchimp route, verify the secret, and reconcile unsubscribes/bounces back into your database. This is the cleanest way to keep local state in sync without polling.
Testing with a fake. Bind an interface (MailchimpGateway) to a fake in tests so no test hits the network:
$this->app->instance(MailchimpGateway::class, new FakeMailchimpGateway());
Config surface to copy from the bundle. The Symfony Configuration class is a good template: default list, API key, double opt-in boolean, merge field map. Reproduce those in config/services.php rather than hardcoding list IDs in services.
/3.0/lists/{list_id}/members/{subscriber_hash}).-us10, -eu1) determines the hostname (https://us10.api.mailchimp.com). Hardcoding https://api.mailchimp.com will fail for non-default data centers. Parse the suffix from the key at boot and use it in the client base URI.subscriber_hash is the MD5 of the lowercased email. Don't pass the raw email to path endpoints — lowercase and hash it, or you'll get 404s for mixed-case addresses.MemberNotExist) on unsubscribe — make these idempotent in your service methods.double_opt_in: true globally, remember that v3 requires status: 'pending' vs 'subscribed' per member. Be explicit rather than relying on a global default.fname or a tag that hasn't been defined, Mailchimp silently drops it (or errors depending on API version). Validate against a cached list of merge fields at deploy time.usleep between calls, a queue with limited workers, or the batch endpoint. The bundle gives no retry logic — add exponential backoff in your HTTP layer.config('services.mailchimp.webhook_secret'). Reject anything without it.Configuration tree (a clean config blueprint), the single-service abstraction (easy to swap clients), and the separation of "list operations" from transport. Everything else — the Symfony DI wiring, the event dispatcher integration — is not reusable in Laravel.How can I help you explore Laravel packages today?