a2zwebltd/laravel-newsletter
A Laravel package for managing newsletter subscriptions and mailing lists. It provides tools to subscribe, unsubscribe, and organize subscribers, integrating with your existing user model and database to simplify email list handling in Laravel applications.
This package adds RFC 8058 one-click unsubscribe to a Laravel app: it registers a POST route named unsubscribe.one-click, wires the List-Unsubscribe / List-Unsubscribe-Post headers onto your mailables, and handles the tokenised request that Gmail and Yahoo fire when a recipient hits "Unsubscribe" in the inbox UI.
Minimum path to a working setup:
^1.3.1 — earlier versions return 419 to mail clients (see Gotchas).config/unsubscribe.php) and read it before touching anything else. The only knob most apps need is routes.enabled, which controls whether the package auto-registers the endpoint.List-Unsubscribe-Post: List-Unsubscribe=One-Click is what makes the POST route get used instead of a browser link.First use case: transactional/marketing mail sent through a single base mailable class. Wire it once there and every subclass inherits compliance.
Base mailable trait. Put header injection and token generation in a HasOneClickUnsubscribe trait applied to your base mailable, rather than per-mailable. Tokens should be signed (or hashed and stored) so the route can resolve the subscriber without an authenticated session — the mail client sends no cookies, no session, no CSRF token.
Keep the package route; don't re-declare it. routes.enabled => true is the low-maintenance default. The package owns the CSRF exclusion for you, and it now covers PreventRequestForgery as well as the two legacy middleware names.
Re-declaring the route (routes.enabled => false). Use this when you need custom resolution logic (multi-tenant, per-list preferences, soft-delete semantics). Mirror the package's middleware exclusions exactly:
Route::post('unsubscribe/one-click/{token}', OneClickController::class)
->name('unsubscribe.one-click')
->withoutMiddleware(array_filter([
\Illuminate\Foundation\Http\Middleware\PreventRequestForgery::class, // Laravel 13
\Illuminate\Foundation\Http\Middleware\ValidateCsrfToken::class, // Laravel 11/12
\App\Http\Middleware\VerifyCsrfToken::class,
], fn ($m) => class_exists($m)));
The package ships an agent skill documenting this form — check it before hand-rolling the exclusion list on a Laravel 13 upgrade.
Idempotency and speed. Mail providers retry. Return 200 for "already unsubscribed" and "token unknown but well-formed" alike; only return 4xx for genuinely malformed tokens. Do the actual work (DB write, list sync to your ESP) in a queued job so the endpoint responds inside the provider's timeout.
Feature tests. Laravel's test suite disables CSRF middleware by default, so a passing test proves nothing about the 419 class of bugs. Add one test that asserts the exclusion explicitly — either by asserting the route's gatherMiddleware() no longer contains the CSRF class, or by hitting the route with the middleware stack enabled.
Compliance surface. Pair one-click with a human-facing GET preference-centre page and honour the unsubscribe immediately for marketing mail (transactional exemptions are narrow and jurisdiction-specific).
419 to mail clients is the signature failure of this package. It means the request reached your app and was rejected by CSRF, not that the token was bad. Log the response status of the endpoint in production; a 419 spike is invisible to users and silently non-compliant.web group's CSRF middleware to PreventRequestForgery, with ValidateCsrfToken/VerifyCsrfToken retained as subclasses. Excluding the subclass does not remove the parent, which is why v1.3.1 adds the new class name. If you re-declare the route and are on Laravel 13, you must exclude PreventRequestForgery too.web-group additions (rate limiting, AuthenticateSession, custom CSRF variants) is worthwhile: any middleware added to web after the package's exclusion list was written becomes a potential 419.auth or verified. Mail clients have no session. The signed token is the credential; make it single-purpose and expiring if your threat model requires it.List-Unsubscribe URL (it must be https, publicly reachable, and stable across sends).1.3.0 or earlier and run Laravel 13, treat this as a required patch, not optional — the endpoint is effectively broken until you upgrade or manually exclude the new middleware class.How can I help you explore Laravel packages today?