Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Laravel Postal Laravel Package

synergitech/laravel-postal

Laravel Postal is a lightweight Laravel integration for the Postal mail server API. Send transactional email and manage messages, domains, and webhooks through an expressive client, with simple configuration for reliable mail delivery in any Laravel application.

View on GitHub
Deep Wiki
Context7

Getting Started

Postal is a self-hosted mail server; this package's job is to make Laravel's Mail facade speak its HTTP API instead of SMTP. Day-to-day, that means you get Laravel Mailables, queues, and events for free, while Postal handles delivery, bounce tracking, and the message log UI.

Minimal path:

  1. composer require synergitech/laravel-postal — on Laravel 13 you need v5.0.4+ (see Gotchas).
  2. Publish the config (php artisan vendor:publish and pick the Postal provider's tag — check php artisan vendor:publish --list rather than guessing the tag name).
  3. Fill in the two things that actually matter:
    • POSTAL_HOST — the full base URL of your Postal install, including scheme (e.g. https://postal.example.com).
    • POSTAL_KEY — the per-server API credential from Postal's UI (Your Server → Credentials → API), not an org/user key.
  4. Register a mailer in config/mail.php:
    'postal' => [
        'transport' => 'postal',
    ],
    
    and set MAIL_MAILER=postal. Credentials live in config/postal.php, so the mailer entry stays thin.
  5. First use case: send a throwaway message and confirm it lands in Postal's message log before you build anything else.
    // php artisan tinker
    Mail::raw('hello from postal', fn ($m) => $m->to('[email protected]')->subject('smoke test'));
    

Where to look first when it misbehaves, in order: Postal's own message log (did Postal receive it at all?) → storage/logs/laravel.log (did the transport throw?) → your config/postal.php (host/key correct?) → DNS verification for the sending domain.

The canonical first real use case is transactional mail: password resets, receipts, notifications. If you only need "send mail from Laravel," a Mailable + this transport is the whole integration — don't reach for the API client layer until you need bounce/message introspection.

Implementation Patterns

Treat it as a transport, not an SDK. The 90% path is plain Laravel mail. Write normal Mailables, use Mail::to()->send(), and let the package do the rest. Anything you'd do with SMTP in Laravel transfers unchanged.

Per-mailer credentials for multi-domain setups. Because credentials sit in the package config while config/mail.php just names the transport, the cleanest pattern for "marketing from one domain, receipts from another" is multiple mailers pointing at different config keys, then selecting at call time:

Mail::mailer('postal_billing')->to($user)->send(new Receipt($order));

Keep the credential blocks in config/postal.php keyed by mailer name so one config:cache covers everything.

Queue everything. Mailables implementing ShouldQueue work transparently — the transport is resolved inside the worker. The one discipline required: workers must see identical config to web nodes (same .env, same config:cache state), otherwise you get mystifying 401s that only appear in production.

Use Mail::fake() in tests, and don't try to unit-test the transport. Assert the Mailable was sent and its content — that's your contract. If you want real integration coverage, stand up Postal via its Docker image in CI and send one message through a dedicated test mailer; verify it appears in the log rather than checking inboxes.

Reach for the API layer only for Postal-specific concerns. Things SMTP can't tell you — message status, bounce detail, per-message info — are why the package exposes an API client alongside the transport. Wrap those calls in small services or jobs with explicit timeouts and a bounded retry count; they're network calls to infrastructure you own, so failures should degrade (log and move on) rather than break a user-facing request.

Tag messages from the start. Add a tag/header identifying the mail type (e.g. receipt, password-reset) on the Mailable. It costs one line and turns Postal's dashboard into something you can actually filter during an incident, when "which of our six mail types is bouncing?" is the question you're asking.

Handle bounces yourself. The package sends; it does not receive webhooks. Plan a small controller for Postal's webhook events if delivery feedback matters to your product — otherwise hard bounces silently accumulate and you keep mailing dead addresses.

Gotchas and Tips

  • Version pinning is the whole story of v5.0.4. The release is a pure compatibility widening for Laravel 13 — no API changes, no deprecations. On L13, require ^5.0.4; on older Laravel, ^5.0 is fine and you gain nothing by upgrading. Read it as "unblocked," not "new features."
  • "Server API key" vs. everything else. Postal has multiple credential scopes and the UI doesn't shout about the difference. Using an org-level key where a server key is expected produces a flat 401 with no hint. Regenerate from the specific server you're sending through.
  • Host URL formatting. Missing https:// or a stray trailing slash are the two most common config typos and both surface as opaque connection/path errors rather than validation messages. Normalize it once and eyeball it.
  • "It sends but nothing arrives" is almost never the package. Postal silently drops or rejects mail from unverified sending domains. Check SPF, DKIM, and return-path records in Postal's domain view before you debug PHP. Same for MAIL_FROM_ADDRESS — a from-address on an unverified domain gets rejected or rewritten.
  • config:cache after every env change. This bites people who edit .env on a cached deployment and conclude the package is broken. php artisan config:clear is your first debugging step, always.
  • Retries can duplicate mail. Queued jobs with $tries > 1 will resend if the worker dies after Postal accepted the message but before the job was marked done. Keep tries low, and prefer marking failure over aggressive retry for transactional mail.
  • Large attachments and default HTTP timeouts. If you send PDFs, the default client timeout in the transport config may be too tight; check the package config for a timeout/HTTP-client option before assuming Postal is slow.
  • Debug the wire, not the abstraction. Laravel's MessageSending/MessageSent events plus Http::globalRequestMiddleware() (or Guzzle middleware if the package pins its own client) will show you the exact request and response body — far faster than reading transport source.
  • Extension point worth knowing: the transport is registered through the mail manager, so a custom driver name or a decorated client is a small service-provider binding away if you need to inject logging, metrics, or a proxy. Do that rather than forking the package.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
pharaonic/laravel-notification-dependencies
beautinow/translug
dev-brainiac/bot-cloaked
octoberrain/html
octoberrain/extension
octoberrain/exception
octoberrain/contracts
frankyso/ipaymu
3mdikram/worldmoneywords
snowsoft/config
itmosoft/laravel-subscriptions
larva/laravel-ai-qwen
adachsoft/api-version-bundle
cgv/localized-resource
noith/settings
kda/laravel-package
kda/filament-dynamic-navigation
bypersoft/signbox
pairus/product-data
fayne/laravel-filament-cms-core