creagia/laravel-web-mailer
Preview and debug outgoing Laravel emails in a built-in web inbox. Laravel Web Mailer captures messages sent by your app so you can inspect recipients, headers, HTML/text content, and attachments without external mail tools.
This package adds a web mail transport to Laravel so Mail/Notification messages are delivered by HTTP POST to an endpoint you control (or a provider that accepts mail over HTTP) instead of SMTP. It is the pragmatic choice when outbound port 25/465/587 is blocked — Lambda/Vapor, Cloud Run, some container platforms — or when mail is relayed through an internal API gateway or a Cloudflare Worker.
Minimal setup:
composer require creagia/laravel-web-mailer:^2.0 — 2.0.0 requires Laravel 11–13 and PHP 8.2+ (8.5 supported). Laravel 9/10 users must stay on 1.0.x.config/mail.php alongside your existing ones:'mailers' => [
'web' => [
'transport' => 'web',
// endpoint + auth keys — confirm exact names against the package config
],
],
MAIL_MAILER=web and the endpoint/credentials in .env, then php artisan config:clear.Mail::raw('hello', fn ($m) => $m->to('[email protected]')->subject('Test'));First real use case: transactional mail (password resets, receipts) from a serverless deployment. Keep your existing SMTP/mailgun/log mailers configured — this package is a mailer, not a replacement for the mail system, so it can be adopted per-mailer rather than globally.
In 2.0 the transport registration was simplified, so the old Mail::extend('web', ...) call in a service provider is no longer part of the setup. Find the transport name in the package's service provider before wiring config.
Named mailers, not a global swap. Define mailers.web, mailers.smtp, mailers.log, and select per-send with Mail::mailer('web')->send(...), ->mailer('web') on a Mailable, or $mailable->mailer = 'web'. This lets you route bulk mail through SMTP and transactional mail through the HTTP relay, and keeps local dev on log.
Keep Mailables transport-agnostic. Nothing about a Mailable or Notification changes — that is the main appeal. Existing ShouldQueue mailables start using the transport by switching the mailer, with no rewrite.
Queue everything. The transport makes a synchronous HTTP call inside the mail send; a slow endpoint directly delays the request. Implement ShouldQueue and set $tries/$backoff on the mailable or use the queue connection's retry settings.
Use the failover mailer for resilience:
'failover' => [
'transport' => 'failover',
'mailers' => ['web', 'smtp'],
],
This is often the best production configuration: HTTP first, SMTP as fallback (or the reverse), with retries handled by the queue.
Serverless integration. On Vapor/Lambda the HTTP transport avoids VPC/SMTP egress entirely. Point the endpoint at your relay, keep the payload small, and push sending to SQS via the queue so a slow relay does not eat into the Lambda timeout.
Multiple endpoints. Use two mailers (e.g. web_transactional, web_bulk) backed by separate config entries when different relays or credentials are needed per flow.
Testing. Combine Mail::fake() (assert the mailable was queued/sent) with Http::fake() (assert the POST actually goes out when you exercise the transport directly). Assert on the outbound payload — recipients, subject, and that the HTML body is present — since that payload is your integration contract with the endpoint.
Local development. Keep MAIL_MAILER=log (or Mailpit) locally and only enable web in staging/production; otherwise you need a reachable relay just to render a password reset.
composer.json constraints resolve — a locked laravel/framework on 10.x will hard-fail the install.Mail::extend('web', ...) or registered a custom Transport in a service provider, delete it. Registering twice can shadow the package transport or throw on duplicate resolution, and the failure looks like an unrelated mail error.config:cache bites. Changing .env values for the endpoint/token has no effect until you re-run config:cache. This is the single most common "the package stopped working" cause on deployed environments.MessageSending/MessageSent/MessageFailed events fire as usual, so an event listener logging the mailable class is your first diagnostic. For the wire, add a global HTTP client middleware (Http::globalRequestMiddleware) to log the outgoing request/response; do not log the auth header.RateLimited middleware) rather than retrying blindly..env per environment and confirm TLS verification is on; self-signed internal relays need an explicit opt-out, which should never ship enabled.How can I help you explore Laravel packages today?