stechstudio/laravel-postmaster
Laravel Postmaster gives your Laravel app a clean, centralized way to compose and send transactional email, with a fluent API that keeps mail logic out of controllers and supports queued delivery.
laravel-postmaster sits between your app and a transactional email provider's HTTP API, so day-to-day you keep writing normal Laravel mail (Mail::to(...)->send(...)) and only touch the package when configuring transports or consuming delivery events.
Minimal path from zero:
composer require stechstudio/laravel-postmasterphp artisan vendor:publish --tag=postmaster-config (also check for a migrations tag if you plan to persist delivery events)..env — one block per provider, e.g. POSTSHIBA_KEY=... — and keep secrets out of config/*.php.config/mail.php:'mailers' => [
'postshiba' => [
'transport' => 'postshiba',
],
],
MAIL_MAILER=postshiba for the environment you're testing and send a real message through a queued mailable.Where to look first: the published config/postmaster.php (the authoritative list of supported providers and their credential keys), then the driver classes under src/ — each provider gets its own class, so the PostShiba driver's options are the best documentation for what that provider actually supports. After that, the webhook/event handling entry point (route registration + event classes), which is where most of the package's real value lives.
First use case worth shipping: replace an SMTP mailer with the API driver in staging, confirm deliverability and headers, then wire bounce/complaint events into your existing suppression logic before you flip production.
Keep application code provider-agnostic. Everything downstream of Mail:: should be unaware of PostShiba. Configure the driver per environment and let the mailer name be the only switch. This is what makes adding v2.32.0's provider a config change rather than a refactor.
Regional or failover routing via multiple mailers. The practical reason to add another provider is usually geography, cost, or redundancy. Define two mailers (postshiba, postmark) and select at runtime:
Mail::mailer($user->isInJapan() ? 'postshiba' : 'postmark')
->to($user)
->send(new Receipt($order));
Better still, wrap that decision in a small MailRouter service or a Mailable-level ->mailer() call so tests can assert on the choice rather than the transport.
Always queue. API transports turn a send into an outbound HTTP round-trip; a slow provider becomes slow page loads. implements ShouldQueue on mailables (or Mail::queue) plus a dedicated queue worker keeps request latency flat and gives you retries via failed() / retryUntil().
Consume provider events, don't poll. The highest-leverage integration is the webhook side: register the package's route, verify signatures, and translate provider events into domain state (mark address invalid on hard bounce, unsubscribe on complaint). Map provider-specific event names in one place so a second provider's payload shape doesn't leak into your models.
Test at the seam. For most feature tests, Mail::fake() is still correct and fast — it asserts intent without exercising the transport. Reserve real provider calls for a thin integration test suite against a sandbox key, and keep them out of CI's default run so a provider outage doesn't fail unrelated PRs.
Extension point. If you need behavior the bundled driver doesn't expose (custom headers, provider-specific tags), don't fork the package — register a subclass and rebind the transport in a service provider. Keep customizations in your own namespace so upstream releases merge cleanly.
php artisan config:cache after adding credentials. Config caching is the most common cause of "the driver isn't found" or "credentials are null" in staging while php artisan tinker works fine locally.'transport' => 'postshiba' (not PostShiba, not post-shiba). A mismatch surfaces as a InvalidArgumentException: Unsupported mail transport from Laravel's transport manager, not from this package — check config/mail.php before filing an issue.ShouldBeUnique jobs or a dedupe key on (provider, event_id), or you'll double-count bounces and double-unsubscribe.RateLimited job middleware.debug level in a dedicated channel. Once mail leaves the API, "it didn't arrive" is undebuggable without the provider's message ID — store it on your outbound email record so support requests and webhook events can be joined.composer.json (^2.32) and read the changelog between minor bumps; this package's changelog is provider-driven and each addition is a new surface area for config drift.How can I help you explore Laravel packages today?