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

Laravel Newsletter Laravel Package

spatie/laravel-newsletter

spatie/laravel-newsletter makes it easy to manage email list subscriptions in Laravel. Integrate Mailcoach, MailChimp or MailerLite with a simple API to subscribe, unsubscribe and update contacts.

View on GitHub
Deep Wiki
Context7

Getting Started

Install and wire it up (about five minutes):

composer require spatie/laravel-newsletter
php artisan vendor:publish --tag="newsletter-config"

Then pick a driver and install its SDK — the package itself ships no HTTP client for any provider:

Provider Extra package Driver class
Mailcoach spatie/mailcoach-sdk-php Spatie\Newsletter\Drivers\MailcoachDriver::class
MailChimp drewm/mailchimp-api Spatie\Newsletter\Drivers\MailChimpDriver::class
MailerLite mailerlite/mailerlite-php Spatie\Newsletter\Drivers\MailerLiteDriver::class

Set NEWSLETTER_DRIVER, NEWSLETTER_API_KEY, and NEWSLETTER_LIST_ID in .env. The default driver in the published config is Mailcoach, so if you're on MailChimp you must change it explicitly — this is the single most common setup mistake.

Where to look first: config/newsletter.php. The two keys that matter are driver and lists. The lists array is your own naming layer — the key (subscribers, customers, beta-testers) is the string you pass to listName: at call sites, and the id inside it is the provider's identifier (Mailcoach list UUID, MailChimp list id, MailerLite group id). Everything else is a thin passthrough.

First use case — a footer signup form. A controller action is the canonical shape:

use Spatie\Newsletter\Facades\Newsletter;

public function store(SubscribeRequest $request)
{
    Newsletter::subscribe($request->validated('email'));

    return back()->with('status', 'Check your inbox to confirm.');
}

The Newsletter facade alias is auto-registered, so use Newsletter; also works — but prefer the FQCN for greppability. Verify the first call returns something truthy before you ship; every method returns a boolean or a resource/false, and none of them throw on a rejected API call by default.

Implementation Patterns

Treat subscribeOrUpdate as your default write. Plain subscribe on an already-subscribed address may return false depending on the provider, which makes idempotent flows (registration, profile save, checkout) brittle. Use:

Newsletter::subscribeOrUpdate($user->email, [
    'first_name' => $user->first_name,
    'last_name'  => $user->last_name,
], 'subscribers');

This is the natural pairing with a User observer or a Registered event listener — one call handles first-time signup and later attribute refreshes.

Keep list names in config, not in code. Rather than sprinkling 'subscribers' through controllers, bind it once:

// config/newsletter.php
'default_list_name' => 'subscribers',

and only pass listName: when you deviate (e.g. Newsletter::subscribe($email, listName: 'vip')). Named arguments matter here — the second positional parameter is attributes, not the list, so Newsletter::subscribe($email, 'vip') silently tries to send a string as merge fields.

Double opt-in. For MailChimp, subscribePending() gives you the pending/unconfirmed state instead of forcing someone onto the list. Pair it with a distinct success message so users aren't told they're subscribed when they aren't.

Reads before writes for UX. hasMember() and isSubscribed() return booleans and are cheap enough to gate UI (a "you're already on the list" state vs. a form). getMember() returns false when nobody matches — for Mailcoach it's a Subscriber resource object, for MailChimp a plain array. If you need to branch on the payload, keep the provider's shape in mind or normalize behind your own small service class rather than letting provider arrays leak into views.

Unsubscribe vs. delete. Default to unsubscribe. delete wipes the member's history (add/opt-in/edit records) and is intended for GDPR-style erasure requests, not for "user clicked unsubscribe." Wire unsubscribe to a signed route from your email footer; reserve delete for an explicit account-deletion flow.

Queue the network call. Every facade method is a synchronous HTTP round-trip to a third party. In a registration flow that's latency you're imposing on the user and an availability risk you don't control. Wrap calls in a job:

dispatch(new SyncNewsletterSubscription($user))->afterCommit();

afterCommit() avoids subscribing someone whose registration transaction later rolls back.

Escape hatch. When the wrapper doesn't cover something — MailChimp tags, interests, activity, marketing permissions — Newsletter::getApi() hands you the underlying SDK client. Reach for it rather than forking the package, and wrap it in your own method so the vendor assumption is isolated to one file.

Tags (MailChimp-oriented). The 4.4.0 release added tag management methods. These are driver-dependent and their signatures aren't documented in the README — read the driver source in src/Drivers/ before relying on them in production.

Gotchas and Tips

Attribute keys are provider-specific. The same conceptual field has three different names:

  • Mailcoach: ['first_name' => 'Rince']
  • MailChimp: ['FNAME' => 'Rince'] (merge tags, uppercased)
  • MailerLite: ['name' => 'Rince']

If you're migrating providers, every call site breaks. Normalize the mapping in one place (a NewsletterAttributes value object or a config map) so a driver swap is a one-file change.

endpoint must be null for MailChimp and MailerLite. Leaving a stale value in NEWSLETTER_ENDPOINT produces confusing connection errors that look like auth failures. Only Mailcoach needs it.

MailChimp interests need IDs, not names. The fourth argument to subscribeOrUpdate takes ['interests' => ['abc123' => true]] — real interest IDs from the list's group settings. Names will be silently ignored, and you'll spend an hour wondering why segmentation isn't working.

Failures return false, they don't throw. Every write is a boolean you are expected to check. If you don't, failed subscriptions are invisible. Log the failures at minimum; better, use the return value to show the user an error. For MailChimp specifically, Newsletter::getApi()->getLastError() gives you the reason — this is the fastest debugging lever and it's not available on other drivers.

lastActionSucceeded semantics. The package tracks whether the previous call worked, and isSubscribed was patched (4.8.2) to respect it. Don't interleave two facade calls and then check a stale result — check the boolean the call itself returned.

Use log or null drivers in local and CI. Never point a test suite at a live list. The log driver writes the call out instead of performing it; the null driver discards it (and gained method-chaining support in 5.2.1 so it stays a drop-in). Set NEWSLETTER_DRIVER=null in phpunit.xml and in your .env.example for local, and you get a working app without any API credentials.

Cache config in production. The driver class name lives in config/newsletter.php. If you've been editing .env without php artisan config:cache / config:clear, you'll be debugging a config that isn't the config being loaded.

MailerLite's unsubscribe overloads its second argument. Passing a list name removes the subscriber from that group only; omitting it marks them unsubscribed account-wide. That's a materially different outcome — be explicit at every call site rather than relying on the default.

Description vs. reality. The package description advertises Brevo, but the README and changelog document only Mailcoach, MailChimp, and MailerLite. Don't plan a Brevo integration on the strength of the tagline — verify in src/Drivers/ first. Similarly, older methods like updateEmail, getMembers, and getMemberActivity come from pre-5.0 driver-based rewrites and are not uniformly supported; check the driver you're actually using.

No webhooks, no batching, no retries. This package is a thin command surface. If you need to react to unsubscribes happening on the provider side, or to sync thousands of members, you're building that yourself — the package won't help. For bulk operations, loop with rate-limit awareness (MailChimp and MailerLite both throttle) and give the job a generous timeout.

List IDs are UUIDs on Mailcoach and numeric/short strings elsewhere. Copying a Mailcoach UUID into a MailChimp config (or the reverse) yields a 404 that reads like a permissions error. Keep provider-specific .env files separate per environment.

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.
labyrinthe/connect-php
encore2/laravel-admin2
cboxdk/laravel-console-kit
shapecode/doctrine-dbal-datetimeutc
gilads-otiannoh254/inertia-protocol
dakiquang/laravel-oauth-refresh-lock
woda/laravel-worktrees
dalefranks/ramasite
faghy/laracashbook
roundly-consulting/google-places-for-laravel
roundly-consulting/kubernetes-api-for-laravel
roundly-consulting/plausible-for-laravel
gvp/gvp-laravel-template
metasyncsite/laravel-metasync-client
roundly-consulting/onboarding-for-laravel
adachsoft/cli-process-tester
adachsoft/changelog-linter
adachsoft/collection
adachsoft/php-code-style
adachsoft/api-version-bundle