bashy/laravel-campaignmonitor
Install the wrapper and let Composer resolve the underlying Campaign Monitor SDK (createsend-php, which this package sits on top of):
composer require bashy/laravel-campaignmonitor
The package is a small wrapper, so there are only three moving parts to wire up:
Config – publish the package config (typically php artisan vendor:publish --provider="Bashy\CampaignMonitor\CampaignMonitorServiceProvider" or the tag the package documents) and add credentials to .env:
CAMPAIGNMONITOR_API_KEY=your_api_key
CAMPAIGNMONITOR_CLIENT_ID=your_client_id
Both values come from Account settings → API keys in Campaign Monitor. The API key is account-wide; the client ID identifies which client (brand/tenant) inside that account you're operating on.
Service provider / facade – older versions may require manually registering the provider in config/app.php; newer ones use auto-discovery. Confirm which applies to your Laravel version (see Gotchas).
First use case – the most common day-one task is subscribing a user to a list:
use Bashy\CampaignMonitor\Facades\CampaignMonitor;
CampaignMonitor::subscribe(
config('services.campaignmonitor.list_id'),
$request->email,
$request->name
);
Where to look first: read the package's README and then the config/campaignmonitor.php file — it tells you exactly which keys the service provider expects. If the README is thin (likely, given the low maintenance signals), read src/CampaignMonitor.php directly; it will be a thin proxy over the createsend client, and every public method maps to a Campaign Monitor API endpoint.
Prefer constructor injection over the facade in services and jobs; the facade is convenient in controllers and Artisan commands but harder to mock in unit tests.
Model observer for opt-in sync. Newsletter subscription is usually a column on users, so hook the model rather than scattering calls:
class UserObserver
{
public function updated(User $user): void
{
if ($user->wasChanged('newsletter_opt_in') && $user->newsletter_opt_in) {
SubscribeToCampaignMonitor::dispatch($user);
}
}
}
Queue the calls. Every wrapper method is a network round-trip to Campaign Monitor; a slow/unavailable API should never block a web request. Dispatch a job with $tries = 3 and backoff() and catch the SDK's CurlException/Exception inside the job so retries are deliberate.
Multi-tenant / multi-list setups. The wrapper is effectively a singleton keyed to one API key, but you can still route per list by storing list_id per tenant (e.g., in a settings table or config/lists.php) and passing it into each call. If you need different API keys per tenant, you'll end up resolving distinct CreateSend clients yourself — at which point the wrapper adds little and the raw SDK is a cleaner fit.
Custom fields are a mapping problem. Campaign Monitor rejects unknown custom fields, so maintain a campaignmonitor_fields map (['first_name' => 'FirstName']) in config rather than hard-coding keys at call sites. Define the fields in the Campaign Monitor UI first; the API cannot create field definitions.
Consent and double opt-in. Most subscribe calls accept an $optIn/resubscribe flag. Pass true (or the wrapper's equivalent) so Campaign Monitor sends its own confirmation email; this keeps you compliant and off the "unconfirmed" list.
Testing. Bind a fake implementing the package's contract (or Mockery::mock(CampaignMonitor::class)) in the container so observers/jobs don't hit the network. If the package lacks a contract, refactor the calls behind your own NewsletterService interface — worth doing on day one since this package is unlikely to stay in lockstep with Laravel/SDK versions.
composer.json for the illuminate/support constraint and verify it supports your version; if not, either fork it or drop the wrapper and use campaignmonitor/createsend-php directly — the wrapper is thin enough that the migration is a few hours.Bashy\CampaignMonitor\CampaignMonitorServiceProvider and alias the facade manually in config/app.php. Otherwise you'll get "target class does not exist" on the facade.false. The underlying SDK throws CreateSend_Exception (or similar) on non-2xx responses. Wrap subscriber syncs in try/catch and log the API message — otherwise a duplicate-email error looks like a silent no-op.resubscribe flag; set it explicitly rather than relying on the default..env/config, not in code, and cache any "get list" lookups — there is no reason to fetch list metadata on every request.CreateSend client; override that binding in a service provider to inject a mocked client in tests or to customize the HTTP client, rather than subclassing the wrapper.*.createsend.com vs. *.createsend.com.au); if your config exposes a base URL, set it correctly or all calls fail with DNS/SSL errors.How can I help you explore Laravel packages today?