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.
Architecture fit
The package is a thin, driver-based facade over third-party newsletter APIs. Since v5.0.0 it exposes a single Newsletter facade (Spatie\Newsletter\Facades\Newsletter) with a pluggable driver selected in config/newsletter.php, and one extra SDK package per provider:
| Driver | Extra dependency | Identifier in config |
|---|---|---|
| Mailcoach (default) | spatie/mailcoach-sdk-php |
Email list UUID |
| MailChimp | drewm/mailchimp-api |
List ID |
| MailerLite | mailerlite/mailerlite-php |
Group ID |
This is a "convenience adapter," not an abstraction layer. It fits well when a Laravel app needs fire-and-forget list membership operations (subscribe, unsubscribe, tag, membership check) from a handful of call sites. It does not fit if we expect it to normalize provider semantics, queue work, or persist subscription state.
Integration feasibility
Low-friction. composer require, php artisan vendor:publish --tag="newsletter-config", four env vars (NEWSLETTER_DRIVER, NEWSLETTER_API_KEY, NEWSLETTER_ENDPOINT, NEWSLETTER_LIST_ID), done. The log and null drivers let us wire the integration in CI and local environments without touching a provider — a genuinely valuable property for test stability.
Technical risk
getMember() returns a Mailcoach Subscriber resource but a plain array for MailChimp; attribute payload keys differ per driver (FNAME/LNAME vs first_name/last_name vs name/last_name). Any calling code that reads subscriber data or passes merge fields is implicitly driver-coupled. Provider portability is configuration-only on paper, not in practice.false rather than throwing (getMember, unsubscribe per changelog 4.2.1). Diagnostics are MailChimp-specific (Newsletter::getApi()->getLastError()). Without our own error wrapping, a dropped unsubscribe can pass unnoticed — a compliance exposure, not just a bug.drewm/mailchimp-api is a low-activity third-party library and warrants an explicit monitoring decision.Last release: 2026-10-19 is future-dated, and Dependents: 0 contradicts a 1.6k-star, heavily-downloaded package; both suggest metadata ingestion issues. Re-check via Packagist before pinning.Key questions
subscribeOrUpdate vs subscribe the right default given double opt-in (subscribePending) and marketing-permissions requirements?Stack fit
Native Laravel fit: service provider, publishable config, facade, Pest test suite, PHPStan in CI, MIT license. Assumes a Laravel app on a supported PHP 8.x line. Multi-list is handled declaratively in config (lists map + default_list_name), so no code changes to add a list.
Migration path
subscribe, subscribeOrUpdate, subscribePending, unsubscribe, delete, getMember, hasMember, isSubscribed, tag methods) covers the common CRUD surface. Newsletter::getApi() is the escape hatch for anything the facade omits, which allows a staged migration rather than a big bang — wrap remaining direct SDK calls behind our own interface and fold them in incrementally.endpoint juggling) and defer provider-agnosticism work until a switch is actually plausible.SubscriptionGateway interface and implement it with a thin wrapper over Newsletter that owns the driver-specific quirks (attribute key mapping, getMember return normalization, error translation). This is the single highest-leverage design decision and costs roughly a day.Compatibility
Pin the major (^5.x) and read the changelog at each bump — v5.0.0 was a breaking driver-based restructure, and v4→v5 changed the config key from laravel-newsletter to newsletter. Confirm the Laravel/PHP constraint matrix in composer.json against our application version rather than relying on the README, and confirm the per-driver SDK version compatible with our pinned package version. Set NEWSLETTER_DRIVER=null in testing and CI.
Sequencing
null/log drivers, validate the four config values and one subscribe/unsubscribe round-trip against a provider sandbox list.SubscriptionGateway; implement the wrapper; normalize attribute keys and return types; centralize error translation and logging.Maintenance
Low surface, low churn: the package itself changes rarely and the changelog is clean and honest. The recurring cost is not the package — it is the per-driver SDK lifecycle (three SDKs tracking three external API versions) and the MailChimp API v3 / MailerLite version transitions behind them. Budget periodic dependency-review time, not feature-development time.
Support
MIT, community support via GitHub issues, security reports to [email protected]. No SLA, no indemnity, no commercial escalation path unless we buy into Mailcoach, which would incidentally give us a vendor relationship for a portion of the stack. For any customer-facing consent or unsubscribe flow, internal ownership is mandatory regardless of upstream responsiveness.
Scaling
The facade performs one synchronous HTTP call per operation. There is no batching and no caching of membership checks, so isSubscribed/hasMember called in a loop is an N-call amplification against provider rate limits (Mailchimp's concurrency and request-per-second ceilings are the usual bottleneck). Consequences:
Failure modes
| Failure | Detection today | Consequence |
|---|---|---|
| Provider 5xx / timeout | false return or exception from SDK |
Request latency spike; subscription silently lost if unqueued |
| Bad/rotated credentials | getLastError() (MailChimp only) |
All writes fail until someone notices |
| Provider-side unsubscribe | None — no webhook | App and provider state diverge; user re-subscribed against their wish |
| Partial multi-provider/multi-list write | None — no transaction | Half-onboarded subscriber |
| Dropped unsubscribe | false return |
Compliance/consent exposure |
| Dependency drift | Composer audit only | SDK incompatible with provider API version |
Mitigations to build, none of which the package provides: queued writes with retry and dead-letter visibility, a structured error log keyed by email + operation + driver, a periodic reconciliation job comparing app state against getMember, and an alert on failed-job rate.
Ramp-up
A competent Laravel developer is productive with the facade in under an hour — the README is accurate and the API is small. Realistic effort estimate: ~0.5 dev-day spike; ~3–5 dev-days to production-grade (adapter interface, queued jobs, retries, error translation, observability, tests); plus an ongoing ~1–2 days per year of dependency and provider-API upkeep. Day-one code is trivial; the operational rigor around a one-way, silently-failing, unqueued integration is the actual project.
How can I help you explore Laravel packages today?