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 Postmark Laravel Package

coconutcraig/laravel-postmark

Laravel mail transport for Postmark, sending transactional email through Postmark's API instead of SMTP. Drop-in driver configuration, supports Laravel notifications and mailables, and handles Postmark-specific features like message streams and tags with minimal setup.

View on GitHub
Deep Wiki
Context7

Getting Started

This package registers a postmark transport with Laravel's mail manager so you can keep using Mail::send() while mail actually goes out over Postmark's HTTP API instead of SMTP. It is a transport shim only — it does not wrap the full Postmark API (no templates, no broadcasts, no inbound webhooks). Your mental model should be: it swaps the Swift_Transport for a Postmark one, nothing more.

1. Confirm you're on a version that can run it. This is a Swift Mailer-based transport, so it only works on Laravel versions that still ship Swift Mailer — that means Laravel 3.x–8.x. On Laravel 9+ Swift Mailer is gone and this package will not work; use symfony/postmark-mailer with the framework's built-in postmark transport instead. Check composer.json on the repo for the exact Laravel/PHP constraint your pinned version needs before you install.

2. Install:

composer require coconutcraig/laravel-postmark

The service provider is auto-discovered. There is nothing to publish — the package does not ship a config file, it reads from config/services.php.

3. Add the server token to config/services.php:

'postmark' => [
    'token' => env('POSTMARK_TOKEN'),
],

4. Register a mailer in config/mail.php:

'mailers' => [
    'postmark' => [
        'transport' => 'postmark',
    ],
],

5. Point the app at it:

MAIL_MAILER=postmark
POSTMARK_TOKEN=your-server-token

First use case — a normal mailable, unchanged:

Mail::to($user->email)->send(new WelcomeEmail($user));

Where to look first. config/mail.php and config/services.php. Almost every "it doesn't work" report for this package is a mismatched mailer name or a missing/incorrectly-keyed token — not a code bug. Then read the repo README once, because the expected env key changed across releases (POSTMARK_TOKEN on later versions vs. older POSTMARK_API_KEY / POSTMARK_SECRET). If the token key your installed version expects isn't the one you set, you'll get an auth error from Postmark, not a helpful config error from Laravel.

Implementation Patterns

Keep the default mailer on log/smtp and add Postmark as a second, named mailer. This is the single most useful pattern. You get real transactional delivery in production, log or array driver locally and in CI, and no code changes between them:

// config/mail.php — leave 'default' => env('MAIL_MAILER', 'log')
'postmark' => ['transport' => 'postmark'],

Then route per call or per Mailable when needed:

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

Use Mail::mailer() for runtime routing by audience, not by environment. A common setup: transactional mail (receipts, password resets, verification) goes to Postmark for deliverability guarantees; bulk notifications or low-value mail route to a cheaper provider. Encoding this in a helper or a base Mailable keeps the choice out of call sites.

Queue everything. The transport makes a synchronous HTTP call to Postmark on every send. A slow or failing API response should never block a web request:

class Receipt extends Mailable implements ShouldQueue
{
    public $tries = 3;
    public $backoff = [10, 60, 300];
}

This is where the package shines — queued mailing works exactly as normal because the transport is just a transport.

Use failover transports (Laravel 7+) so a Postmark outage degrades to SMTP instead of dropping messages:

'postmark-failover' => [
    'transport' => 'failover',
    'mailers'   => ['postmark', 'smtp'],
],

Set 'transport' => 'failover' on the mailer that actually gets used, and point mailers at your Postmark and SMTP mailers.

Testing needs no Postmark knowledge. Because the seam is the transport layer, Mail::fake() and assertQueued() / assertSent() behave identically to SMTP. Your test suite never imports from this package. For an end-to-end smoke test, write a tiny artisan command or tinker one-liner that sends to a Postmark inbound address and confirm it in the Postmark activity log — that's the only place you need the real API.

Keep content in Mailables, not Mail::raw(). Mail::raw() works but skips views, localization, and the attachment API — the very abstractions you're using Laravel Mail for. If you find yourself reaching for raw, you're usually one build() method away from a better Mailable.

Postmark-specific features are out of scope. Tags, metadata, message streams, template aliases, and batch sends are not exposed by this transport. Do not try to smuggle them in via custom headers — Postmark won't promote arbitrary headers into its API fields. If you need those, call wildbit/postmark-php directly from a service class for that specific mail, and keep this package for plain transactional sends.

Gotchas and Tips

The big one: this package is superseded. Laravel 9+ ships a first-party Postmark transport through symfony/postmark-mailer. On Laravel 9+, Swift Mailer no longer exists and this package will not function. Before adding it to a new project, check the framework version first — if you're on 9+, you're done without this dependency.

Last release was October 2020, zero dependents. Treat the dependency as frozen: pin it exactly ("coconutcraig/laravel-postmark": "x.y.z"), and treat any Laravel major upgrade as a compatibility gate that requires re-evaluating whether to migrate to the native transport. There is no maintainer SLA and no ecosystem pressure keeping it patched.

Env key changed between releases. POSTMARK_TOKEN on later versions; older releases looked for POSTMARK_API_KEY or POSTMARK_SECRET. If you get an authentication error from Postmark but you're sure the token is valid, this is why. Grep the installed source under vendor/coconutcraig/laravel-postmark/src/ for the exact key.

No config file to publish. The package reads config/services.php directly. Don't look for config/postmark.php — it isn't there. This also means you can't override behavior via config; if you need to change something (timeouts, connection, tags), it requires forking.

Silent-ish failure modes to watch for in production:

  • Invalid/rotated token → Postmark returns 401; with a queued send you'll only see it in failed_jobs, not logs, unless you alert on it.
  • Unverified sender domain/signature → Postmark accepts the API call but flags the message; check the Postmark activity log, not your app logs.
  • Rate limiting / timeouts → Postmark throttles; the transport doesn't retry. Your queue retry policy ($tries, $backoff) is the only retry layer.
  • Bounces and complaints → handled entirely on Postmark's side via webhooks, which this package does not consume. If your app needs to suppress on hard bounce, you need a webhook endpoint and a suppressed-recipients table of your own.

No extension points worth using. There's no documented way to inject custom headers, override the API payload, or hook the HTTP client. If you need to change behavior, the realistic path is to fork under MIT (the license permits it) — but weigh that against a 1–3 day migration to the native transport, which is usually the cheaper long-term move.

Debugging checklist when mail "isn't working":

  1. php artisan config:clear — config caching bites people who edit config/mail.php/services.php.
  2. MAIL_MAILER and the mailer entry name match.
  3. Token env var name matches what the installed version of the package reads.
  4. Mail::mailer('postmark')->raw('test', fn($m) => $m->to(...)->subject('test')) from tinker — this narrows whether the problem is your Mailable or the transport.
  5. Check storage/logs/laravel.log and failed_jobs — queued sends fail silently if you're not watching.
  6. Check Postmark's activity log for the recipient — if the message appears there, the bug is in your Mailable content, not the transport.

Rollback path. Because it's just a transport swap, rollback is a one-line config change plus a config:cache clear. Document this in your runbook and keep a working SMTP (or SES/Mailgun) mailer defined in config/mail.php so the swap is instant. That's the one operational advantage of this kind of package: the blast radius of removing it is small, which makes it easy to defer migration — and easy to do it later without drama.

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.
jinas/dhivatar
store-doc/laravel-storage-driver
plus-it/laravel-translation-manager
diephp/perhaps
diephp/telegraph
osmaviation/spreadsheet
microsoft/fluentui-system-icons
mmccook/php-json-canonicalization-scheme
middlewares/firewall
qubiqx/montapacking
nyoncode/laravel-package-console
pnscripts/filament-broadquill-lite
pnscripts/laravel-sentfold-lite
vleap/mx-sdk-laravel-internal
twidpay/utils
htetmyathlaing/scout-elasticsearch-driver
artisan-toolbox/front-awesome
ls/client-assistant
users-au/laravel-client
cgv/localized-resource