dinubo/laravel-mailer

Schedule and send segmented Laravel newsletters with per-recipient placeholders, open/click tracking, unsubscribe handling, provider webhooks (Postmark, Resend), and a built-in admin UI.

Maintainers

Package info

github.com/dinubo/laravel-mailer

pkg:composer/dinubo/laravel-mailer

Statistics

Installs: 1

Dependents: 0

Suggesters: 0

Stars: 0

Open Issues: 0

v0.1.0 2026-06-10 11:24 UTC

This package is auto-updated.

Last update: 2026-06-10 13:44:40 UTC


README

Latest Version on Packagist Tests Total Downloads

A Laravel newsletter / mailer package: schedule and send segmented newsletters, resolve per-recipient placeholders, track opens and clicks, handle unsubscribes, and process provider webhooks (Postmark, Resend) — with a built-in admin UI.

Installation

Via Composer:

composer require dinubo/laravel-mailer

The service provider and the package's migrations are auto-discovered. Run them:

php artisan migrate

Publish whatever you want to customise:

php artisan vendor:publish --tag=mailer.config      # config/mailer.php
php artisan vendor:publish --tag=mailer.views       # admin Blade views
php artisan vendor:publish --tag=mailer.migrations  # only if you need to edit the schema

Usage

Routes

All routes are mounted under the /mailer prefix and named with a mailer. prefix. The admin UI lives at /mailer/newsletters. Middleware is configurable per group (user, admin, callback) in config/mailer.php.

Group Examples
user mailer.open, mailer.click, mailer.unsubscribe
admin mailer.newsletters.*, mailer.newsletters.statistics.*
callback mailer.callback.postmark, mailer.callback.resend

The callback (webhook) routes verify the provider signature when the matching secret is configured (see Webhooks); the mailer.middleware.callback group is available for any additional middleware.

Recipients

By default newsletters are sent to your application's configured user provider model (config('auth.providers.users.model')). Override it in config/mailer.php via recipient_model.

Placeholders, filters & segments (registrar)

Placeholders and filters use closures, so they are registered on the Mailer class rather than stored in config (this keeps config cacheable). Register them from a service provider's boot() method:

use Dinubo\Mailer\Mailer;
use Dinubo\Mailer\Placeholder;
use Dinubo\Mailer\Filter;
use Dinubo\Mailer\Segment;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;

// Available as {{name}} in a newsletter subject/body (plain substitution, not Blade).
Mailer::placeholders([
    Placeholder::make('name', fn (Model $recipient) => $recipient->name),
    Placeholder::make('site', 'Acme Inc.'),
]);

// Global recipient filter, applied to every send.
Mailer::filter(Filter::make(
    query: fn (Builder $query, $newsletter) => $query->whereNotNull('email_verified_at'),
));

// Segments power the admin dropdown. A built-in "all" (All Users, no filter) is
// always available; register more, each with an optional filter:
Mailer::segments([
    Segment::make('verified', 'Verified users')->filter(Filter::make(
        query: fn (Builder $query, $newsletter) => $query->whereNotNull('email_verified_at'),
    )),
]);

A Filter may define a query: closure (constrains the recipient query) and/or a collection: closure (post-filters the resulting collection).

Events & actions

Trigger event-scheduled newsletters from your app, and compute extra per-recipient placeholders at send time:

use Dinubo\Mailer\Mailer;
use Dinubo\Mailer\Event;
use Dinubo\Mailer\Placeholder;
use Illuminate\Database\Eloquent\Model;

Mailer::events([
    Event::make('signup', 'On signup')
        ->placeholders([Placeholder::make('plan', fn (Model $user) => $user->plan)]),
]);

// Elsewhere in your app — schedules any active newsletters bound to this event:
Mailer::event('signup', $user);

Actions work similarly (Action::make(...)->execute(...), registered via Mailer::actions([...])) for placeholders that run logic per recipient.

Admin access

The admin UI (/mailer/newsletters) is gated by the admin middleware group, which by default only allows the local environment. Authorize real users with Mailer::auth():

use Dinubo\Mailer\Mailer;
use Illuminate\Http\Request;

Mailer::auth(fn (Request $request) => (bool) $request->user()?->is_admin);

Webhooks

Bounce/complaint webhooks from Postmark and Resend update delivery state. Set the matching secret to enable signature verification (without it, requests are accepted and a warning is logged):

# Sent by Postmark as HTTP Basic Auth — set the webhook URL to
# https://user:SECRET@your-app.test/mailer/callback/postmark
MAILER_POSTMARK_WEBHOOK_SECRET=...

# Resend (Svix) signing secret
MAILER_RESEND_WEBHOOK_SECRET=whsec_...

Sending

Newsletters scheduled in the admin UI are dispatched by the scheduler command. Add it to your console schedule:

$schedule->command('mailer:newsletters')->everyFifteenMinutes();

Change log

Please see the changelog for more information on what has changed recently.

Testing

The package uses orchestra/testbench:

composer install
vendor/bin/phpunit

Contributing

Please see CONTRIBUTING.md for details.

Security

If you discover any security related issues, please email petros@dinubo.com instead of using the issue tracker.

Credits

License

MIT. Please see the license file for more information.